Why use the callback format of setState?
ReactThe 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:
previs0, returns1 - Second call:
previs1, returns2 - Third call:
previs2, returns3
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.