How State Updates Are Merged in React

Perhaps one of the rare instances being shallow is welcomed...

Before we get into shallow merging though, let’s talk briefly about React Components and state. React Components help us break down the user interface (UI) into smaller parts, keeping our code organized and modular. We are able to send data down from parent components to child components via props, which holds data that can be used to describe what should be rendered to the DOM. Once passed down to a child component, props cannot be changed internally.

Enter State. State allows React components to modify their output in response to user input. This allows for more dynamic application UIs. According to React docs, however, state should never be directly modified. This is because a manual state mutation can be overwritten later on by asynchronous functions (leading to hard-to-trace bugs and major headaches). Instead, React provides us with the method setState( ).

setState( ) is a method that is available to all React components. Invoking it lets React know that the component state has been changed. We can use it by passing in a new version of the state via an object literal.

In the figure above, we initialize a state object with the property of “name” set to a string value of “Bob” and another property of “isLoggedIn” set to a boolean of “false” within the class’s constructor method (lines 7–13). A logging in action (e.g. clicking a login button) will trigger the callback function “handleLogIn”. Within this handler, a setState( ) method is invoked to update the initial state (line 16).

It is important to note here that React merges the object you provide within the setState( ) method into the current state object you are working with. In other words, only the “isLoggedIn” is updated to be “true”, while the remaining variable “name” (with value ‘Bob’) is left intact. This means that we do not have to pass in an object that lists out all of the properties of the state object. We just have to supply the setState( ) method with the key/value pair(s) for which we would like to update the value. All thanks to shallow merging.

At this point the newly updated state object will look something like this:

The value of “isLoggedIn” was changed to “true” while the key/value pair “name: ‘Bob’” was left untouched.

Shallow merging only merges things on the first level though (hence the term shallow), which means that we have to be careful when we use setState( ) on state objects with nested structures.

Example of a state object with a nested structure

Following the logic of shallow merging, you might try to update the value of “address.city” for the state object illustrated above with the method setState( ) like this…

Attempt to update the value for “address.city” while keeping the other information intact

However, this will change the state object’s structure to look something like:

As you can see, we lost “address.street” within the state object because the nested object (value of the key “address”) got overwritten by a new object with a single key of “city”. The logic of shallow merging does not apply for nested objects.

How do we update the value for “address.city” without totally overwriting the nested object (and thus saving the “address.street” information)? In other words, is there a way to deep merge? There are multiple ways to tackle this but the spread operator provides us a feasible solution.

The code above is a redo of the setState( ) implementation. We pass in a new version of state holding the key of “address”. The value of this new object being passed into setState( ) includes a spread operator, which returns all of the keys and values from within the initial “this.state.address” object. Since this initial “this.state.address” object already had a key of “city”, supplying a new value for the key of “city” will overwrite the original value. If a key of “city” hadn’t been in the original “this.state.address” object, it will have been added via this setState( ) method.

With this implementation of setState( ) the updated state object will look like (with the “address.street” information still intact):

Yay! Just what we needed!

To recap, React.Component’s setState( ) uses shallow merging to update state, ultimately saving us from listing out all of the keys and values from the initial state object. We can’t, however, apply the same logic of shallow merging on nested state objects. For these kinds of object structures, we can make use of the handy spread operator.