Rules of Hooks
Hooks are special functions that must be called at the top level of a component.
Hooks are functions starting with use that let function components use React features like state and lifecycle. They follow two strict rules.
The two rules
- Only call hooks at the top level — never inside loops, conditions, or nested functions. This keeps their order stable across renders.
- Only call hooks from React functions — components or other custom hooks, not plain functions.
The ESLint plugin eslint-plugin-react-hooks enforces these rules for you.
Example
import { useState, useEffect } from 'react';
function Example() {
// Correct: hooks at the top level, unconditional
const [open, setOpen] = useState(false);
useEffect(() => {
document.title = open ? 'Open' : 'Closed';
}, [open]);
return <button onClick={() => setOpen((o) => !o)}>Toggle</button>;
}When to use it
- A code review tool flags hooks called inside if blocks or loops as lint errors so teams catch rules-of-hooks violations before they create subtle state bugs in production.
- A component author calls all hooks unconditionally at the top of the function so React's call order remains stable regardless of which code branch executes.
- A testing library mock verifies that custom hooks are only called inside function components, enforcing the rule that hooks cannot run in class components or plain functions.
More examples
Hooks at the top level
Places both hooks unconditionally at the top; the conditional is inside useEffect, not around it.
import { useState, useEffect } from 'react';
function Timer({ active }) {
// GOOD: hooks are always called in the same order
const [seconds, setSeconds] = useState(0);
useEffect(() => {
if (!active) return;
const id = setInterval(() => setSeconds(s => s + 1), 1000);
return () => clearInterval(id);
}, [active]);
return <p>{seconds}s</p>;
}Hook inside condition — WRONG
Contrasts a hooks-in-condition violation that crashes at runtime with the correct pattern.
// BAD — React will throw a 'Rendered more hooks than previous render' error
function BadComponent({ show }) {
if (show) {
const [val, setVal] = useState(0); // breaks the rules
}
return null;
}
// GOOD — always call the hook, gate the effect inside it
function GoodComponent({ show }) {
const [val, setVal] = useState(0);
if (!show) return null;
return <p>{val}</p>;
}Custom hook follows the rules
Defines a custom hook that itself calls hooks at the top level, satisfying all rules-of-hooks requirements.
import { useState, useCallback } from 'react';
// Starts with 'use' — React treats it as a hook
function useToggle(initial = false) {
const [on, setOn] = useState(initial);
const toggle = useCallback(() => setOn(v => !v), []);
return [on, toggle];
}
function Accordion({ title, children }) {
const [open, toggle] = useToggle();
return (
<div>
<button onClick={toggle}>{title}</button>
{open && <div>{children}</div>}
</div>
);
}
Discussion