Rules of Hooks Explained: Why Hook Order Matters

Learn the two Rules of Hooks, why React depends on call order to track state, and how to keep your Hooks at the top level of components.

6 min read

React's Rules of Hooks are two constraints that every Hook call must follow. React tracks each Hook's state by call order, so a Hook that runs conditionally can quietly attach state to the wrong component.

The two rules

First, call Hooks only at the top level of your component or custom Hook. Second, call Hooks only from React function components or custom Hooks. Both rules protect the call order that React depends on.

  • No Hooks inside conditions, loops, or nested functions.
  • No Hooks after an early return.
  • No Hooks in event handlers or class components.
  • No Hooks in regular JavaScript functions.
  • No Hooks inside functions passed to useMemo, useReducer, or useEffect.

These rules exist because React relies on call order.

Why call order matters

When a component renders, React records each Hook call in the order it runs. It stores state for the first useState call, then the second, and returns them in that same order on the next render.

App.jsxApp.jsx
function Profile() {
  const [name, setName] = useState("Ada");
  const [age, setAge] = useState(30);
  return (
    <p>
      {name} is {age}
    </p>
  );
}

On every render, React expects call one to hold the name state and call two to hold the age state. If a condition skips one call on a later render, the order shifts and React hands the age value to the variable that expected name. A single skipped call desynchronizes every Hook that follows it.

Keep conditions inside the Hook

You cannot wrap a Hook call in an if statement, but the rendering logic can still be conditional. Put the Hook calls before the return, then branch afterward.

App.jsxApp.jsx
function Profile({ showAge }) {
  const [name, setName] = useState("Ada");
  const [age, setAge] = useState(30);
  if (!showAge) {
    return <p>{name}</p>;
  }
  return <p>{name} is {age}</p>;
}

Both useState calls run before the conditional return, so their order never changes. The early return only controls what renders, not which Hooks run. When a condition must decide between Hooks, move the condition inside the Hook instead.

Only call Hooks from React functions

Custom Hooks count as React functions, so a Hook may call other Hooks. Regular JavaScript functions cannot call Hooks, which keeps stateful logic visible inside components and custom Hooks.

Moving a useState call into a plain helper function triggers a lint error, because the plugin cannot guarantee call order there. If a helper does call a Hook, rename it with the use prefix so the plugin treats it as a Hook.

The eslint-plugin-react-hooks plugin catches both rules automatically. Configure it in your lint setup, and a violation shows up as a lint error instead of a runtime bug.

Learn how to create a custom Hook and what it means to reuse stateful logic without sharing state.

Rune AI

Rune AI

Key Insights

  • Call Hooks only at the top level, before any early return.
  • Never call Hooks inside conditions, loops, or nested functions.
  • Call Hooks only from function components or custom Hooks.
  • React matches Hooks to state by call order.
  • Configure eslint-plugin-react-hooks to catch violations automatically.
RunePowered by Rune AI

Frequently Asked Questions

What are the two Rules of Hooks?

Call Hooks only at the top level of your component or custom Hook, and call Hooks only from React function components or custom Hooks.

Why do Hooks have to be called in the same order?

React stores Hook state by call order. If the order changes between renders, React returns the wrong state to the wrong variable.

Can I call a Hook inside an if statement?

No. Move the condition inside the Hook, or place the Hook call before any early return so the call order stays the same on every render.

Conclusion

The Rules of Hooks exist because React matches each Hook to its state by call order. Keep Hooks at the top level of components and custom Hooks, and let the eslint plugin catch violations before they reach runtime.