DEV Community

Safal Bhandari
Safal Bhandari

Posted on

Why Clicking “+3” Only Increments Once in React — and How to Fix It

When working with React state, one of the most confusing discoveries is this:
If you call setNumber(number + 1) three times in a row, your counter only increases by 1, not 3.

Let’s see why.


The Example

import { useState } from "react";

export default function Counter() {
  const [number, setNumber] = useState(0);

  return (
    <>
      <h1>{number}</h1>
      <button
        onClick={() => {
          setNumber(number + 1);
          setNumber(number + 1);
          setNumber(number + 1);
        }}
      >
        +3
      </button>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

You might expect the number to jump from 0 → 3 after one click.
But it doesn’t. It only moves to 1.


Why It Happens

Each render in React has its own snapshot of state values.
During that first render, number equals 0.
So inside your click handler, number is still 0 every time you call setNumber(number + 1).

Here’s what React sees:

  1. setNumber(0 + 1) → queue update: set to 1
  2. setNumber(0 + 1) → queue update: set to 1
  3. setNumber(0 + 1) → queue update: set to 1

React batches all three updates because they happen in the same event.
At the end, the queue just says: “Set number to 1.”
Then React renders again with number = 1.


Batching in Action

React groups all state updates that occur inside one event handler into a single render cycle.
This means even though you called setNumber three times, React will only re-render once — applying the final computed state from that batch.

This optimization makes React faster but can surprise beginners who expect immediate updates.


The Correct Way: Functional Updates

When your next state depends on the previous state, always use the functional updater form:

<button
  onClick={() => {
    setNumber((n) => n + 1);
    setNumber((n) => n + 1);
    setNumber((n) => n + 1);
  }}
>
  +3
</button>
Enter fullscreen mode Exit fullscreen mode

Now React queues three functions, not static values.

When React processes the queue:

  1. First updater runs with n = 0 → returns 1
  2. Second runs with n = 1 → returns 2
  3. Third runs with n = 2 → returns 3

React batches the updates but still computes each in order, so you end up with 3.
It still re-renders only once, after all three updates finish.


Visualizing What’s Going On

You can imagine React doing this internally:

initialState = 0
updates = [
  (n) => n + 1,
  (n) => n + 1,
  (n) => n + 1
]

finalState = updates.reduce((n, fn) => fn(n), initialState)
// finalState = 3
Enter fullscreen mode Exit fullscreen mode

After computing finalState, React re-renders your component with the new state value.


The Rule of Thumb

  • If you need to replace state with a new value, setState(newValue) is fine.
  • If you need to update based on the old value, use setState(prev => ...).
  • React batches updates in one event → expect one render, not one per call.

TL;DR

  • setNumber(number + 1) uses the old number from that render.
  • React batches updates, so only the last one counts.
  • Use the functional form setNumber(prev => prev + 1) to safely chain updates.
  • Three setNumber(prev => prev + 1) calls result in one render and a final value of 3.

Top comments (0)