Rules of Hooks

Call Them at the Top Level, in the Same Order, Every Time

Rules of Hooks

The two non-negotiable rules every hook follows — and why they exist.

4 min read Level 2/5 #react#hooks#rules
What you'll learn
  • Always call hooks at the top level of a component
  • Always call hooks in the same order
  • Recognize hook-name convention (`use*`)

Hooks (any function starting with use) follow two rules. Break them and React loses track of state.

Rule 1: Only Call Hooks at the Top Level

NOT inside if, for, while, or after an early return.

// ✗ Hook inside a condition
function Bad({ show }) {
  if (show) {
    const [n, setN] = useState(0);   // breaks
  }
  return null;
}

// ✓ Hook at the top, condition inside
function Good({ show }) {
  const [n, setN] = useState(0);
  if (!show) return null;
  return <p>{n}</p>;
}

Rule 2: Only Call Hooks From React Functions

  • Components
  • Other hooks

Not from regular event handlers, helper functions, or class components.

// ✗ Hook outside a component / hook
function helper() {
  const [n, setN] = useState(0);   // breaks
}

// ✓ Inside a component
function Counter() {
  const [n, setN] = useState(0);
  return <button>{n}</button>;
}

// ✓ Inside another hook
function useCounter() {
  const [n, setN] = useState(0);
  return { n, inc: () => setN(n + 1) };
}

Why?

React identifies hook calls by their order, not by name.

function Profile() {
  const [name, setName] = useState("");     // hook 1
  const [age, setAge] = useState(0);        // hook 2
  useEffect(() => { ... }, []);             // hook 3
  return ...;
}

React stores state in a list [name-state, age-state, effect]. Next render, it walks the list in the same order. If you wrap useState(0) in an if, the list shifts — and React maps state to the wrong slot.

That’s why the order has to be identical on every render.

The ESLint Plugin

eslint-plugin-react-hooks enforces the rules:

  • react-hooks/rules-of-hooks — top-level only, function only
  • react-hooks/exhaustive-deps — list all reactive deps in useEffect/useMemo/useCallback

Every starter template includes this plugin. Don’t disable it.

The Naming Convention

Functions that use hooks must start with use. This isn’t just style — the linter relies on it to enforce the rules. Your custom hooks should also be named useSomething.

function useOnlineStatus() {
  const [online, setOnline] = useState(navigator.onLine);
  useEffect(() => { ... }, []);
  return online;
}

Up Next

useRef — for values that should persist across renders without triggering one.

useRef →