What is the purpose of callback function argument format of `setState()` in React and when should it be used?
TL;DR
The callback (or updater function) form of setState — both this.setState(prev => ...) in classes and setX(prev => ...) with useState — guarantees that each update is computed from the latest queued state rather than the value captured in your closure. Use it whenever the next state depends on the previous state, especially when you call the setter more than once in the same event handler or when the update may run after an await/timeout/promise.
import { useState } from 'react';function Counter() {const [count, setCount] = useState(0);function handleClick() {setCount((value) => value + 1);setCount((value) => value + 1);}return <button onClick={handleClick}>{count}</button>;}
Purpose of the updater function form of setState
The updater form calculates next state from React's latest queued state rather than from the render snapshot captured by a closure.
What it is
React's state setters — this.setState in class components and the setX returned by useState in function components — accept either a new value or an updater function. The updater receives the latest queued state (and, for class components, the latest props) and returns the next state.
The community calls this the updater function form (sometimes "functional updater"). Recognizing that name is helpful in interviews.
Why it exists: state is a snapshot and updates are queued
State setters do not change the state snapshot in the currently running code. React queues the update for a subsequent render. Since React 18, automatic batching applies to React events and many other callbacks, including promise callbacks, timers, and native event handlers.
That means by the time the queued update actually runs, the variable you captured from the previous render may be stale.
The motivating bug — reading state directly between successive calls
The classic mistake is calling the setter more than once based on the current state value:
import { useState } from 'react';function Counter() {const [count, setCount] = useState(0);const handleClick = () => {// BUG: each call uses the same closed-over `count` (still 0 on this render).setCount(count + 1);setCount(count + 1);setCount(count + 1);// After re-render, count is 1 — not 3.};return <button onClick={handleClick}>{count}</button>;}
count is captured by the closure when the component rendered. All three calls compute 0 + 1, so React queues 1, 1, 1 and the final state is 1.
The same bug exists in classes — reading this.state.counter between setState calls returns the value from the last render, not the in-flight queued value.
The fix — pass an updater function
Passing a pure updater lets React apply each queued transformation in order:
import { useState } from 'react';function Counter() {const [count, setCount] = useState(0);const handleClick = () => {setCount((c) => c + 1);setCount((c) => c + 1);setCount((c) => c + 1);// Final count is 3 — each updater receives the result of the previous one.};return <button onClick={handleClick}>{count}</button>;}
Each updater receives a snapshot of the latest queued state, not the value from your render closure.
When to use it
Use an updater whenever the next value is calculated from the previous value in the same state variable:
- The next state depends on the previous state (counters, toggles, append-to-array, increment-a-map-entry).
- You call the setter more than once in the same handler.
- The update happens after an
await,setTimeout, promise resolution, or subscription callback — an intervening update may have made the closed-over value stale. - Inside
useEffectoruseCallbackwhen the state is read only to calculate its own next value — using the updater can remove that state value from the dependency list.
When you do not need it
If the new value does not depend on the previous state — setName('Alice'), setUser(response.data) — passing the value directly is fine and slightly more readable.
Class component equivalent
The same idea applies in class components, where the updater also receives props:
import { Component } from 'react';class Counter extends Component {state = { counter: 0 };incrementCounter = () => {this.setState((prevState, props) => ({counter: prevState.counter + props.increment,}));};render() {return (<div><p>Counter: {this.state.counter}</p><button onClick={this.incrementCounter}>Increment</button></div>);}}
Further reading
- React Docs: Queueing a series of state updates
- React Docs:
useState - React Docs: State as a snapshot
- React 18: Automatic batching