2

I have the following state:

const [state, setState] = React.useState({
    title: "",
    exchangeTypes: [],
    errors: {
        title: "",
        exchangeTypes: "",
      }
})

I am using a form validation in order to populate the state.errors object if a condition is not respected.

function formValidation(e){
    const { name, value } = e.target;
    let errors = state.errors;

    switch (true) {
      case (name==='title' && value.length < 4): 
        setState(prevState => ({
            errors: {                            // object that we want to update
                ...prevState.errors,             // keep all other key-value pairs
                [name]: 'Title cannot be empty' // update the value of specific key
            }
        }))
        break;
      default:
        break;
    }

  }

When I do so, the object DOES update BUT it deletes the value that I have not updated.

Before I call formValidation My console.log(state) is:

{
    "title": "",
    "exchangeTypes: [],
    "errors": {
            title: "",
            exchangeTypes: "",
    }
}

After I call formValidation My console.log(state) is:

{
    "errors": {
            title: "Title cannot be empty",
            exchangeTypes: ""
        }
}

SO my other state values have disappeared. There is only the errors object.

I followed this guide: How to update nested state properties in React

What I want:

{
    "title": "",
    "exchangeTypes: [],
    "errors": {
            title: "Title cannot be empty",
            exchangeTypes: "",
    }
}

What I get:

   {
        "errors": {
                title: "Title cannot be empty",
                exchangeTypes: "",
        }
    }
Magofoco
  • 5,098
  • 6
  • 35
  • 77

2 Answers2

2

unlike the setState in class component, setState via useState doesn't automatically merge when you use an object as the value. You have to do it manually

setState((prevState) => ({
  ...prevState, // <- here
  errors: {
    // object that we want to update
    ...prevState.errors, // keep all other key-value pairs
    [name]: 'Title cannot be empty', // update the value of specific key
  },
}));
Moinul Hossain
  • 2,176
  • 1
  • 24
  • 30
1

Though you can certainly use the useState hook the way you're doing, the more common convention is to track the individual parts of your component's state separately. This is because useState replaces state rather than merging it, as you've discovered.

From the React docs:

You don’t have to use many state variables. State variables can hold objects and arrays just fine, so you can still group related data together. However, unlike this.setState in a class, updating a state variable always replaces it instead of merging it.

So in practice, your code might look like the following:

const MyComponent = () => {
  const [title, setTitle] = useState('');
  const [exchangeTypes, setExchangeTypes] = useState([]);
  const [errors, setErrors] = useState({
    title: "",
    exchangeTypes: "",
  });

  function formValidation(e) {
    const { name, value } = e.target;

    switch (true) {
      case (name === 'title' && value.length < 4): 
        setErrors({
          ...errors,
          [name]: 'Title cannot be empty'
        });
        break;
      default:
        break;
    }
  }

  return (
    ...
  );
};
ericgio
  • 3,094
  • 4
  • 25
  • 44