useState in Depth
useState stores a value that React remembers between renders, and gives you a setter to change it. In this lesson you'll go deeper: why setting state is asynchronous , why React batches multiple updates into one re-render, and why the functional updater form setX(prev => ...) is the only correct way to update state from its previous value.
Learn useState in Depth in our free React course — a beginner-friendly interactive lesson with runnable examples, a practice exercise and a quick recall.
Part of the free React course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
1️⃣ State Updates Are Asynchronous
When you call setCount , React does not change the count variable on the spot. It schedules a re-render. During the current render, count is a fixed, captured value (a closure). So calling setCount(count + 1) three times reads the same old count all three times — they all compute the same next value.
2️⃣ The Functional Updater Form
When the next value depends on the previous one, pass a function to the setter: setCount(prev => prev + 1) . React calls it with the latest pending value, so chained calls stack correctly — even under batching.
Now your turn — write the updater for a toggle. It should return the opposite of its argument:
3️⃣ Batching — One Re-render for Many Updates
For performance, React batches all the state updates that happen in a single event and re-renders once at the end. Set three pieces of state in one click handler and your component still renders only one extra time. Since React 18, this automatic batching also covers updates inside setTimeout , promises, and native handlers.
These lines build a correct counter component, but they're scrambled. Put them in the right order:
📋 Quick Reference
1. count is 0. You call setCount(count + 1) twice in one handler. What shows on screen?
1. Both reads see the old 0, compute 1, and batching keeps the last write — so 1.
2. Same start, but you call setCount(c => c + 1) twice. Now what?
2. Each updater builds on the pending value: 0→1→2. The screen shows 2.
3. You set three different state variables in one click. How many extra re-renders?
One. React batches all three into a single re-render.
Simulate five "like" clicks using the functional updater form. Run it and confirm the total is 5.
Practice quiz
Are state updates synchronous?
- Yes, the variable changes immediately
- Only in production
- No — setState schedules a re-render; the current variable does not change immediately
- Only for objects
Answer: No — setState schedules a re-render; the current variable does not change immediately. setState schedules an update. During the current render the state variable stays the same; React re-renders with the new value afterwards.
count is 0. You call setCount(count + 1) three times in one handler. What shows on screen?
- 1
- 3
- 0
- 2
Answer: 1. Each call reads the same stale count (0) from the current render's closure, so all three compute 1. Batching keeps the last write: the screen shows 1.
How do you correctly increment based on the previous value?
- setCount(count + 1)
- count = count + 1
- setCount(count++)
- setCount(prev => prev + 1)
Answer: setCount(prev => prev + 1). Use the functional updater setCount(prev => prev + 1). React calls it with the latest pending value, so chained calls stack correctly even when batched.
Starting at 0, you call setCount(c => c + 1) twice in one handler. What shows?
- 0
- 2
- 1
- 3
Answer: 2. Each updater builds on the latest pending value: 0 -> 1 -> 2. The screen shows 2.
What is batching in React?
- Grouping multiple state updates in one event into a single re-render
- Running effects in parallel
- Caching component output
- Delaying renders by one second
Answer: Grouping multiple state updates in one event into a single re-render. React batches the state updates that happen in a single event and re-renders once at the end for performance.
You set three different state variables in one click. How many extra re-renders?
- Three
- Two
- One
- Zero
Answer: One. React batches all three updates into a single re-render, so there is just one extra render.
In React 18+, automatic batching also covers updates inside which contexts?
- Only synchronous event handlers
- setTimeout, promises, and native event handlers too
- Only class components
- Only the initial render
Answer: setTimeout, promises, and native event handlers too. Since React 18, automatic batching extends to updates inside timeouts, promises, and native handlers — not just React event handlers.
When must you use the functional updater form setX(prev => ...)?
- Never; the value form is always fine
- Only for strings
- Only on the first render
- Any time the next state depends on the previous state
Answer: Any time the next state depends on the previous state. Whenever the next state depends on the previous (counters, toggles, appending to arrays), the updater form is the only correct choice under batching/async updates.
Which correctly appends an item to an array in state without mutating?
- arr.push(x); setArr(arr)
Create a new array: setArr(a => [...a, x]). Mutating the existing array with push keeps the same reference and skips the re-render.
What is the benefit of passing a function to useState, e.g. useState(() => expensiveInit())?
- It re-runs expensiveInit on every render
- It makes the state asynchronous
- It runs expensiveInit only once, on the initial render (lazy initialization)
- It memoizes the setter
Answer: It runs expensiveInit only once, on the initial render (lazy initialization). Lazy initialization: passing a function makes React call it only on the first render to compute the initial state, avoiding expensive work on every render.