React setState not merging nested state as expected

React setState not merging nested state as expected

When using setState with a nested object and attempting to update just certain attributes of the state object, an expected state may be obtained, as will be detailed below.

Because React only uses shallow state merging, the result isn't what you'd anticipate.

When you update your state improperly, you run the risk of changing its original shape, which is frequent. Assume your state variable includes a nested object, and you want to change one of the nested object's properties. You'd think React would update the target property while keeping the original state's form, but that's not the case.

Rather of updating a single attribute of the nested state object and expecting Right to merge the objects, just duplicate the original state object and change it before calling setState with the modified copy of the original state object.

This should work, but there is an alternative! Let's go through this in more detail first.

In React, the state is immutable which implies that if you use this state in your component:

state = {
  fullName: "Bob Fisher",
    email: 'a@email.com'
}

Updating the state as follows will fail with errors:

this.state = {
  fullName: "Bob Fisher",
    email: 'b@email.com'
}

Rather than updating the state as above, React provides the setState() method to do an asynchronous update as follows:

this.setState({ fullName: "Bob Fisher", email: 'b@email.com' });

Updating and merging nested state

Now, presume, you have a nested state object and only update one property of your nested state object as follows:

this.setState({ userInfo: { email: 'b@email.com' }});

You would expect setState to update the property and merge it with the original nested state object to get the new {userInfo:{ fullName: "Bob Fisher", email: 'b@email.com' }} state but that doesn't happen! Rather than that, the full state object will be replaced with { userInfo: { email: 'b@email.com' }}.

To solve this issue, you can use varous ways:

  • Use the immutability helpers
  • Build a new state from the original state and the spread operator then call setState again with the updated state.
var newState = React.addons.update(this.state, {
  userInfo: { email: { $set: "b@email.com"} }
});
this.setState(newState);

You can also create a new state object from the old state using the spread operator and set it as the new state as follows:

const { userInfo: oldUserInfo } = this.state;
const userInfo = { ...oldUserInfo, email: "b@email.com" };
this.setState({ userInfo });

Conclusion

As a recap, incorrectly updating your state might result in a change in its original structure. This happens often. Let's say you have a state variable that contains a nested object, and you want to modify one of the attributes of the nested object. You'd expect React to update the target property while maintaining the original state's shape, but that's not the case at all.

Your React components' nested state has to be updated appropriately, either by utilizing imutability helpers or JavaScript built-in mechanisms like the spread operator, to avoid introducing bugs into your code.