How it works
The explanation, with examples you can run.
Slots in call order
Here is a toy version of useState (not React's code, but the same idea), with a component that breaks the rule:
Press Run to see what your code prints, in order.
Runs in this browser, in an isolated frame with no access to this page. console.log, info, warn and error are shown; code runs as a classic script, so use an async function for await.
The first render fills three slots: "Ada", 36, "dark". In the second, the condition skips a call, so theme gets slot 1 and reads 36. React works the same way: hooks have no names, only positions. That is why hooks must run at the top level, unconditionally, in the same order every render. React's lint rule (eslint-plugin-react-hooks) catches violations, and in development React warns when the order changes.
The same reasoning explains the other rule: hooks may only be called while React is rendering a component (or from a custom hook it calls), because only then does React know whose slots to use.
One set of closures per render
Press Run to see what your code prints, in order.
Runs in this browser, in an isolated frame with no access to this page. console.log, info, warn and error are shown; code runs as a classic script, so use an async function for await.
Each call made its own count and its own arrow function that closed over it, so the three handlers return [0, 1, 2]. In React that is usually what you want: a click handler from the render on screen sees the state on screen.
It becomes a bug when a callback from an old render outlives it:
import { useEffect, useState } from "react" export function Ticker() { const [count, setCount] = useState(0) useEffect(() => { const id = setInterval(() => { setCount(count + 1) // always 0 + 1: this closure is from the first render }, 1000) return () => clearInterval(id) }, []) // the effect never re-runs, so the closure is never replaced return <p>{count}</p>}The counter goes to 1 and stays there. Fixes, from simplest: use an updater (setCount((c) => c + 1)), which doesn't need count at all; or list count in the dependencies so the effect is recreated with each new value; or read the latest value from a ref.
Custom hooks
A function whose name starts with use and that calls other hooks is a custom hook. It is not a new mechanism: its hook calls simply take their slots in the calling component's list, in order. Two components using the same custom hook get independent state.
Check yourself
3 questions. Get them all right to finish this topic.
0 of 3 right so far. Get all three to finish this topic.