React's setState Batching: Why setCount(count + 1) Doesn’t Do What You Expect

React's setState Batching: Why setCount(count + 1) Doesn’t Do What You Expect
Photo by Max Shilov / Unsplash

If you’ve ever written React code that tries to update state multiple times in a row, like this:

setCount(count + 1);
setCount(count + 1);

you might have noticed something strange: the count only increases by 1, not 2. Why is that? Let’s dive into the mechanics of React’s state batching, what’s going on under the hood, and how to get the behavior you expect.


Understanding the Problem

Here’s a simple React component that demonstrates the issue:

function Counter() {
  const [count, setCount] = useState(0);

  function handleClick() {
    setCount(count + 1);
    setCount(count + 1);
  }

  return (
    <>
      <p>Count: {count}</p>
      <button onClick={handleClick}>Increase</button>
    </>
  );
}

At first glance, you might think clicking the button would increase the count by 2. However, it only increases by 1. Why?


The Role of React's Batching Mechanism

React batches state updates that occur during a synchronous event (like a button click). This means that multiple setCount calls are grouped together and applied as a single update. Importantly, both calls use the same stale count value that existed when the function started executing.

Here’s a step-by-step breakdown:

  1. count starts at 0.
  2. setCount(count + 1) is called — React schedules a state update to 1.
  3. setCount(count + 1) is called again — but it’s still referencing the same initial count of 0, so React schedules the state to be 1 (again).

When React flushes these updates, it only applies the last one, resulting in count increasing by 1.


Why This Happens

React’s batching behavior exists for performance reasons. By combining multiple state updates into a single re-render, React avoids unnecessary work and keeps your app running smoothly. However, this behavior can be surprising if you assume each setState call happens immediately.


How to Make It Increment by 2

To fix this, you need to use the functional updater form of setCount, which ensures each update gets the latest state value. Here’s the corrected version:

function handleClick() {
  setCount(prevCount => prevCount + 1);
  setCount(prevCount => prevCount + 1);
}

Now, React will:

  1. Take the current count, increment it by 1.
  2. Use the new value from the first update and increment it by 1 again.

This gives you the expected count + 2.


Considerations and Gotchas

React’s batching works not only in function components with useState, but also in class components with this.setState.
Batching behavior is more aggressive in React 18+ with automatic batching across more contexts (including promises and async functions).
✅ If you call setCount with a new object reference (e.g., setCount({})) each time, you’ll trigger a re-render, but still, state updates can be batched.


Other Things You Should Know

  • React updates are not synchronous. Even though you call setCount, the update is scheduled and applied after the function finishes.
  • Async updates (like setTimeout or network requests) break batching, so multiple setCount calls in those contexts are applied separately.
  • If you want immediate updates or need to do something right after the state changes, you can use a useEffect that listens for changes to count.

Finally

React’s batching of state updates is a powerful feature designed to optimize performance and reduce unnecessary re-renders. However, it’s crucial to understand how and when React batches these updates to avoid unexpected behavior. Whenever you need multiple state updates to rely on the latest value, always use the functional updater form.

So the next time you write:

setCount(count + 1);
setCount(count + 1);

Remember: React batches them, and they’re using the same stale count.

To make it work, write:

setCount(prev => prev + 1);
setCount(prev => prev + 1);

And enjoy seeing the correct output!

💡
Demo here.

Support Us