React Rules of Hooks
Table of Contents + −
In the last lesson you met React Hooks like useState. This lesson covers the two small rules you must follow when calling them, because breaking one gives you confusing bugs where the screen shows the wrong state and nothing in your code looks wrong.
📜 The two rules in one breath
Here are both rules so you have the full picture before the details.
- Rule one: only call hooks at the top level of your component. Never inside an
if, never inside a loop, never inside a nested function. - Rule two: only call hooks from a React function component, or from a custom hook. Don’t call them from regular JavaScript functions.
The rest of this lesson explains why each rule matters, and how to do what you want without breaking them.
🔢 Why call order matters
This is the key idea behind rule one. React does not track hooks by name, it tracks them by call order.
- On every render, React goes through your hooks one by one, in order.
- It remembers the first hook, the second, the third, and matches each call to the right stored value.
- So the first
useStatealways gets slot one, the second always gets slot two, and so on. - This only works if the order is the same on every single render.
Think of it like a row of numbered boxes. React puts your first piece of state in box one, your second in box two. Next render it reads them back in that same order, so if the order shifts you get the wrong box.
React counts, it does not read names
React never sees count or name. It only sees “first hook, second hook,
third hook”. The order of the calls is the only thing it uses to line up your
state. Keep the order stable and everything matches up.
🚫 Don’t call hooks inside conditions
Now you can see why a hook inside an if is dangerous. The condition can change between renders, so the call order changes too.
This pair shows a useState hidden inside an if versus the same hook at the top.
// ❌ Wrong - the hook only runs when isLoggedIn is truefunction Profile({ isLoggedIn }) { if (isLoggedIn) { const [name, setName] = useState("Alex"); // sometimes runs, sometimes not }
const [age, setAge] = useState(20); // ...}
// ✅ Right - both hooks run every render, in the same orderfunction Profile({ isLoggedIn }) { const [name, setName] = useState("Alex"); const [age, setAge] = useState(20); // ...}Let’s trace what goes wrong in the bad version.
- When
isLoggedInis true, React sees two hooks:nameis slot one,ageis slot two. - When
isLoggedInturns false, the first hook is skipped, so nowagebecomes slot one. - React still has slot one holding the old
namevalue, soagenow reads the wrong stored value. - The state is scrambled, and you get bugs that make no sense from looking at the code.
So the fix is simple: move every hook to the top, before any if, and they run in the same order no matter what.
🔁 Don’t call hooks inside loops or nested functions
The same rule covers loops and inner functions, for the same reason: both can change the call order.
This shows a hook stuck inside a loop versus a clean fixed list of hooks at the top.
// ❌ Wrong - the number of hooks changes with items.lengthfunction List({ items }) { for (let i = 0; i < items.length; i++) { const [open, setOpen] = useState(false); // count changes every render }}
// ✅ Right - one fixed hook at the top, hold the per-item data inside itfunction List({ items }) { const [openIds, setOpenIds] = useState([]); // one array holds all of it // toggle ids inside this single piece of state}Here is the takeaway in points.
- A loop that runs three times calls three hooks. Next render it might run five times and call five hooks, so the count is not stable.
- A hook inside a nested function may run later, or never, so its slot is unpredictable.
- The fix is to keep a single hook at the top and store the changing data inside it, like an array or an object.
🧩 How to do conditional logic the right way
Maybe you really do want state that only matters in one case. The trick is to call the hook unconditionally, then put your if around how you use the value, not around the hook itself.
This pair shows the wrong way (condition around the hook) and the right way (condition around the value).
// ❌ Wrong - the hook itself is behind a conditionfunction Greeting({ isLoggedIn }) { if (isLoggedIn) { const [name, setName] = useState("Alex"); // breaks call order return <p>Hello {name}</p>; } return <p>Please log in</p>;}
// ✅ Right - hook always runs, the branch is on the valuefunction Greeting({ isLoggedIn }) { const [name, setName] = useState("Alex"); // always called, top level
if (!isLoggedIn) { return <p>Please log in</p>; } return <p>Hello {name}</p>;}The rule of thumb is short.
- Always call the hook at the top, every render, no matter what.
- Then branch on the value the hook gave you, or branch on what you return.
- The hook order stays stable, so your conditional behaviour still works.
Hooks first, conditions after
A handy habit: write all your hooks at the very top of the component, then add
any if checks and early returns below them. If a hook ever sits below an early
return, that is a sign you have broken the rule.
🧪 Only call hooks from components or custom hooks
That covers rule one. Rule two is shorter, because hooks only belong in two places.
- Inside a React function component (a function that returns JSX).
- Inside a custom hook (your own function whose name starts with
use). - Not inside a plain helper function, an event handler body, or any normal JavaScript function.
The reason is that hooks need React to be in the middle of rendering a component. A plain function called on its own is not part of any render, so React has nowhere to store the state.
This pair shows a hook in a regular function versus the same logic moved into a proper custom hook.
// ❌ Wrong - getName is a plain function, not a component or custom hookfunction getName() { const [name, setName] = useState("Alex"); // React has no render to attach to return name;}
// ✅ Right - a custom hook, name starts with "use"function useName() { const [name, setName] = useState("Alex"); // fine, this is a custom hook return name;}The only difference that matters here is the name and the role.
- A function named
useSomethingis treated as a custom hook, so it is allowed to call other hooks. - A plain function like
getNameis not, so calling a hook inside it is a mistake. - Starting the name with
useis how both you and the tools know it follows the hook rules.
🛡️ Let ESLint catch the mistakes for you
You don’t have to spot every one of these by eye. There is an official tool that watches for hook rule violations as you type.
- It’s called eslint-plugin-react-hooks, and it ships with most React setups by default.
- It warns you the moment a hook sits inside an
if, a loop, or a plain function. - If you used a tool like Vite or Create React App, it is usually already turned on.
This is the kind of warning it shows when you put a hook in the wrong place.
A typical ESLint warning
React Hook "useState" is called conditionally. React Hooks must becalled in the exact same order in every component render.So even if you forget a rule, the tool reminds you before the bug ever reaches the screen. Keep it on and trust it.
🧩 What You’ve Learned
- ✅ React tracks hooks by their call order, not by name, so the order must be the same on every render
- ✅ Rule one: call hooks only at the top level, never inside an
if, a loop, or a nested function - ✅ A hook inside a condition can shift the call order and scramble your state
- ✅ To do conditional logic, call the hook unconditionally and branch on the value or on what you return
- ✅ Rule two: call hooks only from React function components or from custom hooks (names starting with
use) - ✅
eslint-plugin-react-hookscatches these mistakes automatically, and it’s on by default in most setups
Check Your Knowledge
Test what you learned. Pick an answer for each question, then click Check.
- 1
How does React keep track of which stored value belongs to which hook?
Why: React does not read your variable names. It matches each hook to its stored value purely by call order, which is why the order must stay the same on every render.
- 2
Why is calling useState inside an if statement a problem?
Why: When the condition changes, the hook is sometimes called and sometimes skipped. That shifts the call order, so React lines up the wrong stored values.
- 3
You need state that only matters when a user is logged in. What is the correct approach?
Why: Always call the hook unconditionally at the top, then put the condition around how you use the value or what you return. The call order stays stable.
- 4
Where are you allowed to call a hook like useState?
Why: Hooks need React to be rendering a component, so they only work inside a function component or a custom hook (a function whose name starts with use). A plain function has no render to attach to.
🚀 What’s Next?
Now you know the two rules and why call order is the reason behind them. Next you’ll go deeper into the hook you use the most, with the patterns and edge cases that come up once you start building real components.