Empresas
Empregos
  • Sobre nós
  • Soluções
    • Publicação de vagas
      Publique sua vaga e receba candidatos qualificados em 48h.
    • Avaliações de candidatos
      Mais de 500 testes técnicos e psicológicos, mais anti-fraude.
    • Headhunting
      Busca executiva personalizada do início ao fim.
    • Folha de Pagamento + EOR
      Dispersão de folha e EOR em mais de 15 países da LATAM.
  • Preços
  • Empregos

0

226
Visualizações
Dynamic form returns the same value for the first and second 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.

over 4 years ago · Santiago Trujillo
2 Respostas
Responde à pergunta

0

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

over 4 years ago · Santiago Trujillo Relatório

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

over 4 years ago · Santiago Trujillo Relatório
Responde à pergunta
Encontrar trabalhos remotos

Descubra a nova forma de encontrar um emprego!

melhores empregos
Principais categorias de trabalho
Empresas
Postar vaga Preços Comercial
Jurídico
Termos e Condições Política de privacidade
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Recomende algumas ofertas para mim
Preciso de ajuda