Chapter 116: Rules of Hooks
Core Idea
Hooks must be called in the exact same order on every render of a given component — only at the top level (never inside conditions, loops, or nested functions) and only from React function components or other custom Hooks — because React associates each Hook's internal state with its call position, not with any name or identifier.
Key Concepts
- Only call Hooks at the top level. Never inside
if/for/nested helper functions — a Hook called conditionally means its position in the call sequence can differ between renders, and React has no way to reliably match that render's useState(0) call to the correct previously-stored state.
- Only call Hooks from React functions: a component function, or another custom Hook (Ch 41) — never from a plain JavaScript function, a class method, an event handler passed around as a callback, or top-level module code.
- Why order matters mechanically: React tracks Hook state internally as an ordered list per component instance, matched to the Nth Hook call in render order — call order is the only identifier React has, since Hook calls have no explicit name/key the way state variables or props do.
- The fix for "I need a Hook conditionally": restructure — extract the conditional logic into a separate component that either renders or doesn't (so the Hook itself is always called unconditionally within that component), rather than trying to skip a Hook call based on a condition.
use (Ch 91) is the sole documented exception — it's explicitly designed to tolerate conditional/loop calls, unlike every other built-in and custom Hook, because of how it integrates with Suspense and Context reading rather than persistent per-instance state.
- Enforced mechanically by the linter: the
rules-of-hooks ESLint rule (Ch 47) catches violations of this rule automatically — treat its errors as non-negotiable, not warnings to suppress.
Code Examples
// ❌ Wrong: conditional Hook call — breaks call-order matching
if (isLoggedIn) {
const [name, setName] = useState('');
}
// ✅ Correct: Hook always called; the condition affects what's rendered, not whether the Hook runs
const [name, setName] = useState('');
return isLoggedIn ? <Greeting name={name} /> : null;
- What it demonstrates: moving the conditional logic around the Hook call (affecting the render output) rather than around the call itself (affecting whether the Hook executes at all).
Key Takeaways
- "Top level only, same order every render" is a mechanical requirement of how React tracks Hook state, not an arbitrary style rule.
- When a Hook feels like it should be conditional, restructure — either extract a component that renders or doesn't, or call the Hook unconditionally and use its result conditionally.
use (Ch 91) is a deliberate, singular exception — don't generalize its conditional-call flexibility to any other Hook.
Connects To
- Ch 21 (State: A Component's Memory): the underlying per-call-position state-tracking model this rule protects.
- Ch 91 (use): the one Hook explicitly exempt from this rule.
- Ch 47 (ESLint Rules Reference): the
rules-of-hooks lint rule enforcing this automatically.