Estoy creando un formulario dinámico personalizado usando la biblioteca Material UI https://mui.com/ como componente para mi aplicación React Js.
Aquí está el estado inicial del componente de formulario dinámico. 
Como podemos ver, el componente de formulario dinámico comienza solo con un formulario de punto de entrega. Podríamos agregar otro formulario de punto de entrega haciendo clic en el botón "Agregar entrega". Aquí está el ejemplo después de hacer clic en el botón "Agregar entrega".
El problema es: el primer formulario y el segundo formulario tienen los mismos valores (si hay más de 1 formulario) después de que le dimos entrada a uno de los primeros o segundos formularios como se ve en la captura de pantalla a continuación. Esta no es la condición que quiero.
Pero las formas restantes (tercera, cuarta, etc.) son únicas, como se ve en la siguiente captura de pantalla. Esta es la condición que quiero.
Aquí está el código de trabajo simple:
DeliveryPoint.jsx
const DeliveryPoint = (props) => { const { initialFormObject, formObject, setFormObject, collapseObject, setCollapseObject } = props; const classes = useStyles(); const deliveryPointBaseObject = initialFormObject.deliveryPointList[0]; const handleFormObjectChange = (inputEvent, inputIndex) => { let deliveryPointListData = [...formObject.deliveryPointList]; deliveryPointListData[inputIndex][inputEvent.target.name] = inputEvent.target.value; setFormObject((current) => ({ ...current, deliveryPointList: deliveryPointListData })); }; const handleDatePickerChange = (inputNewValue, inputIndex) => { let deliveryPointListData = [...formObject.deliveryPointList]; deliveryPointListData[inputIndex].deliveryDate = inputNewValue; setFormObject((current) => ({ ...current, deliveryPointList: deliveryPointListData })); }; const handleCollapseChange = (inputIndex) => { let deliveryPointListData = [...collapseObject.deliveryPointList]; deliveryPointListData[inputIndex] = !deliveryPointListData[inputIndex]; setCollapseObject((current) => ({ ...current, deliveryPointList: deliveryPointListData })); }; const handleAddFormItemButtonClick = () => { let deliveryPointListData = [...formObject.deliveryPointList]; deliveryPointListData.push(deliveryPointBaseObject); setFormObject((current) => ({ ...current, deliveryPointList: deliveryPointListData })); deliveryPointListData = [...collapseObject.deliveryPointList]; deliveryPointListData.push(false); setCollapseObject((current) => ({ ...current, deliveryPointList: deliveryPointListData })); }; const handleDeleteFormItemButtonClick = (inputIndex) => { let deliveryPointListData = [...formObject.deliveryPointList]; deliveryPointListData.splice(inputIndex, 1); setFormObject((current) => ({ ...current, deliveryPointList: deliveryPointListData })); deliveryPointListData = [...collapseObject.deliveryPointList]; deliveryPointListData.splice(inputIndex, 1); setCollapseObject((current) => ({ ...current, deliveryPointList: deliveryPointListData })); }; return ( <> {formObject.deliveryPointList.map((item, index) => ( <Box key={index} className={classes.formItemContainer}> <Box className={classes.formItemTitleContainer}> {/* TITLE */} <Typography variant="h6"> {index === 0 ? `Delivery point` : `#${index + 1} Delivery point`} </Typography> {/* ADD OR DELETE BUTTON */} {index === 0 ? ( <Button className={classes.formItemTitleButton} variant="outlined" startIcon={<IconAdd />} onClick={handleAddFormItemButtonClick} > Add Delivery </Button> ) : ( <Button className={classes.formItemTitleButton} variant="outlined" startIcon={<IconRemove />} color="error" onClick={() => handleDeleteFormItemButtonClick(index)} > Remove Delivery </Button> )} </Box> {/* CONSIGNEE */} <FormControl required variant="outlined" className={classes.formItemInput} > <InputLabel>Consignee</InputLabel> <OutlinedInput label="Consignee" type="text" name="consignee" value={item.consignee} onChange={(event) => handleFormObjectChange(event, index)} /> <FormHelperText> Search for name, street, city, or state by typing in the box. </FormHelperText> </FormControl> {/* DELIVERY DATE */} <LocalizationProvider dateAdapter={AdapterDateFns}> <DatePicker disableFuture label="Select Delivery Date" openTo="year" views={["year", "month", "day"]} value={item.deliveryDate} onChange={(newValue) => handleDatePickerChange(newValue, index)} renderInput={(params) => ( <TextField required className={classes.formItemInput} {...params} /> )} /> </LocalizationProvider> {/* COLLAPSE */} <Collapse in={collapseObject.deliveryPointList[index]} timeout="auto" unmountOnExit className={classes.formItemCollapse} > {/* DELIVERY INSTRUCTION */} <FormControl variant="outlined" className={classes.formItemInput}> <InputLabel>Delivery Instructions</InputLabel> <OutlinedInput label="Delivery Instructions" type="text" name="deliveryInstruction" value={item.deliveryInstruction} onChange={(event) => handleFormObjectChange(event, index)} /> </FormControl> </Collapse> {/* EXPAND BUTTON */} <Button variant="contained" disableElevation startIcon={ collapseObject.deliveryPoint ? ( <IconArrowDropUp /> ) : ( <IconArrowDropDown /> ) } className={classes.formItemButtonExpand} onClick={() => handleCollapseChange(index)} > {collapseObject.deliveryPoint ? "Hide full data entry" : "Fill in more complete data?"} </Button> </Box> ))} </> ); }; export default DeliveryPoint;Aplicación.jsx
const App = () => { const classes = useStyles(); const initialFormObject = { // DELIVERY POINT deliveryPointList: [ { consignee: "", deliveryDate: new Date(), deliveryInstruction: "" } ] // OTHER OBJECT ITEMS HERE }; const initialCollapseObject = { deliveryPointList: [false] // OTHER LIST ITEMS HERE }; const [formObject, setFormObject] = useState(initialFormObject); const [collapseObject, setCollapseObject] = useState(initialCollapseObject); return ( <Box className={classes.pageRoot}> {/* FORM */} <Box className={classes.formContainer}> {/* DELIVERY POINT */} <DeliveryPoint initialFormObject={initialFormObject} formObject={formObject} setFormObject={setFormObject} collapseObject={collapseObject} setCollapseObject={setCollapseObject} /> </Box> </Box> ); }; export default App;Aquí está la demostración completa https://codesandbox.io/s/stackoverflow-dynamic-form-wxrmd0
Pasos para reproducir:
¿Qué le pasa a mi gestión estatal y cuál es la solución para esto?
Nota: puede asumir el componente OultinedInput de Material UI como un elemento input HTML.
Está mutando el estado en sus handleFormObjectChange y handleDatePickerChange .
const handleFormObjectChange = (inputEvent, inputIndex) => { let deliveryPointListData = [...formObject.deliveryPointList]; // Mutates the nested property!! deliveryPointListData[inputIndex][inputEvent.target.name] = inputEvent.target.value; setFormObject((current) => ({ ...current, deliveryPointList: deliveryPointListData })); }; const handleDatePickerChange = (inputNewValue, inputIndex) => { let deliveryPointListData = [...formObject.deliveryPointList]; // Mutates the nested property!! deliveryPointListData[inputIndex].deliveryDate = inputNewValue; setFormObject((current) => ({ ...current, deliveryPointList: deliveryPointListData })); };Asegúrese de copiar superficialmente todas las propiedades y las propiedades anidadas que se actualizan. Esto garantiza que todas las actualizaciones creen nuevas referencias de objetos.
const handleFormObjectChange = (inputEvent, inputIndex) => { let deliveryPointListData = [...formObject.deliveryPointList]; deliveryPointListData[inputIndex] = { ...deliveryPointListData[inputIndex], // <-- shallow copy [inputEvent.target.name]: inputEvent.target.value }; setFormObject((current) => ({ ...current, deliveryPointList: deliveryPointListData })); }; const handleDatePickerChange = (inputNewValue, inputIndex) => { let deliveryPointListData = [...formObject.deliveryPointList]; deliveryPointListData[inputIndex] = { ...deliveryPointListData[inputIndex], // <-- shallow copy deliveryDate: inputNewValue, }; setFormObject((current) => ({ ...current, deliveryPointList: deliveryPointListData })); };No está haciendo una copia de deliveryPointBaseObject. Pero, repitiéndolo. Por lo tanto, obtiene el mismo valor para los primeros 2. Cambiarlo por debajo de uno corrige su problema de mutación.
const deliveryPointBaseObject = {...initialFormObject.deliveryPointList[0]};