Peter Ekene Eze Learn, Apply, Share

Simplifying state management in React apps with batched updates

5 min read 1410

After making an update to your component’s state using either useState or this.setState, parts of the component re-renders depending on the update. More so, if you have various calls to update the state within a React event handler like onClick, React makes the updates in a batch, instead of one at a time, reducing the number of renders the component will make.

However, you might not always be making a call to update the state within an event handler and in these cases (for example within a Promise or a SetTimeout), React makes the updates synchronously instead of in a batch. This means that you’ll get multiple re-renders. Consider the example below (or check out the demo on CodeSandbox):

import React, { Fragment, useState } from "react";
import ReactDOM from "react-dom";
function Component() {
  const [item1, setItem1] = useState("Initial Item 1");
  const [item2, setItem2] = useState("Initial Item 2");
  console.log("render: ", item1, item2);
  function handleClickWithPromise() {
    Promise.resolve().then(() => {
      setItem1("Updated Item 1");
      setItem2("Updated Item 2");
  function handleClickWithoutPromise() {
    setItem1("Updated Item 1");
    setItem2("Updated Item 2");
  return (
      <button onClick={handleClickWithPromise}>
        {item1} - {item2} (with promise)
      <button onClick={handleClickWithoutPromise}>
        {item1} - {item2} (without promise)
function App() {
  return <Component />;
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

In this example, we have two state values item1 and item2, and we update their values when any of the two buttons are clicked. However, in the first button, we make the updates within a Promise.

By default, React batches updates made in a known method like the lifecycle methods or event handlers, but doesn’t do the same when the updates are within callbacks like in SetTimeout or Promises. This means that if you have multiple calls to update the state, React re-renders the component each time the call is made.

When the Component first renders, the console outputs the initial values of item1 and item2.

Initial render

Then, if you click on the first button the component re-renders twice and you see the initial render and then the two subsequent re-renders:

Initial render and additional two re-renders

But if you refresh the page and click on the second button, you see the initial render and only one re-render even though there are still two updates made to the state:

Initial render and only one re-render

We made a custom demo for .
No really. Click here to check it out.

Why was that possible? Well, because React automatically batches updates in known methods, in this case, an event handler. Please feel free to play around with the demo to test out the functionalities yourself:

When you have multiple state calls wrapped in a Promise like in the example above, you can force React to make batched updates, hence causing only one re-render. This can be done by wrapping the calls to update the state in *ReactDOM.unstable_batchedUpdates()* like this:

function handleClickWithPromise() {
    Promise.resolve().then(() => {
      ReactDOM.unstable_batchedUpdates(() => {
        setItem1("Updated Item 1");
        setItem2("Updated Item 2");

Next, update the handleClickWithPromise() function on the previous demo with the snippet above like we currently have on this Sandbox. Now, if you click on the first button, the updates will be batched, causing only one render. If you look at your console you should see this after you click on the first button:

This is unlike the last time where we had two re-renders on clicking the first button (with promise). As a result of wrapping the calls to update state in *ReactDOM.unstable_batchedUpdates()*, we get the same exact functionality we had with the second button (without promise).

As I mentioned earlier, if you use the *unstable_batchedUpdates()* keep in mind that it’s an implementation detail. Future versions of React will probably make this the default behavior and you wouldn’t have to use the unstable API.

Should you use it?

The name of the method does make it a bit concerning whether it’s safe to use in production. However, the React team has previously encouraged (and at the time of writing, still do) the use of this API when appropriate. So, it’s safe to say that although “unstable” it is stable enough to be used in production today.

When to use it

If you need to make multiple calls to update the state like in the examples above, you might find that there’s a better way to do it. In my experience, most cases where I’ve seen developers make multiple calls to update the state, those calls could have easily been replaced with a single call. Let’s take a look at some instances where you might make multiple calls to update the state.

this.setState({ ...this.state, foo: 42 });
if (condition) {
    this.setState({ ...this.state, isBar: true });

The above code could be refactored to update the state with a single call like so:

let newState = { this.state, foo: 42 };
if (condition) {
    newState = { ...this.state, isBar: true };

Of course, you are creating a whole new variable, and that is okay. Usually, as we saw earlier, React would automatically batch the updates made in certain functions and not in others. As a result, you should be deliberate about when you try to reduce the number of calls to setState.

Another instance where you would have multiple calls to update the state is:

// Increment foo
this.setState({ ...this.state, foo: + 1 });
this.setState({ ...this.state, foo: + 1 });

In this case, the subsequent calls make use of the updated values made by previous calls. Again, the above code can be refactored like so:

function incrementFooBy(delta) {
    return (previousState, currentProps) => {
        return { ...previousState, foo: + delta };

Here, we use currying to “compose” what the update should be, based on the previous state and the intended changes and then pass the value to this.setState.

Does it apply to Hooks?

This is probably a question you want to ask so let me stop here and say YES it does. I see this case a lot with the introduction of Hooks. Consider this example below:

const [value, updateValue] = useState({});
const [anotherValue, updateAnotherValue] = useState({});

updateValue({ content: "Hello" });
updateAnotherValue({ content: "World" });

Sometimes when you use Hooks you might find yourself creating multiple state objects in a single functional component. If you are doing this, it could be a sign that your functional component is violating the Single Responsibility Principle, doing more than one thing. If the multiple state objects make sense to belong together, then you should combine the values into one state object like so:

const [allValues, updateAllValues] = useState({});

updateAllValues({firstContent: "Hello", secondContent: "World" });

Or separate the two state objects into their own independent functional component if they don’t make sense to be together. If you don’t fall into any of the above mentioned cases, then I think you should use the *unstable_batchedUpdates*.

I feel the need to mention that making multiple calls to update the state isn’t so bad especially because React automatically batches the updates in some cases and in other cases it doesn’t really create any performance issues. So if you find yourself needing to use *unstable_batchedUpdates* then you must be in a very rare situation.

Will it be deprecated?

According to Dan Abramov’s response to a Stackoverflow question:

“However, we won’t remove [unstable_batchedUpdates] in a minor version, so you can safely rely on it until React 17 if you need to force batching in some cases outside of React event handlers.”

And another comment he made on a Github issue goes:

“This is expected behavior because we currently only batch updates inside scopes known to React (e.g. during a synchronous lifecycle method, or during an event handler). You can work around this with unstable_batchedUpdates as mentioned above. In the future batching will be on by default everywhere.”

As of the time of writing this article, there’s no mention in the official roadmap blog posts of any React version where the unstable_batchedUpdates will be deprecated and there isn’t much information besides Dan’s comments that more accurately say when the API will be deprecated.

Final thoughts

In this post, we have taken a closer look at the batched updates feature and demonstrated how it simplifies the statement management and rendering process in React applications. Having mentioned that this featured is not stable at the moment, it is worthy to note that it can be used at the moment. To get started, simply put your state calls in a callback function passed to *ReactDOM.unstable_batchedUpdates*.

You come here a lot! We hope you enjoy the LogRocket blog. Could you fill out a survey about what you want us to write about?

    Which of these topics are you most interested in?
    ReactVueAngularNew frameworks
    Do you spend a lot of time reproducing errors in your apps?
    Which, if any, do you think would help you reproduce errors more effectively?
    A solution to see exactly what a user did to trigger an errorProactive monitoring which automatically surfaces issuesHaving a support team triage issues more efficiently
    Thanks! Interested to hear how LogRocket can improve your bug fixing processes? Leave your email:

    Full visibility into production React apps

    Debugging React applications can be difficult, especially when users experience issues that are difficult to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

    LogRocket is like a DVR for web apps, recording literally everything that happens on your React app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

    The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

    Modernize how you debug your React apps — .

    Peter Ekene Eze Learn, Apply, Share

    Leave a Reply