Managing State with useState
State updates, batching, and why you update state instead of mutating it.
What you'll learn
- Why a plain variable cannot drive the UI
- Reading and setting state, and when the new value arrives
- Updating state from the previous value
- Changing objects and arrays in state without mutating them
A variable is not state
React re-renders a component when its state changes. A plain variable is not state: you can change it, but nothing tells React to run the component again, so the screen keeps showing the old value.
Both buttons below increment a number. Only one of them tells React:
The console proves plain really does change — the screen just never hears
about it. And even if something else re-rendered the component, plain would be
reset to 0, because the function runs again from the top.
State solves both halves: React remembers the value between renders, and changing it schedules a re-render.
Reading and setting
useState returns a pair, which you unpack with
array destructuring:
const [count, setCount] = useState(0);
countis the value for this render. It never changes during a render.setCountasks React for another render with a new value.- The argument to
useStateis the initial value, used only on the first render.
State is not updated immediately
This is the part that surprises everyone. Setting state does not change the
count variable you are holding — it schedules a new render. Reading count
straight after setCount gives you the old value:
count is a const inside a function call. React calls your function again with
a new value; it cannot reach into the call that already happened.
Three increments, one step
The consequence: calling the setter three times with the same stale value sets the same number three times.
The first button adds one. All three calls compute 0 + 1.
The second adds three. Passing a function to the setter means "given the latest value, here is the next one", so React applies them in sequence. Reach for the updater form whenever the next value depends on the previous one.
Objects and arrays
State follows the copying rule from Destructuring, Spread, and Rest: React compares by identity, so mutating an object in place leaves it the same object, and React concludes nothing changed.
The first button changes the data and the screen does not move — the object handed back is the one React already has. The second passes a new object, so React re-renders.
For arrays, use the methods that return a new array: [...tags, "react"] to
add, filter to remove, map to change one item.
Where state should live
Each call to useState belongs to one component instance. Two <Counter />s
have two independent counts. When two components need the same value, it
belongs in their closest common parent — the subject of
Lifting State Up.
Common mistakes
- Reading state right after setting it. You get the old value; the new one arrives on the next render.
setCount(count + 1)several times. UsesetCount(c => c + 1).- Mutating an object or array in state. Copy it instead.
- Calling
useStatein a condition or loop. Hooks run in the same order on every render — see Writing Custom Hooks.
Exercise
Two bugs. "Add two" only adds one, and "Rename" does nothing. Fix both.