State: data that changes while the page is open
Why the screen has to be told that something changed, the difference between props and state, and why writing directly into state makes the update disappear.
A like button is one of the smallest programs people actually use. It stores a number, adds one when you click, and shows the result. Almost every difficulty in frontend development is hiding inside those three steps.
Start with the naive version, the one every beginner writes first.
let likes = 0;
function like() {
likes = likes + 1;
}
like();
like();React solves this by giving the value and the way to change it in the same breath. useState returns a pair: the value now, and a setter. Call the setter and the component runs again with the new value.
import { useState } from "react";
function LikeButton() {
const [likes, setLikes] = useState(0);
return (
<button onClick={() => setLikes(likes + 1)}>
{likes} likes
</button>
);
}The value and the way to change it, returned together.The two halves of that line have different jobs. likes is what this run of the component sees and draws. setLikes is the only honest way to change what the next run will see. A button whose label never changes is almost always a button that changes the variable and never calls the setter.
The same idea handles typing. A search box that loses what you typed is the classic symptom of a field nobody is remembering.
function SearchBox() {
const [text, setText] = useState("");
return (
<form>
<input value={text} onChange={(e) => setText(e.target.value)} />
<p>You typed: {text}</p>
</form>
);
}The field shows what state holds, and the keystroke writes state back.Now the trap that gets everybody, usually with a to-do list. Arrays and objects are not values the way numbers are: they are references to a place in memory. Changing the contents leaves the reference exactly where it was.
const [items, setItems] = useState(["Bread", "Tea"]);
function addBad(name) {
items.push(name);
setItems(items);
}
function addGood(name) {
setItems([...items, name]);
}One of these two versions leaves the list on screen unchanged.So there are two kinds of data in a component, and one question separates them: who is allowed to change it? Props are given from outside and read-only. State is owned inside and changeable. A counter with a configurable step needs both.
function Counter({ step }) {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + step)}>
{count}
</button>
);
}
const a = <Counter step={1} />;
const b = <Counter step={5} />;The step is given from outside, the count is owned inside.Practice
A counter component decides how much to add per click from a step value, and keeps the running total itself. Which of the two is state?
Hints
- The number on the screen comes from one of the two, and only one of them can be written by this component.
- A prop is a value handed in from outside. Changing it inside would be the component rewriting its own input.
Match each value with whether it arrives as a prop or is owned as state.
Hints
- Ask who is allowed to write each value: the component itself, or whoever places the component on the page.
- Two of these belong to the component and change while it lives; the other two are decided from outside.
A like button is clicked. Put what happens next in order.
- 1The label on the button shows the new number
- 2The component function runs again and reads the new value
- 3The setter is called with the new number
- 4The click handler runs
- 5React remembers the new value for this component
Hints
- Nothing can be drawn that has not been set yet, so the setter call comes before the new picture.
- The component runs again after the value changed, which is why the function body is not the first step of the next pass.