Skip to main content

Multiple setState calls

Short answer

If you call setState (or setCount, setUser, etc.) several times in a row within one event or a synchronous piece of code, React batches these updates and performs only one re-render.

But! If you pass setState just a value (not a function), all calls will use the same old state value, so they can "get lost".


1. An example of the problem

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

It seems that count should become 3, but in fact it becomes 1.

Why?


2. What React does

React does not update count instantly - it puts all the updates into a queue:

javascript
setCount(count + 1)"update count from 0 to 1" setCount(count + 1)"update count from 0 to 1" setCount(count + 1)"update count from 0 to 1"

All three updates use the old value count = 0, so the result after applying them is 1.


3. The correct way - use a functional update

For each call to build on the current value, you need to pass setState a function that receives the previous state:

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

Now each call:

javascript
1) prev = 01 2) prev = 12 3) prev = 23

The result is 3, as expected.


4. Why React does this

Asynchronous updates

React uses batching - it waits for the event to finish (onClick, onChange, etc.) and applies all accumulated changes in one render, so it does not re-render the component after every call.

Performance savings

Instead of 3 re-renders React does 1. That is tens of times faster with large component trees.


5. Outside an event (for example, setTimeout)

Sometimes batching does not apply (in older React versions <18) inside asynchronous callbacks, and updates are performed separately:

javascript
setTimeout(() => { setCount(count + 1); setCount(count + 1); }, 0);

In React 18 and above batching is enabled even for asynchronous code, so now both updates get merged too.


6. What React stores internally

React keeps a state update queue (updateQueue):

javascript
[ (prev) => prev + 1, (prev) => prev + 1, (prev) => prev + 1 ]

During render it walks through it:

javascript
let newState = oldState; for (const update of queue) { newState = update(newState); }

After applying all the updates the component re-renders once.


Summary

SituationWhat happens
Several setState(value) in a rowUse the old value → result is only from the last one
Several setState(prev => …) in a rowWork sequentially → correct result
All updates within one eventMerge (batching) → 1 re-render
React <18 (older) in setTimeoutbatching does not apply
React 18+batching works even in async code

Conclusion

React does not update state immediately - it accumulates changes. If you need to update state based on the previous one, always use the functional form:

javascript
setState(prev => newValueBasedOn(prev));

This guarantees correctness even with several calls in a row.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.