Runnable lesson · press Try it on the examples to edit and run them here.
You already know the basics of useState. This lesson covers the cases that confuse people once they build bigger components.
Reset state with a key
State belongs to a component at a certain position in the tree. If you render a different component, or the same one with a different key, React throws away the old state and starts fresh:
function NoteEditor({ person }) {
const [notes, setNotes] = React.useState("");
return (
<div>
<p>Editing: {person}</p>
<textarea aria-label="Notes" value={notes} onChange={(e) => setNotes(e.target.value)} />
</div>
);
}
function App() {
const [person, setPerson] = React.useState("Ada");
return (
<div>
<button onClick={() => setPerson(person === "Ada" ? "Grace" : "Ada")}>
Switch to {person === "Ada" ? "Grace" : "Ada"}
</button>
{/* The key makes React treat each person as a separate editor */}
<NoteEditor key={person} person={person} />
</div>
);
}
Type something and switch person: the notes are cleared, because the key changed. Without key, the old notes would stay while the name changes, a classic bug. This is the right way to “reset state when a prop changes”.
Storing a function in state
If you want the value to be a function, wrap it, because passing a function to the setter or to useState means “compute it”:
function App() {
const [op, setOp] = React.useState(() => (n) => n * 2); // initial value is a function
const [name, setName] = React.useState("double");
return (
<div>
<p>Mode: {name}</p>
<p>Result: {op(5)}</p>
<button onClick={() => { setOp(() => (n) => n * 3); setName("triple"); }}>Use triple</button>
</div>
);
}
setOp(() => (n) => n * 3): the outer arrow is the updater, and it returns the function we want to store. This is rare in practice.
Initial state from props
useState(props.value) uses the prop only on the first render. That is fine for an initial value, but then do not expect changes to follow. See Props Are Read-Only.
Updates are asynchronous but ordered
If you call the setter twice with plain values, the last one wins. With updater functions, they run in order. The details are in Updating State Correctly.
Comparing values: Object.is
React skips a re-render when you set a value that is Object.is-equal to the current one. For primitives that means “same value”. For objects it means “same reference”, which is why you must create new objects and arrays.
function App() {
const [n, setN] = React.useState(5);
const renders = React.useRef(0);
renders.current++;
return (
<div>
<p>n = {n}, Renders: {renders.current}</p>
<button onClick={() => setN(5)}>Set to same value</button>
<button onClick={() => setN(6)}>Set to new value</button>
</div>
);
}
(Occasionally React runs the component function once more before bailing out, so the render count of a same-value update can be one higher in some situations. Do not depend on exact numbers.)
Too many states?
If you have a dozen states that change together, see Multiple State Variables and Reducers and useReducer.
When state is the wrong tool
| Need | Use instead |
|---|---|
| A value you can calculate | A plain variable during render (Derived State) |
| A value that must survive renders but should not cause one (timer id, previous value) | useRef |
| Data shared by many components | Lift state up, or Context (Context API) |
| Data from a server | Effects and loading states, or a data library (Fetching Data with Effects) |
| State that should survive a page reload or be in the URL | localStorage, or URL search parameters |
Key points
- Changing a component’s
keyresets its state, which is the clean way to reset when props change. - The initial value is used once; wrap a function to store a function.
- Setting an equal value skips the render, so always create new objects and arrays.