← Debugging Guides

阅读中文版本

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.

intermediatedebuggingreacthooks

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.