I'm creating a custom dynamic form using Material UI
As we can see that the dynamic form component starts only with one delivery point form. We could add another delivery point form by clicking the "Add Delivery" button. Here is the example after clicking the "Add Delivery" button.
The problem is: the first form and the second form have the same values (if there are more than 1 form) after we gave input to one of the first or second forms as seen in the screenshot below. This is not the condition I want.
But the rest (third, fourth, and so on) forms are unique as seen in the screenshot below. This is the condition I want.
Here is the simple working code:
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;
App.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;
CodePudding user response:
You are not making a copy of deliveryPointBaseObject. But, repeating it. Hence, you are getting the same value for first 2. Changing it to below one corrects your mutation issue.
const deliveryPointBaseObject = {...initialFormObject.deliveryPointList[0]};