State with useState
Give components memory and update it without mutating.
What you will learn
- Use useState
- Update objects and arrays immutably
- Lift state up
A regular variable inside a component is reset every time the component runs and, crucially, changing it does not tell React to redraw. State solves both problems: it is memory that React preserves between renders, and updating it triggers a new render.
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Clicked {count} times
</button>
);
}useState(0) returns the current value and a setter. The argument is only the initial value. Calling setCount schedules a re-render with the new value. Hooks must be called at the top level of a component, never inside loops, conditions or nested functions.
State updates are snapshots
Within one render, count is a fixed number. Calling setCount(count + 1) three times adds only one, because each call sees the same count. When the next value depends on the previous one, pass an updater function.
function addThree() {
setCount((c) => c + 1);
setCount((c) => c + 1);
setCount((c) => c + 1); // total +3
}Never mutate state
Treat state as immutable. Create a new object or array instead of editing the existing one; otherwise React cannot detect the change.
const [user, setUser] = useState({ name: "Ada", age: 36 });
const [todos, setTodos] = useState(["write", "test"]);
// objects: copy then override
setUser({ ...user, age: 37 });
// arrays
setTodos([...todos, "deploy"]); // add
setTodos(todos.filter((t) => t !== "test")); // remove
setTodos(todos.map((t) => (t === "write" ? "edit" : t))); // changeLifting state up
When two components need the same data, move the state to their closest common parent and pass it down as props, along with functions to change it. This keeps one source of truth.
function Parent() {
const [query, setQuery] = useState("");
return (
<>
<SearchBox value={query} onChange={setQuery} />
<Results query={query} />
</>
);
}Deriving values
Do not store something in state if you can compute it from existing state or props. Storing fullName alongside first and last creates a chance for them to disagree; compute it during render instead.
State is for things that change over time and affect what is shown. Everything else should be a constant, a prop, or a value computed while rendering.
Try it yourself
Build a Toggle component with a button that switches between "ON" and "OFF", and a Counter with plus, minus and reset buttons where the count never goes below zero.
Show solution
function Toggle() {
const [on, setOn] = useState(false);
return <button onClick={() => setOn(!on)}>{on ? "ON" : "OFF"}</button>;
}
function Counter() {
const [n, setN] = useState(0);
return (
<>
<button onClick={() => setN((c) => Math.max(0, c - 1))}>-</button>
<span>{n}</span>
<button onClick={() => setN((c) => c + 1)}>+</button>
<button onClick={() => setN(0)}>Reset</button>
</>
);
}