State Updates in React
What is a state
A state is a data container that contains the data for a component throughout its lifecycle. A state can contain any data and can be used anywhere in the component.
React do a great job with the state and every time the state is changed in react it performs a rerender of the component to update the value of the state throughout the component. But these updates are asynchronous and a state update will be done after the function which is updating the state is executed.
For e.g
const [counter, setCounter] = useState(0);
So here in this example, the initial value of the counter is 0. Please note the counter here is a state variable.
So if i try to update the state here like this
counter = 1
it will be updated but not communicated to the component that the state is updated and you need to recalculate all your logic which you are managing with this state. So how to update the state correctly. Well, the answer is simple. By using the setCounter
function.
So if I do it like this
setCounter(1)
Then react will re-render the component and all the logic which is depended upon the counter variable will be recalculated and the value for the state is updated at all respective places.
So React state updates are async what does that mean
So let me give you a quick example of this, try doing this in your code
function foo() {
setCounter(5);
console.log(counter);
}
as the previous value of count was 0 and if you execute this function the value of counter printed in the function will be 0 and not 5. So why did this happen? The reason is simple because the updates are async and moreover react will execute the function setCounter
after the foo is executed. So why react do it like this why not updating the state first and then do the other tasks.
It's like this because of performance suppose you have done this setState multiple times in the function and then react has to re-render the component each time the state is updated within the function which costs performance. So that's why react executes all the setState functions after the parent function is executed which means all the state updates are async and batched in nature.
So this means that the react state updates will be triggered after the function has finished its execution and then react will execute all the setState function in the parent function and one by one and after all functions done execution react will cause a re-render to update the state value related with the respective setState function
Batched Updates, huh …
So it is clear that if you do multiple setState in a function they will be batched and will be executed one by one. So this creates some bug/problem in the code like say we want to do something like this
// Let us suppose the initial value of counter is 0
function foo() {
setCounter(counter + 1)
setCounter(counter + 2)
setCounter(counter + 3)
}
So if you look at this code we are updating the counter thrice and the expected result should be 6. But if you check the output of this code the result will be 3.
Can you guess the reason …
Yes, you get it right it's because of the async batched updates of react.
So what react has done here is it took all the setCounter function and executed them one by one but while executing react didn't trigger a re-render to update the value of the counter in the component so the counter's value remains 0 for all the 3 setCounter functions and the end result was 3. We discussed earlier why react don't re-render the component on each state update when there are multiple state updates happening inside a function it's because of performance.
So this introduced a bug in the code how to solve it.
So there can be multiple ways to solve this but there is one way which react provides to solve it in the best react way possible.
So React is smart and it knows its pitfall of batching the state updates. So react comes up with a callback function in setState so that the setState has access to the previous state of the component and bugs like these don't occur in the codebase and keeping the code the same and maintain the performance.
So here is the solution for this
function foo() {
setCounter((prevState) => prevState + 1);
setCounter((prevState) => prevState + 2);
setCounter((prevState) => prevState + 3);
}
// Now the value of the counter will be 6 as expected
So react takes a callback function in setState and pass the previous state of the component as a param to provide access to the latest value of the previous state so that the updates which depend upon the previous state values can be performed efficiently.
But are all state updates batched?
The answer is no.
Only state updates inside a Synthetic event
and React lifecycle
methods are batched. The reason is simple because for normal js functions react has no idea when they are triggered and when the execution is complete so it doesn't know how many states updates are triggered from it. A cool example of this will be
setState
inside setTimeOut
and Promises. All
. State updates inside promises and js inbuilt functions will be non-batched and cause a re-render every time the state is updated inside them.
What does this mean??
fetch('someApiUrl')
.then(res => res.json())
.then(datat => {
setName(data.name);
setLoading(false)
})
.catch(err => {
setError(err);
setLoading(false);
})
So if you run the above code then React will cause 2 re-renders of component one for updating the name and one for updating loading status and these updates won't be batched. But they still will be async.
So here performance is compromised, but there is always a way around it. So if you really want to do batched updates here and don't want to lose on performance use useReducer this hook will combine the related states and perform one single update based on the reducer function you wrote
Top comments (0)