How to Update State Based on Previous State in React

Update React state with an updater function when the next value depends on the previous one. See why it avoids stale reads and double-click bugs.

5 min read

To update state based on the previous state, pass a function to the setter instead of a plain value. The function receives the latest pending value and returns the next one. React calls this an updater function, and it prevents stale reads when several updates happen in one event.

Why the plain value falls short

Calling the setter three times with the current value increments a counter only once. Each call reads the same value from the current render, so all three calls record the same next value. Here is the bug in a component.

App.jsxApp.jsx
import { useState } from "react";
 
function Counter() {
  const [count, setCount] = useState(0);
  function addThree() {
    setCount(count + 1);
    setCount(count + 1);
    setCount(count + 1);
  }
  return <button onClick={addThree}>Add 3: {count}</button>;
}

Clicking the button shows 1, not 3. The count variable in this render is still 0, so every call asks for 0 plus 1. React replaces the value with 1 once, which is why the button only moves by one.

This is the same snapshot behavior as reading state right after a setter. The handler keeps the value it captured when the render began.

Pass an updater function

The setter returned by useState also accepts a function. React calls this an updater, and it is the reliable choice whenever the next value is calculated from the previous one:

App.jsxApp.jsx
function addThree() {
  setCount((previous) => previous + 1);
  setCount((previous) => previous + 1);
  setCount((previous) => previous + 1);
}

Now clicking Add 3 shows 3. React queues the three updaters and runs them in order during the next render. Each receives the value produced by the one before it, so 0 becomes 1, then 2, then 3.

The argument is commonly named after the first letter of the variable, like previous for count, but the name is up to you.

How the queue works

React does not apply an update on the line where you write it. It queues each update while the handler runs, then processes the queue during the next render. An updater always receives the latest value React has computed so far, not the value the variable had when the handler started.

What you passWhat React queues
A plain valueReplace the state with that value
A functionCompute the next value from the latest state

That queue is the mechanism behind batching. Batching groups updates so one handler triggers one render instead of many. It also explains why the screen never shows a half-finished update.

When this matters

Use an updater whenever the next value depends on the previous one. This includes counters, toggles, and any list change that builds on the current list. For a single update in a fresh handler the difference is invisible, but the updater form is always safe.

A pending order counter shows the risk. If a handler increments pending, waits for a network response, then decrements it, a plain value reads the old number after the wait. An updater reads the latest queued number at each step, so rapid clicks stay consistent.

If the variable looks stale inside a handler, the cause is the same snapshot behavior. Why state does not update immediately walks through it.

Common mistake

The classic bug is reading the variable from the render instead of from the queue. If three setter calls each read a frozen value, only the last one effectively matters. Switching to an updater fixes it because the value is read from the queue instead.

A second mistake is putting side effects inside the updater. React may run it more than once, so the function should only compute and return the next value.

What to learn next

Next, apply the same idea to objects and arrays, where the updater form also keeps you from mutating state. When several values change together, a reducer models those updates as one event.

Rune AI

Rune AI

Key Insights

  • Pass a function to the setter when the next value depends on the previous value.
  • A plain value reads a frozen snapshot from the current render.
  • React queues updaters and runs them in order during the next render.
  • Each updater receives the value produced by the one before it.
  • The updater form is always safe, even for a single update.
RunePowered by Rune AI

Frequently Asked Questions

When should I pass a function to a setter?

Whenever the next value depends on the previous value. A counter, a toggle, or a list change that builds on the current list all need the updater form.

Why does setCount(count + 1) called three times only add one?

Each call reads the same count from the current render, so all three record the same next value. React applies the replacement once.

Does the updater function receive the latest value?

Yes. React queues the updaters and runs them in order during the next render, passing each function the value produced by the previous one.

Conclusion

When the next value of state depends on its current value, pass a function to the setter. The updater reads the latest queued value, so rapid updates stay correct.