How to Fix a React useState Stale-State Bug

Calling setCount(count + 1) three times in one React handler adds 1, not 3 - each call closes over the same stale count from the current render. Switch to the functional updater form and each call gets the latest pending state instead.

Fullstack Engineerfullstackreactvite

What the stale-state bug looks like

You have a handler that fires setState multiple times:

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

Click the button when count is 0. You expect 3, but the counter shows 1.

All three calls read count from the enclosing render scope - it's 0 for all of them. React batches the three identical 0 + 1 = 1 updates into a single state transition, so only one increment lands.

The fix: functional updater form

Pass a function to setState instead of a value. React calls that function with the latest pending state as the argument, so each call sees the result of the previous one:

function addThree() {
  setCount(c => c + 1);
  setCount(c => c + 1);
  setCount(c => c + 1);
}

Now the sequence is: - First call: c = 0 -> 1 - Second call: c = 1 -> 2 - Third call: c = 2 -> 3

React applies the updates in order, and the counter correctly reaches 3.

Why the value form usually seems fine

A single setCount(count + 1) per click works correctly because React re-renders between clicks - so count is fresh the next time the handler runs. The stale-closure bug only bites when you queue multiple updates in one handler, in a loop, or inside an async callback where the render-time snapshot is already out of date.

When to always use the functional form

Use setState(prev => ...) whenever:

A simple rule: if the new value depends on the previous value, use the functional updater. If it's completely independent (e.g. setName("Alice")), either form is fine.

Catching it early with ESLint

The react-hooks/exhaustive-deps rule flags stale dependencies in useEffect hooks. Enable it in your project's ESLint config:

{
  "rules": {
    "react-hooks/exhaustive-deps": "warn"
  }
}

For multi-setState handlers, code review and test coverage (a "+3 button should add 3" assertion) catch it before it ships.

Want to try it hands-on? HeyDevJob gives you this exact setup in a live cloud workspace in your browser - edit it, run it, and see it work. Free, nothing to install.

Try it in a workspace →

What you'll practice

FAQ

Why does calling setCount multiple times in React only update once?

Each call captures the same count value from the current render. React batches the identical updates into one state transition. Use the functional updater form setCount(c => c + 1) so each call receives the latest pending state.

What is the functional updater form in React useState?

Instead of setState(value), you write setState(prev => newValue). React passes the latest pending state as prev, so multiple calls in one handler chain correctly rather than all reading the same stale value.

When should I use the functional form of setState?

Whenever the new state depends on the previous state - counters, toggles, appending to a list, or any time you call setState more than once in one handler or inside an async callback.

Keep learning

Fix a Blank React Dashboard (Failed Fetch)Fullstack projectFix the React Search That Won't Filter (useEffect Deps)Fullstack projectFix the CORS Error Blocking Your API CallsFullstack projectFullstack roadmapStep by step to hiredFullstack interview questionsSTAR answersAll Fullstack projectsProjects hub

Learn it by doing. Open this in a live cloud workspace, make the change yourself, and keep a record of the work you can share.

Open the workspace →