Navigate
Search topics, hooks, and fundamentals
Think of useState as a telephone switchboard. Each line is a state variable β plug in a cable and the board lights up, React re-renders.
π Theme: Telephone Switchboard
Your component is a telephone switchboard. State values are connections β plug one in and the board lights up (re-renders). Without useState, you can move cables around all day, but the board stays dead. setState is the act of plugging a cable into a jack React can see.
Regular variables are like cables plugged into a switchboard that isn't powered. The value changes behind the scenes, but React never sees it β the display stays frozen.
Active Lines
useState wires the board to React. Every time you plug in a cable, React sees the connection change and re-renders the display immediately.
Active Lines
0function LiveSwitchboard() {
const [activeLines, setActiveLines] = useState(0);
function plugInCable() {
setActiveLines(prev => prev + 1);
// React sees the change β re-renders β board updates!
}
}useState is like wiring the switchboard to React β every connection change lights up the board.
State isn't always a single line β booleans, numbers, and objects are all fair game. Each playground below demonstrates a different type of connection on the board.
DISCONNECTED
The line is disconnected. Toggle the switch to flip the boolean.
const [isConnected, setIsConnected] = useState(false);
// Toggle: flip the boolean
setIsConnected(prev => !prev);Frequency
88.0 MHz
SIGNAL: 1/5
const [frequency, setFrequency] = useState(88.0);
// Tune the dial
setFrequency(101.1);
// Fine-tune with updater
setFrequency(prev => Math.min(prev + 0.1, 108.0));Active Call
Unknown
const [call, setCall] = useState({
caller: "",
destination: "Office",
priority: "Normal",
status: "Ringing",
});
// Update one field β spread to keep the rest!
setCall(prev => ({ ...prev, destination: "Office" }));Pass a function to useState for expensive initial values. Otherwise the computation runs on every render β even though React throws the result away after the first.
Eager Wiring
useState(heavyComputation())Computation runs every render!
total = 1,249,975,000
Lazy Wiring
useState(() => heavyComputation())Computation runs only once!
total = 1,249,975,000
Red = computation runs every render. Green = runs once.
// Eager: runs EVERY render (slow!)
const [total, setTotal] = useState(
heavyComputation() // called on each render
);
// Lazy: runs ONLY on first render
const [total, setTotal] = useState(
() => heavyComputation() // called once
);These are the patterns that trip up developers most often. Switch between Wrong and Fixed to compare the code side by side.
function Profile() {
const [user, setUser] = useState({ name: "Alice", score: 0 });
function handleClick() {
user.score += 1; // mutating the existing object
setUser(user); // same reference β React bails out
}
return <button onClick={handleClick}>{user.score}</button>;
}function Counter() {
const [count, setCount] = useState(0);
function handleTripleClick() {
setCount(count + 1); // all three read the same count
setCount(count + 1);
setCount(count + 1); // result: 1, not 3
}
return <button onClick={handleTripleClick}>{count}</button>;
}function SortedList({ items }) {
// items.sort() runs on EVERY render
// even though React ignores the result after mount
const [sorted, setSorted] = useState(items.slice().sort());
return <ul>{sorted.map(i => <li key={i}>{i}</li>)}</ul>;
}