动态表单为第一个和第二个输入返回相同的值
我正在使用 Material UI https://mui.com/ 库作为我的组件创建自定义动态表单React Js 应用程序。
我们所看到的,动态表单组件仅以一个交付点表单开始。 我们可以通过单击“添加交付”按钮来添加另一个交付点表单。 以下是单击“添加交付”按钮后的示例。
问题是:在我们向第一个或第二个表单之一提供输入后,第一个表单和第二个表单具有相同的值(如果有多个表单),如下面的屏幕截图 。这不是我想要的条件。
但其余(第三、第四等)形式都是独特的,如下面的屏幕截图所示。这就是我想要的条件。
这是简单的工作代码:
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;
这是完整的演示 https://codesandbox.io/s/stackoverflow-dynamic-form-wxrmd0
重现步骤:
- 单击“添加交付”按钮两次。所以会有 3 个交货点表格。
- 更改第一个交货点表格中的“收货人”或“交货日期”。因此,第二个交货点表单将具有与第一个表单相同的值。
我的状态管理出了什么问题,解决方案是什么?
注意:您可以将 Material UI 中的 OultinedInput
组件假定为 HTML input
元素。
I'm creating a custom dynamic form using Material UI https://mui.com/ library as the component for my React Js app.
Here is the initial state of the dynamic form component.
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;
Here is the full demo https://codesandbox.io/s/stackoverflow-dynamic-form-wxrmd0
Steps to reproduce:
- Click the "Add Delivery" button twice. So there will be 3 delivery point forms.
- Change the "consignee" or the "delivery date" in the first delivery point form. Therefore, the second delivery point form will have the same value as the first form.
What's wrong with my state management and what's the solution for this?
Note: you can assume the OultinedInput
component from Material UI as an HTML input
element.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
问题
您正在改变
handleFormObjectChange
和handleDatePickerChange
处理程序中的状态。解决方案
确保浅复制所有正在更新的属性和嵌套属性。这确保所有更新都会创建新的对象引用。
Issue
You are mutating the state in your
handleFormObjectChange
andhandleDatePickerChange
handlers.Solution
Ensure you are shallow copying all properties and nested properties that are being updated. This ensures all updates create new object references.
您没有制作deliveryPointBaseObject 的副本。但是,重复一遍。因此,您将获得第一个 2 的相同值。将其更改为以下值可以纠正您的突变问题。
const DeliveryPointBaseObject = {...initialFormObject.deliveryPointList[0]};
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]};