Introduction
React Hooks work correctly only if they are called in a predictable, consistent way on every render. To guarantee this, React defines two strict Rules of Hooks. Breaking either rule leads to subtle bugs, such as state being assigned to the wrong variable or effects firing at the wrong time, so the eslint-plugin-react-hooks package enforces these rules automatically during development.
Cricket analogy: A bowler must deliver the same run-up and action every ball for the umpire to judge fairly; if Bumrah suddenly changed his approach mid-delivery, the whole rhythm breaks, which is why Hooks need a fixed, predictable calling pattern every render.
Syntax
// Rule 1: Only call Hooks at the top level
function Component() {
const [a, setA] = useState(0); // top level - OK
useEffect(() => { /* ... */ }); // top level - OK
return <div>{a}</div>;
}
// Rule 2: Only call Hooks from React functions
function useCustomHook() {
const [value, setValue] = useState(null); // custom Hook - OK
return value;
}Explanation
Rule 1 — Only call Hooks at the top level: never call Hooks inside loops, conditions, nested functions, or after an early return. Rule 2 — Only call Hooks from React function components or from custom Hooks (functions whose name starts with 'use'); never call them from regular JavaScript functions, class components, or event handler callbacks directly. Both rules exist because React does not track Hooks by name — it tracks them by call order. Internally, each component instance keeps a linked list (or array) of Hook 'slots' populated in the exact sequence the Hooks were called during the first render. On every subsequent render, React walks that same list in order, matching the Nth useState call to the Nth slot, the Nth useEffect call to the Nth slot, and so on. If a Hook call is conditionally skipped on some renders (e.g., inside an if block or after an early return), the call order shifts, and React ends up associating the wrong state or effect with the wrong slot — corrupting component state in ways that are very hard to debug. Keeping Hooks unconditional at the top level guarantees the same number and order of calls on every render, keeping the slot-to-Hook mapping stable.
Cricket analogy: A scorer tracks each over by ball number in a fixed sequence in the scorebook; if a no-ball is skipped without recording it, every subsequent ball's slot shifts and the whole scorecard misaligns, just like a skipped Hook shifts React's slot list.
Example
// WRONG: Hook called conditionally
function Bad({ isLoggedIn }) {
if (isLoggedIn) {
const [user, setUser] = useState(null); // breaks call order between renders
}
useEffect(() => { /* ... */ });
return <div />;
}
// CORRECT: Hook always called, condition moved inside
function Good({ isLoggedIn }) {
const [user, setUser] = useState(null); // always called
useEffect(() => {
if (isLoggedIn) {
// conditional logic goes inside the Hook, not around it
fetchUser().then(setUser);
}
}, [isLoggedIn]);
return <div />;
}Output
In the 'Bad' component, if isLoggedIn is true on one render and false on the next, the number of Hook calls changes between renders. React will throw a runtime error ('Rendered fewer hooks than expected') or silently misalign state, since the useEffect call now occupies a different slot index than before. The 'Good' component calls useState unconditionally every render and instead puts the conditional logic inside the effect body, keeping the Hook call order identical across all renders and avoiding the bug entirely.
Cricket analogy: If a captain fields only ten players when the opposition's star batter is on strike and eleven otherwise, the umpire will flag the mismatch immediately, just as React throws 'Rendered fewer hooks than expected' when isLoggedIn changes the Hook count.
Key Takeaways
- Rule 1: only call Hooks at the top level — never inside loops, conditions, nested functions, or after an early return.
- Rule 2: only call Hooks from React function components or from custom Hooks, never from plain functions or classes.
- Both rules exist because React matches Hooks to state using call order, not by name.
- Conditionally skipping a Hook call shifts every subsequent Hook's slot index, corrupting component state.
- Move conditional logic inside the Hook body (e.g., inside useEffect) rather than wrapping the Hook call itself in a condition.
- The eslint-plugin-react-hooks 'rules-of-hooks' rule catches most violations automatically during development.
Practice what you learned
1. Why does React require Hooks to be called in the same order on every render?
2. Which of these is a violation of the Rules of Hooks?
3. From where is it valid to call a Hook?
4. What is the correct fix when you need conditional behavior involving a Hook?
Was this page helpful?
You May Also Like
Introduction to React Hooks
Learn what React Hooks are, why they were introduced in React 16.8, and how they let function components use state and lifecycle features.
The useEffect Hook
Understand useEffect for side effects in function components, covering the dependency array, cleanup functions, and effect timing.
Custom Hooks in React
Learn how to extract reusable stateful logic into your own custom hooks that follow the rules of hooks.
Related Reading
Related Study Notes in Web Development
Browse all study notesWebSockets Study Notes
Web Development · 30 topics
Web DevelopmentWebAssembly Study Notes
WebAssembly · 30 topics
Web DevelopmentgRPC Study Notes
Protocol Buffers · 30 topics
Web DevelopmentSpring Boot Study Notes
Java · 30 topics
Web DevelopmentFlask Study Notes
Python · 30 topics
Web DevelopmentDjango Study Notes
Python · 30 topics