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.
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:
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 pass | What React queues |
|---|---|
| A plain value | Replace the state with that value |
| A function | Compute 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
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.
Frequently Asked Questions
When should I pass a function to a setter?
Why does setCount(count + 1) called three times only add one?
Does the updater function receive the latest value?
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.
More in this topic
How to Build a Dropdown Menu in React
Build a React dropdown menu with the ARIA menu button pattern. Handle open and close, keyboard arrows, and clicks outside the menu.
How to Animate React Components with Motion
Animate React components with the Motion library. Set up motion, add enter, hover, and exit animations, and respect reduced motion.
Headless UI Components Explained: Logic Without Locked Styling
Understand headless UI components and how libraries like Radix give you unstyled, accessible behavior that you style yourself.