React: Hooks can only be called inside the body of a function component
Fix React hook order errors by moving hooks out of conditionals, loops, and callbacks, and understanding the render-order invariant they enforce.
Symptoms
- React has detected a change in the order of Hooks called by Component
- Hooks can only be called inside the body of a function component error
- The app crashes only when a condition changes between renders
Possible causes
- A hook is called inside an if statement or loop, so it runs on some renders only
- A hook is called inside a callback or event handler
- An early return occurs before all hooks are called
- A custom hook is invoked conditionally
Diagnosis steps
Read the hook order message listing the differing hooks
Interpretation
It shows which hook changed position, revealing the conditional that guards it.
Move every hook above early returns
Interpretation
All hooks must run in the same order on every render, before any branching.
Check custom hook call sites for conditions
Interpretation
A hook called inside a ternary or if block changes the count between renders.
Root cause
React identifies hooks by call order within a component, not by name. If a render skips or adds a hook call — because it sits inside a conditional, loop, or early return — the internally stored state and effects attach to the wrong hooks. React detects the mismatch and throws rather than corrupting state silently.
Fix
Call all hooks unconditionally at the top level of the component, before any early return or branch. Move conditionals inside the hook (for example, pass a flag to useEffect) instead of around it. When behaviour is conditional, extract it into a custom hook that always runs and handles the branch internally.
Verify the fix
Toggle the condition that previously crashed the component; it now re-renders without any hook-order error, and the hook count stays constant across renders.
Symptoms
A component throws a hook-order error or “Hooks can only be called inside the body of a function component”. It typically happens only when a flag flips, so toggling state reproduces it.
Diagnosis
Read the message and find the guarded hook:
function Panel({ user }) {
if (!user) return <p>Loading</p>; // early return BEFORE hooks
const [open, setOpen] = useState(false); // order changes later
useEffect(() => { /* ... */ }, []);
return <div>{open}</div>;
}
The hook count differs when user is null versus present.
Fix
Hoist all hooks above branches:
function Panel({ user }) {
const [open, setOpen] = useState(false);
useEffect(() => {
if (user) { /* conditional work inside the effect */ }
}, [user]);
if (!user) return <p>Loading</p>;
return <div>{open}</div>;
}
Verification
Toggle user between null and a value; the component renders in both cases with no hook-order error, confirming the hook sequence is constant.
Additional Notes
The rule that hooks run in the same order every render is what lets React match state to calls without a key, so the fix is always to keep the sequence constant. Wrapping a conditional inside a hook instead of around it is the standard remedy, and putting an early return after all hooks preserves the ordering. Extracting conditional behaviour into a custom hook that always runs keeps components readable, and the development warning React prints is worth treating as an error, because the same mistake that merely warns today can attach state to the wrong hook after a refactor.