Material-UI TextField loses focus on every onChange

Viewed 3659

I am creating the following component:

component to add medicines

It will contain an array of objects, where each object is a prescription, with the medicine name from the select and a TextField for the Dosis.

My problem is that the TextField loses focus on every onChange() and is very frustrating because it cannot be edited on a single focus.

This is my component :

const MedicineSelect = ({ medications, setMedications, ...props }) => {
    const { medicines } = useMedicines()
    const classes = useStyles()

    const handleChange = (index, target) => {
        // setAge(event.target.value)
        const newMedications = cloneDeep(medications)
        newMedications[index][target.name] = target.value
        setMedications(newMedications)
    }

    const handleAddMedicine = () => {
        const newMedications = cloneDeep(medications)
        newMedications.push({ medicine: '', dosis: '', time: '' })
        setMedications(newMedications)
    }

    const handleDeleteMedicine = (index) => {
        console.log('DELETE: ', index)
        const newMedications = cloneDeep(medications)
        newMedications.splice(index, 1)
        setMedications(newMedications)
    }

    return (
        <Paper style={{ padding: 5 }}>
            <List>
                {medications.map((medication, index) => (
                    <ListItem key={nanoid()} divider alignItems='center'>
                        <ListItemIcon>
                            <Tooltip title='Eliminar'>
                                <IconButton
                                    className={classes.iconButton}
                                    onClick={() => handleDeleteMedicine(index)}
                                >
                                    <HighlightOffOutlinedIcon />
                                </IconButton>
                            </Tooltip>
                        </ListItemIcon>
                        <FormControl className={classes.formControl}>
                            <InputLabel
                                id={`${index}-select-${medication}-label`}
                            >
                                Medicamento
                            </InputLabel>
                            <Select
                                labelId={`${index}-select-${medication}-label`}
                                id={`${index}-select-${medication}`}
                                name='medicine'
                                value={medication.medicine}
                                onChange={(event) =>
                                    handleChange(index, event.target)
                                }
                            >
                                {medicines.map((medicine) => (
                                    <MenuItem
                                        key={nanoid()}
                                        value={medicine.name}
                                    >
                                        {medicine.name}
                                    </MenuItem>
                                ))}
                            </Select>
                        </FormControl>
                        <TextField
                            // fullWidth
                            id={`${index}-text-${medication}`}
                            label='Dosis'
                            name='dosis'
                            onChange={(event) =>
                                handleChange(index, event.target)
                            }
                            value={medication.dosis}
                        />
                    </ListItem>
                ))}
                <Button onClick={handleAddMedicine}>+ agregar</Button>
            </List>
        </Paper>
    )
}

And here is where I set the component:

const [medications, setMedications] = useState([
        { medicine: '', dosis: '', time: '' },
    ])
...
<Grid item md={12} xs={12}>
                                <Accordion>
                                    <AccordionSummary
                                        expandIcon={<ExpandMoreIcon />}
                                        aria-controls='panel1a-content'
                                        id='panel1a-header'
                                    >
                                        <Typography variant='h4'>
                                            Tratamiento:
                                        </Typography>
                                    </AccordionSummary>
                                    <AccordionDetails>
                                        <Container disableGutters>
                                            <MedicineSelect
                                                medications={medications}
                                                setMedications={setMedications}
                                            />
                                        </Container>
                                    </AccordionDetails>
                                </Accordion>
                            </Grid>
...

Adding and removing objects from the array works perfect. selecting the medicine from the select, also works perfect. the only problem I have is when editing the Dosis TextField, with every character, the focus is lost and I have to click again on the TextField.

Please help me getting this fixed!!!

1 Answers

After searching a lot, finally I found the solution. Actually when using nanoid() to create unique keys, on every state update React re-renders all components and since the id of both the List and the TextField component are regenerated by nanoid on every render, React loses track of the original values, that is why Focus was lost.

What I did was keeping the keys unmuttable:

<ListItem key={`medication-${index}`} divider alignItems='center'>

and

<TextField
    key={`dosis-${index}`}
    fullWidth
    // id={`${index}-dosis-${medication}`}
    label='Dosis'
    name='dosis'
    onChange={(event) =>
        handleChange(index, event.target)
    }
    value={medication.dosis}
/>
Related