Reputation: 113
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>
);
Any help or suggestion are welcome. Thank you
Upvotes: 1
Views: 1488
Reputation: 2693
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.
Upvotes: 3