State and useState
Memory that survives between renders, and the rules for changing it correctly.
Why a normal variable will not do
A local variable inside a component is recreated every time the function runs, and changing it does not tell React to render again. Both problems have to be solved at once.
function Counter() {
let count = 0; // reset on every render, and nothing re-renders
return <button onClick={() => count++}>{count}</button>;
}The button above never changes. useState fixes it.
useState
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Clicked {count} times
</button>
);
}useState returns a pair: the current value, and a function that replaces it. Calling the setter does two things — it stores the new value outside the function, and it schedules a re-render. The array destructuring is a convention; you may name the pair whatever you like.
State updates are asynchronous
Setting state does not change the variable you are holding. The new value appears on the next render.
function handleClick() {
setCount(count + 1);
console.log(count); // still the old value
}This also means several calls based on the same stale value collapse into one:
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
// count increases by 1, not 3When the next value depends on the previous one, pass a function instead. React calls it with the latest value, in order.
setCount(c => c + 1);
setCount(c => c + 1);
setCount(c => c + 1);
// count increases by 3Never mutate state
React decides whether to re-render by comparing the old value to the new one. Editing an object or array in place leaves both references identical, so React sees no change.
const [user, setUser] = useState({ name: "Ada", age: 36 });
// Wrong: same object, React sees nothing new
user.age = 37;
setUser(user);
// Right: a new object
setUser({ ...user, age: 37 });Arrays follow the same rule. Use methods that return a new array rather than ones that change the original.
const [items, setItems] = useState(["a", "b"]);
setItems([...items, "c"]); // add
setItems(items.filter(i => i !== "a")); // remove
setItems(items.map(i => i === "a" ? "A" : i)); // replace onepush, pop, splice, sort, and reverse all mutate. If you need sort or reverse, copy first: [...items].sort().
Lazy initial state
The argument to useState is only used on the first render, but it is still evaluated on every render. If computing it is expensive, pass a function instead and React will call it once.
const [data, setData] = useState(() => expensiveSetup());Choosing where state lives
State belongs in the closest component that needs it. When two siblings need the same value, move it up to their nearest common parent and pass it down as props. This is called lifting state up.
function Parent() {
const [query, setQuery] = useState("");
return (
<>
<SearchInput value={query} onChange={setQuery} />
<Results query={query} />
</>
);
}The parent owns the value; both children read from the same source, so they cannot disagree.
Grouping related values
Several independent pieces of state are usually clearer as separate calls. Group them into one object only when they genuinely change together.
// Independent — keep separate
const [name, setName] = useState("");
const [email, setEmail] = useState("");
// Always change together — group
const [position, setPosition] = useState({ x: 0, y: 0 });