Alex Sidorenko AvatarAlex Sidorenko

28 Jun 2021

Why is it so difficult to modify deeply nested state in React?

It's not uncommon to see code like this in the React world. All this effort just to update a single to-do item inside a nested state:

const updateTodo = ({ taskId, todoId, value }) => {
setProject({
tasks: {
...state.tasks,
[taskId]: {
...state.tasks[taskId],
todos: {
...state.tasks[taskId].todos,
[todoId]: {
value: value,
},
},
},
},
});
};

But how come that such a popular, widely spread tool as React makes such a hustle from something that a beginner may bump into during the first project? Is that really so that solo React can not tackle deeply nested state updates with less boilerplate? Isn't it a bit disappointing?

The Immutability Trade-off

React embraces the concept of immutability. The code example above updates the state immutably, meaning it doesn't modify the state directly. Instead, it creates a new copy of the state. In comparison, here's how a direct mutation of the state would look:

project.tasks[taskId].todos[todoId].value = true;

But why is immutability so important if it makes the trivial task of updating state so complicated? Here are three main reasons from the React documentation:

I

Immutability makes complex features much easier to implement. Avoiding direct data mutation lets us keep previous versions of the state history intact, and reuse them later.

II

Detecting changes in mutable objects is difficult because they are modified directly. This detection requires the mutable object to be compared to previous copies of itself and the entire object tree to be traversed.

III

The main benefit of immutability is that it helps you build pure components in React. Immutable data can easily determine if changes have been made, which helps to determine when a component requires re-rendering.

More information on immutability:

Okay, I get it - immutability is important, but how do I update my nested state?

There are two main ways to simplify nested state updates in React: flattening the state and using immutable libraries.

Flattening the state

The best solution is to avoid the problem altogether. If you have nested state, try to flatten it. Check out three main reasons to keep your state flat by Redux maintainer Mark Erikson. You can try to flatten your state manually or use a third-party library like Normalizr.

Using immutable libraries

There are libraries designed to help with immutable updates. For example, here's how Immer can help reduce our boilerplate:

const updateTodo = ({ taskId, todoId, value }) => {
setState(
produce(baseState, (draftState) => {
draftState.tasks[taskId].todos[todoId].value = value;
return draftState;
})
);
};

Check out the top 13 immutable libraries for more options.

Summary

The challenge of updating nested state stems from React's fundamental architectural decision to embrace immutability. Immutability comes with many great benefits, such as predictability and performance.

There are two main ways to deal with the problem of updating deeply nested state. The first is flattening your state to avoid the problem altogether. The second is using immutable libraries that help with state manipulations.


Follow me on 饾晱 for short videos about Next.js

饾晱 Follow for more