动态表单为第一个和第二个输入返回相同的值

发布于 2025-01-10 02:07:57 字数 8674 浏览 1 评论 0原文

我正在使用 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

重现步骤:

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

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

注意:您可以将 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.
enter image description here

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.

enter image description here

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.

enter image description here

But the rest (third, fourth, and so on) forms are unique as seen in the screenshot below. This is the condition I want.

enter image description here

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:

  1. Click the "Add Delivery" button twice. So there will be 3 delivery point forms.
  2. 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

梦中楼上月下 2025-01-17 02:07:57

问题

您正在改变 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
  }));
};

编辑dynamic-form-returns-the-same-value-for-the-first-and-second-input

Issue

You are mutating the state in your handleFormObjectChange and handleDatePickerChange handlers.

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

Solution

Ensure you are shallow copying all properties and nested properties that are being updated. This ensures all updates create new object references.

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

Edit dynamic-form-returns-the-same-value-for-the-first-and-second-input

何必那么矫情 2025-01-17 02:07:57

您没有制作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]};

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文