动态形式 returns 第一和第二个输入相同的值

Dynamic form returns the same value for the first and second input

我正在使用 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/Whosebug-dynamic-form-wxrmd0

重现步骤:

  1. 点击“添加配送”按钮两次。所以会有3个送货点表格。
  2. 更改第一个交货点表格中的“收货人”或“交货日期”。因此,第二个交货点表格将与第一个表格具有相同的值。

我的状态管理出了什么问题,解决方案是什么?

注意:您可以将 Material UI 中的 OultinedInput 组件假设为 HTML input 元素。

问题

您正在改变 handleFormObjectChangehandleDatePickerChange 处理程序中的状态。

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
  }));
};

解决方案

确保您浅复制所有正在更新的属性和嵌套属性。这确保所有更新都创建新的对象引用。

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
  }));
};

您没有复制 deliveryPointBaseObject。但是,重复一遍。因此,您将获得前 2 个相同的值。将其更改为低于 1 可以纠正您的突变问题。

const deliveryPointBaseObject = {...initialFormObject.deliveryPointList[0]};