React state batching: sync vs async

2 mins |

December 07, 2020

We update a state, React updates the DOM. Reacting to the state changes is what React is about. DOM updates are expensive operations, they take a long time. So if React updates the DOM every time there is a state update, our UI would be sluggish. To overcome this, React batches these updates. Batching of states is usually referred to as async. It comes from the official docs ”State Updates May Be Asynchronous”. React does this as part of it’s performance optimization strategy.

Let’s dive in to see what happens when we update a state.

Open up the logs, and click the counter. You would see a log Rendering every time you click the counter.

That is not surprising,🙆‍♂️? Let’s add another setState in the handler. How many times do think we would get Rendering this time?

It still gives you only one Rendering even though we are setting the state twice. Yup, this is the performance optimization we were talking about. So, no matter how many times we set the state in a single tick ✅ React would render only once.

How does React know there is going to be a state update before it decides to render? Let’s add a debugger in our increment event handler.

Debugger paused after event handler

That’s interesting, we see batchedEventUpdates that is being called from ReactDOM. If we look into React’s source code, we see ReactDOM exports unstable_batchedUpdates are used to batch all the updates. And If we dig a little deeper, we see that it uses Reconciler to know which DOM node to be updated. Okay, you might be thinking that’s a bunch of code. A lot is going on in the source code. To be honest, I don’t understand most of it either. But when we poke around a little, we get to know that React uses a Scheduler to time keep the updates.

So somewhere in the React source code, its wrapping all the updates in a batchedUpdates. This is possible for React to do, because it knows the handlers attached and it can wait for the “render” function to complete. batchedUpdatescould give React the states it needs to update for the next render. This also means that everything outside the event loop (the tick), React wouldn’t be able to batch them.

To test that out, let’s add an async handler and add await on something, then update the state.

We have two updates before an await and two after. If you click on the counter now, you will see three logs per click. That is because as discussed before, React can batch state updates only if it knows all the states before hand that has to be updated.

If we put an await in the handler, we set the state update sometime in the future. Here, React can’t batch updates as it doesnt know what all state update it has to perform in this tick. So any updates after the await, React would immediately flush those updates and render again.

This is true for anything that is updating the state outside React’s “loop” (scheduler). It could be setTimeout’s, event handlers (set directly to DOM element) etc.

You can try all these cases in a single code sandbox.

Original demo was created by Yago

We have these kind of fun conversion on discord.Join us if you want to be part of it.

Now we know that React render every time a state update is called after an async operations. We can avoid it. If multiple states are related and they have to updated together, use useReducer instead of useState


React would batch your updates as long it happens in the same loop. State updates after any async operation would make it to update the state immediately (No batching).

Got a Question? Bala might have the answer. Get them answered on #AskBala

Edit on Github

Subscribe Now! Letters from Bala

Subscribe to my newsletter to receive letters about some interesting patterns and views in programming, frontend, Javascript, React, testing and many more. Be the first one to know when I publish a blog.

No spam, just some good stuff! Unsubscribe at any time

Written by Balavishnu V J. Follow him on Twitter to know what he is working on. Also, his opinions, thoughts and solutions in Web Dev.