How to focus text input on transition end? - reactjs

If the Mui Collapsible has extended I want to show a text input that is focused. The autofocus property does not work. Is there away to achive the focus?
Sandbox
<Collapse in={checked}>
<input autofocus type="text" />
</Collapse>

You can assign a ref to input element and make it focused in the useEffect according to the value of checked.
Sandbox
export default function SimpleCollapse() {
const classes = useStyles();
const [checked, setChecked] = React.useState(false);
const textInput = React.useRef(null);
const handleChange = () => {
setChecked((prev) => !prev);
};
React.useEffect(() => {
if (checked) {
textInput.current.focus();
}
}, [checked]);
return (
<div className={classes.root}>
<FormControlLabel
control={<Switch checked={checked} onChange={handleChange} />}
label="Show"
/>
<div className={classes.container}>
<Collapse in={checked}>
<Paper elevation={4} className={classes.paper}>
<input ref={textInput} type="text" />
</Paper>
</Collapse>
</div>
</div>
);
}

Related

Why my checkbox doesn't work in my Dialog?

I create a component for my Dialog and my Checkbox my issue is when my checkbox is not in the Dialog the update works but when it's inside it doesn't work. I don't understand why.
const Popup = ({ title, handleClose, openned, children }) => {
return (
<Dialog className='react-popup-template' fullWidth={true} maxWidth='sm' open={openned} onClose={handleClose} aria-labelledby="parent-modal-title" aria-describedby="parent-modal-description">
<DialogContent id="modal-description" >
<div>
{title && <div><h4 style={{ textAlign: 'center', fontWeight: 'bold', fontSize : '23px' }}>{title}</h4><br/></div>}
{children}
</div>
</DialogContent>
</Dialog>
);
}
const CheckBox = (value, onChange) => {
return (
<label>
<input type='checkbox' value={value} onChange={onChange} />
</label>)
}
const App = () =>{
const [openPopup, setOpenPopup] = React.useState(false)
const [checked, setChecked] = React.useState(false)
const [title, setTitle] = React.useState('')
const [description, setDescription] = React.useState('')
const showModal = (title) =>{
setTitle(title)
setDescription(<CheckBox value={checked} onChange={() => {setChecked(!checked)}} />)
}
return (
<button onClick={() => {showModal('Title')}}>showModal</button>
<PopupTemplate title={title} handleClose={() => { setOpenPopup(false) }} openned={openPopup}>
{description}
</PopupTemplate>)
}
In your Checkbox you should either destructure your props
const CheckBox = ({ value, onChange }) => {
return (
<label>
<input type="checkbox" value={value} onChange={onChange} />
</label>
);
};
Or use your props via the props value
const CheckBox = (props) => {
return (
<label>
<input type="checkbox" value={props.value} onChange={props.onChange} />
</label>
);
};
EDIT:
The state only updates the first time you click the checkbox. Using the callback in the setChecked method will solve this.
...
setDescription(
<CheckBox
value={checked}
onChange={() => {
setChecked((prevChecked) => !prevChecked);
}}
/>
);
...
PS: I don't now if its just a copy/paste error, but you're missing setOpenPopup(true) in your showModal function.
Try this, as mui uses forwarRef for its components this should work,
setDescription(<CheckBox checked={checked} onChange={e => setChecked(!checked)} />)

How to lift up DatePicker(#mui) value to a parent component?

I've just started to study ReactJS and want to write a simple app with DatePicker. My idea is to create a component, called, DateTimeSelector (Child component), with DatePicker, and to call DateTimeSelector in App.js (Parent component) multiple times (at least, to specify start and end date).
And I'm stuck at lifting up state from child to parent component.
Here is my code:
1) DateTimeSelector (Child) component
...
export default function DateTimeSelector(props) {
const [value, setValue] = React.useState(null);
function onChangeHandler(newValue) {
setValue(newValue);
console.log(newValue);
}
return (
<LocalizationProvider dateAdapter={AdapterDateFns}>
<DatePicker label={props.text}
value={value}
onChange={onChangeHandler}
renderInput={(params) => <TextField {...params} />}
/>
</LocalizationProvider>
);
}
2) Main (Parent) component
Within this component I want to output values of each DateTimeSelector component in console and, for example, print these values in .
Now in console I see only values from my DateTimeSelector component, but not from the parent.
So, my question is how to pass date values from DatePickers to parent component as the state variables: startDate and endDate?
...
function App() {
const [startDate, setStartDate] = React.useState(null);
const [endDate, setEndDate] = React.useState(null);
function startDateChangeHandler(value) {
setStartDate(value);
console.log(startDate);
}
function endDateChangeHandler(value) {
setEndDate(value);
console.log(endDate);
}
return (
<Container>
<Grid container spacing={2}>
<Grid item xs={3}>
<div>
<DateTimeSelector text='Start Date'
value={startDate}
onChange={startDateChangeHandler} />
</div>
<div>
<DateTimeSelector text='End Date'
value={endDate}
onChange={endDateChangeHandler}/>
</div>
</Grid>
<Grid item xs={9}>
<div><p>Start Date: {startDate ? JSON.stringify(startDate) : null}</p>
<p>End Date: {endDate ? JSON.stringify(endDate) : null}</p>
</div>
</Grid>
</Grid>
</Container>
);
}
export default App;
I found the solution! Thanks for the advise not to have state in Child!
DateTimeSelector (Child)
export default function DateTimeSelector(props) {
function onChangeHandler(date) {
props.onChange(date);
}
return (
<LocalizationProvider dateAdapter={AdapterDateFns}>
<DatePicker label={props.text}
value={props.date}
onChange={onChangeHandler}
renderInput={(params) => <TextField {...params} />}
/>
</LocalizationProvider>
);
}
App (Parent)
function App() {
const [startDate, setStartDate] = React.useState(null);
const [endDate, setEndDate] = React.useState(null);
function startDateChangeHandler(date) {
console.log('startDateChangeHandler');
setStartDate(date);
console.log(startDate);
}
function endDateChangeHandler(date) {
console.log('endDateChangeHandler');
setStartDate(date);
console.log(endDate);
}
return (
<Container>
<Grid container spacing={2}>
<Grid item xs={3}>
<div>
<DateTimeSelector text='Start Date'
date={startDate}
onChange={startDateChangeHandler} />
</div>
<div>
<DateTimeSelector text='End Date'
date={endDate}
onChange={endDateChangeHandler}/>
</div>
</Grid>
<Grid item xs={9}>
<div>
<p id='start_date'>Start Date: {startDate ? JSON.stringify(startDate) : null}</p>
<p id='end_date'>End Date: {endDate ? JSON.stringify(endDate) : null}</p>
</div>
</Grid>
</Grid>
<Button variant="contained">Hello World!</Button>
</Container>
);
}

How to Use the Same handleChange Event on Different Components

I'm trying to make a function which can disabled textfield when checkbox is checked.
The function is doing what I wanted, but when I trying to make multiple fields with the same function, the components are just bind together.
I've tried to handle them with event.target but I think I messed it up so I deleted the lines.
What should I do so they can separated into two components working individually?
Here's my code:
import React from 'react';
import Checkbox from '#material-ui/core/Checkbox';
import TextField from '#material-ui/core/TextField';
export default function Checkboxes() {
const [required, setRequired] = React.useState(true);
const [checked, setChecked] = React.useState(false);
const [disabled, setDisabled] = React.useState(false);
const handleChange = event => {
setRequired(!required);
setChecked(!checked);
setDisabled(!disabled);
};
return (
<div>
<form>
<TextField
required={required}
autoComplete="off"
id="standard-required"
label="text1"
disabled={disabled}
/>
<Checkbox
label="cb1"
checked={checked}
onChange={handleChange}
color="primary"
/>
</form>
<form>
<TextField
required={required}
autoComplete="off"
id="standard-required"
label="text1"
disabled={disabled}
/>
<Checkbox
label="cb1"
checked={checked}
onChange={handleChange}
color="primary"
/>
</form>
</div>
);
}
Here's my sandbox:
https://stackblitz.com/edit/react-ts-3zvmp5?file=demo.tsx
No need to use state for this purpose. This can be done with manipulating the DOM easily. I have edited your code here in the above link. Just pass the id of the input element to the function and disable it using normal javascript.
I solved my problem by myself.
export default function Checkboxes() {
const [required, setRequired] = React.useState({ setA: true, setB: true });
const [checked, setChecked] = React.useState({ setA: false, setB: false });
const [disabled, setDisabled] = React.useState({ setA: false, setB: false });
return (
<div>
<form>
<TextField
required={required.setA}
autoComplete="off"
id="standard-required"
label="text1"
disabled={disabled.setA}
/>
<Checkbox
label="cb1"
checked={checked.setA}
onChange={() => {
setRequired({ ...required, setA: !required.setA });
setChecked({ ...checked, setA: !checked.setA });
setDisabled({ ...disabled, setA: !disabled.setA });
}}
color="primary"
/>
</form>
<form>
<TextField
required={required.setB}
autoComplete="off"
id="standard-required"
label="text2"
disabled={disabled.setB}
/>
<Checkbox
label="cb2"
checked={checked.setB}
onChange={() => {
setRequired({ ...required, setB: !required.setB });
setChecked({ ...checked, setB: !checked.setB });
setDisabled({ ...disabled, setB: !disabled.setB });
}}
color="primary"
/>
</form>
</div>
);
}
Sandbox here.
However, I decided to separate the hooks.
It's not a large component so I decided to make hooks function individually to avoid any issue might happen in the same hook.
export default function Checkboxes() {
const [requiredA, setRequiredA] = React.useState(true);
const [checkedA, setCheckedA] = React.useState(false);
const [disabledA, setDisabledA] = React.useState(false);
const [requiredB, setRequiredB] = React.useState(true);
const [checkedB, setCheckedB] = React.useState(false);
const [disabledB, setDisabledB] = React.useState(false);
const handleChangeA = () => {
setRequiredA(!requiredA);
setCheckedA(!checkedA);
setDisabledA(!disabledA);
};
const handleChangeB = () => {
setRequiredB(!requiredB);
setCheckedB(!checkedB);
setDisabledB(!disabledB);
};
return (
<div>
<form>
<TextField
required={requiredA}
autoComplete="off"
id="standard-required"
label="text1"
disabled={disabledA}
/>
<Checkbox
label="cb1"
checked={checkedA}
onChange={handleChangeA}
color="primary"
/>
</form>
<form>
<TextField
required={requiredB}
autoComplete="off"
id="standard-required"
label="text2"
disabled={disabledB}
/>
<Checkbox
label="cb2"
checked={checkedB}
onChange={handleChangeB}
color="primary"
/>
</form>
</div>
);
}
sandbox here.

Jest/Enzyme: Mock function in Functional Component

I have a functional component in React. Here is the code
import React, { useState, Fragment } from "react";
import { makeStyles } from "#material-ui/core/styles";
import "./K8sIPCalculator.css";
import { initialState, checkBoxLabel } from "./FormMetaData";
import { checkPositiveInteger } from "./FormDataValidation";
const useStyles = makeStyles((theme) => ({
// Styles
}));
const K8sIPCalculator = (props) => {
const classes = useStyles();
let intialStateCopy = JSON.parse(JSON.stringify(initialState));
const [data, setData] = useState(intialStateCopy);
const handleCheckBox = (path, value) => {
const newData = { ...data };
if (path === "nodes" || path === "pods") {
if (!checkPositiveInteger(value)) {
newData[path].value = value;
newData[path].helperText = "It should be a positive integer!";
} else {
newData[path].value = value;
newData[path].helperText = "";
}
} else newData[path] = value;
setData(newData);
};
const calculate = () => {
// Does some calculation and update data state
};
const onSubmit = () => {
if (data.nodes.helperText !== "" || data.pods.helperText !== "") {
alert("Data is not correct");
return;
}
calculate();
};
const onReset = () => {
intialStateCopy = JSON.parse(JSON.stringify(initialState));
setData(intialStateCopy);
};
return (
<Fragment>
<h2 className="name">K8s IP Calculator</h2>
<form className={classes.formRoot}>
<Accordion
defaultExpanded={true}
classes={{ expanded: classes.expanded }}
>
<AccordionSummary
expandIcon={<ExpandMoreIcon />}
id="accordion1"
className={classes.summary}
>
<Typography className={classes.heading}>Results</Typography>
</AccordionSummary>
<AccordionDetails className="container">
<InputLabel className={classes.label}>
Total useable IPs required:
</InputLabel>
<TextField
disabled
className={classes.textDisabledInput}
id="ips-required-output"
variant="outlined"
value={data.total}
/>
<InputLabel className={classes.label} htmlFor="subnet-size-output">
Subnet Size required:
</InputLabel>
<TextField
disabled
className={classes.textDisabledInput}
id="subnet-size-output"
variant="outlined"
value={data.subnet_size}
/>
</AccordionDetails>
</Accordion>
<br />
<Accordion
defaultExpanded={true}
classes={{ expanded: classes.expanded }}
>
<AccordionSummary
expandIcon={<ExpandMoreIcon />}
id="accordion2"
className={classes.summary}
>
<Typography className={classes.heading}>K8s Details</Typography>
</AccordionSummary>
<AccordionDetails className="container">
<InputLabel className={classes.label}>Nodes:</InputLabel>
<TextField
size="small"
type="number"
onChange={(e) => handleCheckBox("nodes", e.target.value)}
className={classes.textInput}
id="nodes-input"
variant="outlined"
value={data.nodes.value}
helperText={data.nodes.helperText}
/>
<InputLabel className={classes.label} htmlFor="pods-input">
Pods:
</InputLabel>
<TextField
size="small"
type="number"
onChange={(e) => handleCheckBox("pods", e.target.value)}
className={classes.textInput}
id="pods-input"
variant="outlined"
value={data.pods.value}
helperText={data.pods.helperText}
/>
<div id="nodes-error"></div>
</AccordionDetails>
</Accordion>
<div className="button-container">
<Button
id="reset-button"
className="button"
variant="outlined"
color="primary"
size="small"
onClick={onReset}
startIcon={<UndoIcon />}
>
Reset
</Button>
<Button
id="submit-button"
className="button"
variant="contained"
color="primary"
size="small"
startIcon={<SaveIcon />}
onClick={onSubmit}
>
Submit
</Button>
</div>
</form>
</Fragment>
);
};
export default K8sIPCalculator;
Things, I am trying to test,
When input changes, I want to check if handleCheckBox function has been called
When submit button is called, I want to check if calculate function has been called
How can I call the setData function to update data through Jest/Enzyme
I tried something like this
const spy = jest.spyOn(K8sIPCalculator, "calculate");
But I got
Cannot spy the calculate property because it is not a function; undefined given instead
PS: I am able to assert data change after submit is called. But I want to check if calculate function is called.
const submit = wrapper.find("button#submit-button");
submit.simulate("click");
expect(wrapper.find("input#ips-required-output").props().value).toEqual(35);
expect(wrapper.find("input#subnet-size-output").props().value).toEqual(
"/26"
);

React Hooks - Input loses focus when adding or removing input fields dynamically

I have a form displayed in a modal window. This form is divided into several tabs. One of them has two grouped field: a dropdown list countries and a description textfield. There is an "Add button" which allows to create a new grouped field.
The problem is that each time, I filled the textfield, i lost the focus, because the form is re-rendered. I tryed to move the form outside of the default function but i still have the same issue.
I also set unique keys to each element, but still.
I know there is a lot of documentation of this, but despite this, its not working. I could set the autofocus, but when there is more than a one group field, the focus will go to the last element.
I am using Material UI (react 17)
Anyway, below is the code (which has been truncated for a better visibility) :
function GeoForm (props) {
return(
<React.Fragment>
<Autocomplete
id={"country"+props.i}
style={{ width: 300 }}
options={Object.keys(countries.getNames('fr')).map(e => ({code: e, label: countries.getNames('fr')[e]}))}
getOptionSelected={(option, value) => (option.country === value.country)}
classes={{
option: props.classes.option,
}}
defaultValue={props.x.country}
key={"country"+props.i}
name="country"
onChange={(e,v) => props.handleInputGeoCountryChange(e, v, props.i)}
getOptionLabel={(option) => (option ? option.label : "")}
renderOption={(option) => (
<React.Fragment>
{option.label}
</React.Fragment>
)}
renderInput={(params) => (
<TextField
{...params}
label="Choose a country"
variant="outlined"
inputProps={{
...params.inputProps,
autoComplete: 'new-password', // disable autocomplete and autofill
}}
/>
)}
/>
<TextField
id={"destination"+props.i}
onChange={e => props.handleInputGeoDestinationChange(e, props.i)}
defaultValue={props.x.destination}
name="destination"
key={"destination"+props.i}
margin="dense"
label="Destination"
type="text"
/>
{props.inputGeoList.length !== 1 && <button
className="mr10"
onClick={() => props.handleRemoveGeoItem(props.i)}>Delete</button>}
{props.inputGeoList.length - 1 === props.i &&
<Button
onClick={props.handleAddGeoItem}
variant="contained"
color="primary"
//className={classes.button}
endIcon={<AddBoxIcon />}
>
Add
</Button>
}
</React.Fragment>
)
}
export default function modalInfo(props) {
const classes = useStyles();
const [openEditDialog, setOpenEditDialog] = React.useState(false);
const handleAddGeoItem = (e) => {
console.log(e);
setInputGeoList([...inputGeoList, { country: "", destination: "" }]);
};
// handle input change
const handleInputGeoCountryChange = (e, v, index) => {
const list = [...inputGeoList];
list[index]['country'] = v;
setInputGeoList(list);
};
const handleInputGeoDestinationChange = (e, index) => {
const { name, value } = e.target;
console.log(name);
const list = [...inputGeoList];
list[index][name] = value;
setInputGeoList(list);
console.log(inputGeoList)
};
// handle click event of the Remove button
const handleRemoveGeoItem = index => {
const list = [...inputGeoList];
list.splice(index, 1);
setInputGeoList(list);
};
const TabsEdit = (props) => {
return(
<div className={classes.root}>
<form className={classes.form} noValidate onSubmit={onSubmit}>
<Tabs
orientation="vertical"
variant="scrollable"
value={value}
onChange={handleChange}
aria-label="Vertical tabs example"
className={classes.tabs}
>
[...]
<Tab label="Geo-targeting" {...a11yProps(4)} disableRipple />
</Tabs>
[...]
</TabPanel>
<TabPanel value={value} index={4}>
{
inputGeoList.map((x, i)=>{
return(
<GeoForm
inputGeoList={inputGeoList}
x={x}
i={i}
handleRemoveGeoItem={handleRemoveGeoItem}
handleInputGeoDestinationChange={handleInputGeoDestinationChange}
handleInputGeoCountryChange={handleInputGeoCountryChange}
handleAddGeoItem={handleAddGeoItem}
handleInputGeoDestinationChange={handleInputGeoDestinationChange}
classes={classes}
/>
)
})
}
</TabPanel>
<TabPanel value={value} index={5}>
Mobile-targeting
</TabPanel>
<DialogActions>
<Button onClick={props.handleClose} color="primary">
Annuler
</Button>
<Button type="submit" color="primary">
Enregistrer
</Button>
</DialogActions>
</form>
</div>
)
}
return (
<div>
<div>
<EditIconButton onClickEdit={() => setOpenEditDialog(true)} />
</div>
<div>
<EditDialog open={openEditDialog} handleClose={() => setOpenEditDialog(false)} >
<TabsEdit/>
</EditDialog>
</div>
</div>
);
codesandbox
Any help or suggestion are welcome. Thank you
TL;DR: Your TabsEdit component was defined within another component, thus React was remounting it as a new component each time, making the focused state to be lost. This Pastebin fixes your code, it maintains the focus as you type.
NL;PR: I suffered from this same issue for months, the props are not the only reference checked for reconciliation, the component's memory ref is too. Since the component's function ref is different each time, React process it as a new component, thus unmounting the previous component, causing the state to be lost, in your case, the focus.

Resources