How it works under the hood: React's batching mechanism
A deep dive into the inner workings of React's batch process
State updating is a basic thing we have to do when building an app in React. Each state update calls a render process which lets us show changes on the screen. However, there are situations where you may want to do a lot of state updates before a re-render is called.
Do you have any idea what kind of problem you could have in this situation? Let's take a look.
State updates - standard way
Imagine you're running an online store and you want to refresh users' basket counters each time they add an item to their basket.
'use client'
import {useState} from "react";
export default function Store() {
const [shopCartItemsCount, setShopCartItemsCount] = useState(0)
const addItemToShopCart = (item: object) => {
// action to save item in shop cart
};
return (
<>
<p>Items in shop cart: {shopCartItemsCount}</p>
<button onClick={() => {
addItemToShopCart({name: "Coffee"});
setShopCartItemsCount(shopCartItemsCount + 1);
addItemToShopCart({name: "Tea"});
setShopCartItemsCount(shopCartItemsCount + 1);
}}>
Add items to cart
</button>
</>
);
}
What do you think is going to be the result of this code that the user is going to see?
The result will be: Items in the shop cart: 1.
We need to mention two things that happened here to fully understand why the value of shopCartItemsCount is 1. The first is that the state values of each render are unchanging, meaning that the value of shopCartItemsCount within the event handler of the first render will always be 0, no matter how many times you run the function that tries to update the state.
This is what our code looked like when it was being processed:
addItemToShopCart({name: "Coffee"});
setShopCartItemsCount(0 + 1);
addItemToShopCart({name: "Tea"});
setShopCartItemsCount(0 + 1);
Another important point is that React doesn't process state updates immediately after an event handler is triggered. It waits for all code within the handler to finish before updating the state. This is why the re-render happens after all setShopCartItemsCount() calls.
React's batching feature optimizes performance by grouping multiple state updates into a single render, preventing unnecessary UI refreshes.
This means that even when updating multiple state variables from different components within an event handler, the UI won't be updated until the entire handler and its operations have finished.
However, it's important to note that React only batches updates within a single event. For example, if you have two separate click events, React will treat them as separate updates, not batching them together.
Updater function for multiple updates of the same state variable
You can use a function instead of a direct value if you need to update the same state variable multiple times before React re-renders. This is a function that takes the current value of the state and returns the new value.
These functions are called updater functions. This tells React that it should perform a calculation on the state, rather than simply replacing it with another.
Let’s update our example:
'use client'
import {useState} from "react";
export default function Store() {
const [shopCartItemsCount, setShopCartItemsCount] = useState(0)
const addItemToShopCart = (item: object) => {
// action to save item in shop cart
};
return (
<>
<p>Items in shop cart: {shopCartItemsCount}</p>
<button onClick={() => {
addItemToShopCart({name: "Coffee"});
setShopCartItemsCount(count => count + 1);
addItemToShopCart({name: "Tea"});
setShopCartItemsCount(count => count + 1);
}}>
Add items to cart
</button>
</>
);
}
After our changes, React will calculate the next state based on the previous state. In the beginning, the state’s value was 0. React will use this value as the starting point for the first updater function. The result of this function is then passed to the next updater, and so on. This creates a chain of updates.
Now our code looked like this when it was being processed:
addItemToShopCart({name: "Coffee"});
setShopCartItemsCount(0 => 0 + 1);
addItemToShopCart({name: "Tea"});
setShopCartItemsCount(1 => 1 + 1);
When React updates a component, it calls updater functions to calculate new values for the component's state. To keep things predictable and avoid problems, these updater functions should be pure. This means they should only take in input values and produce a new output value without changing anything else.
Don’t try to directly modify the state inside them, and avoid doing anything that could affect how the component behaves outside of the update process.
Summary
When you use setState to update state, it doesn't directly modify the current render's variables but instead schedules a re-render. React waits for all event handlers to finish before processing these state updates, a process known as batching.
If you need to update the state multiple times within a single event, you can use the set state with an updater function to use the current state value.