Suggest an editImprove this articleRefine the answer for “Multiple setState calls”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)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**. **Key point:** if you pass `setState` a plain value instead of a function, all calls use the same old state value, so they can "get lost".Shown above the full answer for quick recall.Answer (EN)Image## 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 = 0 → 1 2) prev = 1 → 2 3) prev = 2 → 3 ``` 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 | Situation | What happens | | --- | --- | | Several `setState(value)` in a row | Use the old value → result is only from the last one | | Several `setState(prev => …)` in a row | Work sequentially → correct result | | All updates within one event | Merge (batching) → 1 re-render | | React <18 (older) in `setTimeout` | batching 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.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.