Call Them at the Top Level, in the Same Order, Every Time
Rules of Hooks
The two non-negotiable rules every hook follows — and why they exist.
What you'll learn
- Always call hooks at the top level of a component
- Always call hooks in the same order
- Recognize hook-name convention (`use*`)
Hooks (any function starting with use) follow two rules. Break
them and React loses track of state.
Rule 1: Only Call Hooks at the Top Level
NOT inside if, for, while, or after an early return.
// ✗ Hook inside a condition
function Bad({ show }) {
if (show) {
const [n, setN] = useState(0); // breaks
}
return null;
}
// ✓ Hook at the top, condition inside
function Good({ show }) {
const [n, setN] = useState(0);
if (!show) return null;
return <p>{n}</p>;
} Rule 2: Only Call Hooks From React Functions
- Components
- Other hooks
Not from regular event handlers, helper functions, or class components.
// ✗ Hook outside a component / hook
function helper() {
const [n, setN] = useState(0); // breaks
}
// ✓ Inside a component
function Counter() {
const [n, setN] = useState(0);
return <button>{n}</button>;
}
// ✓ Inside another hook
function useCounter() {
const [n, setN] = useState(0);
return { n, inc: () => setN(n + 1) };
} Why?
React identifies hook calls by their order, not by name.
function Profile() {
const [name, setName] = useState(""); // hook 1
const [age, setAge] = useState(0); // hook 2
useEffect(() => { ... }, []); // hook 3
return ...;
} React stores state in a list [name-state, age-state, effect].
Next render, it walks the list in the same order. If you wrap
useState(0) in an if, the list shifts — and React maps state to
the wrong slot.
That’s why the order has to be identical on every render.
The ESLint Plugin
eslint-plugin-react-hooks enforces the rules:
react-hooks/rules-of-hooks— top-level only, function onlyreact-hooks/exhaustive-deps— list all reactive deps inuseEffect/useMemo/useCallback
Every starter template includes this plugin. Don’t disable it.
The Naming Convention
Functions that use hooks must start with use. This isn’t just
style — the linter relies on it to enforce the rules. Your custom
hooks should also be named useSomething.
function useOnlineStatus() {
const [online, setOnline] = useState(navigator.onLine);
useEffect(() => { ... }, []);
return online;
} Up Next
useRef — for values that should persist across renders without
triggering one.