Why we should never update React State directly!
Whoever worked or learned a bit of React probably knows that we should never change or update the state directly. Rather we should always declare a new object const obj = {}
and use this.setState({ obj })
and let react handle it, just like I did in the example below. But why?
state = {
counters: [
{ id: 1, value: 4 },
{ id: 2, value: 0 },
{ id: 3, value: 0 },
{ id: 4, value: 0 },
{ id: 5, value: 0 }
]
};handleIncrements = counter => {
const counters = [...this.state.counters];
const index = counters.indexOf(counter);
counters[index] = { ...counter };
counters[index].value++;
this.setState({ counters });
};};
To have a grip on this matter we first need to know how React lifecycle works.
(Those who want the short answer can scroll down to the bottom)
React has three lifecycle phases:
- Mounting Phase:
When the component is mounted for the first time. It has three lifecycle hooks that we commonly use:
i. constructor
ii. render
ii. componentDidMount - Updating Phase:
When the component is already mounted and a change in state or props has happened. It has two lifecycle hooks that we commonly use:
i. render
ii. componentDidUpdate - Unmounting Phase:
When a component is deleted. It has one lifecycle hook that we commonly use:
i. componentWillUnmount
All these lifecycle hooks are called in order.
In React, whenever a component is rendering either in theMounting phase or in Updating Phase, it always renders all the components that are in its tree. Consider that we have an App structure like this:
When we run this App and if we declare all the lifecycle hooks mentioned above and console.log()
in every hook, we will see that the hooks are called in the following order.
i. App-constructor
ii. App-rendered
iii. NavBar-rendered (NavBar is a Stateless functional component)
iv. Counters-rendered (Counters is a Stateless functional component)
v. (4) Counter-rendered (Counter is a Stateless functional component)
vi. App-didMount
Now, if you don’t know what a Stateless functional component is, its a component that has no state or constructor or any lifecycle hooks and are fully controlled by their parents. They only render a react component.
So now, what happens when a state or props is changed? It renders all its components again.
Notice, I have said all the components are rendered again not updateed again.
As you can see in the screenshot below, only the incremented span is being updated(in the inspect element you can see the changes in violet color). So, how it works?
React, keeps a track record of all its virtual DOM. Whenever a change happens, all the components are rendered and this new virtual DOM is then compared with the old virtual DOM. Only the differences found are then reflected in the original DOM.
So, it’s obvious from the statement that if we mutate the state directly, it will change the reference of the state in the previous virtual DOM as well. So, React won’t be able to see that there is a change of the state and so it won’t be reflected in the original DOM until we reload. The problem is more obvious when we extend a component with React.PureComponent
instead of React.component
, where React tries to optimize some time by not rendering components if no changes are found.
Also, mutating the state directly can lead to odd bugs and components that are hard to optimize.
I hope you enjoyed this blog. If you want to read more of my blogs than you can follow me on here in medium.
I am Anurag. Working professionally as a Software Engineer since 2014. I often write blogs on the questions I often see people asking and find important. In my free time, I like to develop web and mobile apps that simple and useful. You can connect with me here:
🔗 https://www.linkedin.com/in/anuragbhattacharjee/
🕸 http://anuragbhattacharjee.com
Happy Coding 😀