Why use the callback format of setState?

React

The short answer

The callback format (also called the functional updater) gives you the most recent state value when updating state. Instead of passing a new value directly, you pass a function that receives the previous state and returns the new state. This is important when the new state depends on the old state, especially inside closures or batched updates.

The problem with direct updates

function Counter() {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
};
return <button onClick={handleClick}>{count}</button>;
}

You might expect clicking the button to increase count by 3. But it only increases by 1. Here is why:

When handleClick runs, count is 0. All three setCount calls use this same count value. They all say "set count to 0 + 1." React batches these updates and the result is 1, not 3.

The callback format fixes this

const handleClick = () => {
setCount((prev) => prev + 1);
setCount((prev) => prev + 1);
setCount((prev) => prev + 1);
};

Now each call receives the latest state value:

  • First call: prev is 0, returns 1
  • Second call: prev is 1, returns 2
  • Third call: prev is 2, returns 3

The result is 3. Each updater function gets the most recent value, including the result of the previous updater.

Inside setTimeout and async code

The callback format is also important inside setTimeout, setInterval, and async functions where the closure captures a stale value:

function Counter() {
const [count, setCount] = useState(0);
const delayedIncrement = () => {
setTimeout(() => {
// count is the value from when delayedIncrement was called
setCount(count + 1); // stale!
}, 3000);
};
return (
<div>
<p>{count}</p>
<button onClick={delayedIncrement}>
Increment after 3s
</button>
</div>
);
}

If you click the button three times quickly, the count only goes to 1 because all three timeouts captured count = 0.

Fix with the callback format:

const delayedIncrement = () => {
setTimeout(() => {
setCount((prev) => prev + 1); // always gets the latest value
}, 3000);
};

Now clicking three times results in count going to 3.

When to use which format

Direct update — when the new value does not depend on the old value:

setName('John');
setIsOpen(false);
setItems([...newItems]);

Callback format — when the new value depends on the old value:

setCount((prev) => prev + 1);
setItems((prev) => [...prev, newItem]);
setTodos((prev) => prev.filter((t) => t.id !== id));

Interview Tip

The best way to explain this is with the triple setCount example. Show the direct update version (result is 1) and the callback version (result is 3). This clearly demonstrates the problem and the solution. If the interviewer asks about closures and stale state, the setTimeout example is perfect.

Why interviewers ask this

This question tests if you understand how React batches state updates and how closures work with state. It is a very practical question — getting this wrong causes real bugs in production. A candidate who knows when to use the callback format shows they understand React's update model and can avoid common state-related bugs.