What are the rules of React hooks?
The short answer
There are two rules: only call hooks at the top level of your component (never inside loops, conditions, or nested functions), and only call hooks from React function components or custom hooks (never from regular JavaScript functions). These rules exist because React relies on the order hooks are called to work correctly.
Rule 1: Only call hooks at the top level
This is the rule that matters most. Every time your component renders, React expects hooks to be called in the exact same order. If you put a hook inside an if statement, it might run on one render and not on another — and that breaks everything.
Here's what goes wrong:
function Profile({ user }) {
const [name, setName] = useState(user.name);
// This breaks the rules
if (user.isAdmin) {
const [adminTools, setAdminTools] = useState([]);
}
const [theme, setTheme] = useState('light');
// ...
}On the first render, let's say user.isAdmin is true. React sees three useState calls and internally stores them as:
- Hook 1 →
name - Hook 2 →
adminTools - Hook 3 →
theme
Now if user.isAdmin becomes false on the next render, the second useState is skipped. React still has three stored hooks, but now the component only calls two:
- Hook 1 →
name(correct) - Hook 2 →
theme(wrong — React thinks this isadminTools)
The state gets completely mixed up. theme reads the value that belongs to adminTools. From here, things cascade into bugs that are incredibly hard to trace.
The fix: move the condition inside the hook
If you need conditional behavior, keep the hook call unconditional and put the condition inside:
function Profile({ user }) {
const [name, setName] = useState(user.name);
const [adminTools, setAdminTools] = useState([]);
const [theme, setTheme] = useState('light');
// Use adminTools only when needed
const visibleTools = user.isAdmin ? adminTools : [];
// ...
}The same applies to useEffect. Don't wrap it in a condition — put the condition inside the effect:
// Bad — conditional hook call
if (shouldFetch) {
useEffect(() => {
fetchData();
}, []);
}
// Good — condition inside the effect
useEffect(() => {
if (shouldFetch) {
fetchData();
}
}, [shouldFetch]);And don't call hooks inside loops either. If you need to manage a list of items with state, lift the state up to the parent or use a single piece of state that holds the collection.
Rule 2: Only call hooks from React functions
Hooks can only be called from two places:
- React function components — the functions that start with an uppercase letter and return JSX
- Custom hooks — functions that start with
useand call other hooks
You can't call hooks from a regular JavaScript function, a class component method, or an event handler that's been extracted into a utility file.
// This won't work — it's a regular function, not a component or custom hook
function getWindowWidth() {
const [width, setWidth] = useState(window.innerWidth);
return width;
}
// This works — it's a custom hook (starts with "use")
function useWindowWidth() {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
const handleResize = () => setWidth(window.innerWidth);
window.addEventListener('resize', handleResize);
return () =>
window.removeEventListener('resize', handleResize);
}, []);
return width;
}The use prefix isn't just a naming convention — it's how React (and the linter) knows that a function follows the rules of hooks. If a function starts with use, it's treated as a hook, and the same rules apply to it.
Why these rules exist
Here's where things get interesting. React doesn't store hook state by name. It doesn't know that your first useState call is for name and your second is for theme. Instead, it stores hooks in an array and matches them by position.
On every render, React goes through your component and counts: "First hook call, second hook call, third hook call." It uses that index to find the right state for each hook. This is an intentional design choice that keeps hooks simple and fast — no need to name each piece of state with a key.
But this index-based system only works if the number and order of hook calls is identical on every render. The moment a hook is skipped or added conditionally, the indices shift and state gets assigned to the wrong hooks.
The linter that enforces them
The React team built eslint-plugin-react-hooks specifically for this. It has two rules:
react-hooks/rules-of-hooks— enforces calling hooks at the top level and only from React functions. This catches the bugs we talked about.react-hooks/exhaustive-deps— checks that youruseEffect,useMemo, anduseCallbackdependency arrays are correct. Not technically a "rule of hooks," but it prevents a different class of bugs.
If you're using Create React App, Next.js, or most modern React setups, these rules are already enabled. If you see a lint warning about hooks, take it seriously — it's almost always pointing at a real bug, not a false alarm.
npm install eslint-plugin-react-hooks --save-dev{
"plugins": ["react-hooks"],
"rules": {
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn"
}
}What happens in practice if you break them
Most of the time, the linter catches it before you run your code. But if you somehow bypass the linter, you'll see symptoms like:
- State values that seem to belong to the wrong variable
- Effects that fire when they shouldn't, or don't fire when they should
- Stale closures where a hook reads an old value
- Crashes with React's internal error messages about "rendered more hooks than during the previous render"
These bugs are notoriously hard to debug because the root cause (hook order mismatch) is invisible — everything looks fine in the code unless you know to count the hook calls.
Why interviewers ask this
The rules of hooks are one of the few places in React where you can't just rely on intuition — you need to know the rules explicitly. Interviewers ask this to see if you understand the mechanism behind hooks (index-based storage), not just the surface-level rules. It also checks whether you use the linter and understand why React's constraints exist, which signals that you write hooks correctly in production code.