State: data that remembers
useState, the pair it hands back, why a plain variable cannot survive a render, and the rule about storing only what cannot be worked out.
The piece React was named for
A variable inside a component is thrown away when the render ends. let count = 0 then count++ looks like it works — until React renders again and the function runs from the top, with count back at 0. State is the value React keeps between renders: const [count, setCount] = useState(0).
useState(0) returns a pair: the current value, and a function to change it. Calling setCount(5) tells React "the value is now 5, render again with it". The render function runs once more, useState answers 5, and the picture matches the data. That loop is React in one sentence.
Setting state, and what happens next
The setter never changes the variable in front of you; the new value arrives with the next render. That is why setCount(count + 1) twice in one handler adds only one: both calls read the same old count. When the new value depends on the old one, hand over a function instead: setCount((n) => n + 1), and each update sees the previous one.
Setting the same value twice is fine: React skips the re-render when the new state is equal to the old. That is a small detail with a large consequence in the effects chapter, where a render-triggered effect could otherwise loop.
Store the smallest truth
State should hold what cannot be worked out from anything else. A full name, a total, a filtered list — those are computed during render from the data that is already there. Copying them into state means two copies that must be kept in step, and the day they disagree is the day the page lies.
A component may call useState as many times as it has independent pieces of truth: a name and an email are two, a form and its sent-message are two. Grouping unrelated values in one object state is the other common slip — then every change makes a new object and every field must be spread in by hand.
Updates are queued, not applied
A setter does not write to a variable; it puts an update in a queue and asks React to render again. React may batch several updates from one event into a single render, which is why two setters in one handler produce one picture rather than two. The queue is also why the value you read in the handler is the one from before the click.
The function form of a setter is the way to say "whatever is in the queue when this runs", and it is the only safe form when the update depends on the previous value.
A counter, and one that grows by five
const { useState } = React;
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>one more</button>
<button onClick={() => setCount((n) => n + 5)}>five more</button>
<button onClick={() => setCount(0)}>reset</button>
</div>
);
}
// In a real project the hooks arrive by import:
// import { useState } from 'react';
// Here they come off the global React the lesson inlines.A render sees one version of state
Calling setCount(5) does not change count in the function that is already running. It asks React for another render, and that render calls the component again with the new value. Event handlers therefore close over the state of the render that created them, which is a snapshot, not a live variable.
This explains the classic surprise: setCount(count + 1); setCount(count + 1); adds one, because both expressions read the same snapshot. The function form receives the pending value instead, so each updater sees the result of the one before it.
Each instance owns its own state
State is attached to a place in the tree, not to the function as a type. Render <Counter /> twice and each copy has its own count; clicking one does not move the other. Unmount a component and its state is gone; mount it again and it starts from the initial value.
The place is decided by position and key. Two counters in the same branch keep their state across renders as long as React can match them; swap their order without keys and the state stays with the position, not the thing you thought was there. The lists chapter shows that in full.
Three state mistakes: changing an object or array in place, so React sees the same value and skips the render; reading the state variable right after calling its setter and expecting the new value; and copying a derived total into a second useState, which gives you two numbers that can disagree.
Try these next
- Add a third counter for fouls and show all three in a summary line.
- Make the reset button clear every counter in one handler.
- Add a button that runs
setHome(home + 1)and one that runssetHome((n) => n + 1), then explain the difference when each is called twice in one handler.
A counter you can steer
Run it and press the buttons a few times. Then add a button that subtracts one, and make the count show in the document title too — you will need an effect chapter for that properly, so for now just predict what happens.
A scoreboard with independent counters
Run it and add points to each side. Then add a third counter for fouls, include it in the total, and make the reset clear all three in one click.
Two updates in one handler are one render, and both read the state from before it. If the second update needs the first one's value, use the function form — that is what it is for.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
Why does let count = 0; count++ inside a component not work?
