Rashomon
Rashomon

Reputation: 6792

setState only setting last input when using object as state

Im trying to create a form with React. This form uses a custom Input component I created various times. In the parent form Im trying to get a complete object with all names and all values of the form:

  {inputName: value, inputName2: value2, inputName3: value3}

For this, I created a 'component updated' hook, that calls the function property onNewValue to send the new value to the parent (two way data binding):

  useEffect(() => {
    if (onNewValue) onNewValue({ name, value });
  }, [value]);

The parent form receives the data in the handleInputChange function:

export default () => {
  const [values, setValues] = useState({});

  const handleInputChange = ({
    name,
    value
  }: {
    name: string;
    value: string | number;
  }): void => {
    console.log("handleInputChange", { name, value }); // All elements are logged here successfully
    setValues({ ...values, [name]: value });
  };

  return (
    <>
      <form>
        <Input
          name={"nombre"}
          required={true}
          label={"Nombre"}
          maxLength={30}
          onNewValue={handleInputChange}
        />
        <Input
          name={"apellidos"}
          required={true}
          label={"Apellidos"}
          maxLength={60}
          onNewValue={handleInputChange}
        />
        <Input
          name={"telefono"}
          required={true}
          label={"Teléfono"}
          maxLength={15}
          onNewValue={handleInputChange}
        />
        <Input
          name={"codigoPostal"}
          required={true}
          label={"Código Postal"}
          maxLength={5}
          onNewValue={handleInputChange}
          type={"number"}
        />
      </form>
      State of values: {JSON.stringify(values)}
    </>
  );
};

This way all elements from all inputs should be set on init:

  {"codigoPostal":"","telefono":"","apellidos":"","nombre":""}

But for some reason only the last one is being set:

  {"codigoPostal":""}

You can find the bug here: https://codesandbox.io/s/react-typescript-vx5py

Thanks!

Upvotes: 1

Views: 1388

Answers (4)

 const [ list, setList ] = useState( [ ] );

correct:

 setList ( ( list ) => [ ...list, value ] )

avoid use:

 setList( [ ...list, value ] )

Upvotes: 0

Oleg Levin
Oleg Levin

Reputation: 3621

The updated and fixed code, look at:

https://codesandbox.io/s/react-typescript-mm7by

look at:

  const handleInputChange = ({
    name,
    value
  }: {
    name: string;
    value: string | number;
  }): void => {
    console.log("handleInputChange", { name, value });
    setValues(prevState => ({ ...prevState, [name]: value }));
  };

Upvotes: 0

Agney
Agney

Reputation: 19234

The set state process in React is an asynchronous process. Therefore even if the function is called, values has not updated the previous state just yet.

To fix, this you can use the functional version of setState which returns the previous state as it's first argument.

setValues(values=>({ ...values, [name]: value }));

Upvotes: 3

Joseph D.
Joseph D.

Reputation: 12174

useState() doesn't merge the states unlike this.setState() in a class.

So better off separate the fields into individual states.

const [nombre, setNombre] = useState("")
const [apellidos, setApellidos] = useState("")
// and so on

UPDATE:

Given setValue() is async use previous state during init.

setValues((prevState) => ({ ...prevState, [name]: value }));

Upvotes: 1

Related Questions