The Rules of Hooks
useState is a hook: a function whose name starts with use and that plugs your component into a React feature. You will meet several more — useReducer, useContext, useEffect, useRef. They are powerful, and they come with two short rules that keep them from breaking in confusing ways.
The rule
Top level, same order, every render — and only in components or hooks.
Top level. Call hooks directly in the body of the component, before any early return. Never inside if, loops, nested functions or event handlers.
function BookCard({ book }) {
const [open, setOpen] = useState(false); // ✓ top level
if (!book) return null; // early return comes after hooks
// ✗ if (book.read) { const [x, setX] = useState(0); }
// ✗ function handleClick() { useState(0); }
return <article onClick={() => setOpen(!open)}>{book.title}</article>;
}Same order, every render. Why the fuss? React does not know your variable names. It stores hook data in a list and hands it back by position: "first hook call gets slot 1, second gets slot 2." If a hook is skipped on one render, every hook after it receives the wrong slot.
Only in components or hooks. Call hooks from function components or from your own custom hooks (functions named useSomething, covered later). Not from ordinary helper functions or class components.
The official ESLint plugin, eslint-plugin-react-hooks, catches these mistakes as you type, and the Vite template includes it.
A few habits that go with the rules:
- Keep state minimal. Do not store what you can calculate. If you have
books, computebooks.lengthduring render instead of keeping acountstate that can drift out of sync. - Name setters clearly.
[isOpen, setIsOpen],[books, setBooks]. - Let the linter warn you rather than silencing it.
The shortcut
Picture the hooks as a numbered queue at the top of the function. Every render must pass the same numbered hooks in the same order before anything can branch.
Render 1
book.readis true: hooks run as #1open, #2rating, #3note.
Drills
Answer fast — trust the rule.
Where may useState be called?
How does React match each useState call to its stored value?
A component has if (loading) return <Spinner />; followed by useState. What is the problem?
A component keeps books in state and also count in state, updating both when a book is added. What is better?
Remember
- Top level, same order, every render — only in components or custom hooks.
- Picture a numbered queue of hooks before any branch.
- Trap: hooks after an early return, and storing values you could compute.
