Rules of Hooks

Hooks are special functions that must be called at the top level of a component.

Hooks are functions starting with use that let function components use React features like state and lifecycle. They follow two strict rules.

The two rules

  1. Only call hooks at the top level — never inside loops, conditions, or nested functions. This keeps their order stable across renders.
  2. Only call hooks from React functions — components or other custom hooks, not plain functions.

The ESLint plugin eslint-plugin-react-hooks enforces these rules for you.

Example

Example · javascript
import { useState, useEffect } from 'react';

function Example() {
  // Correct: hooks at the top level, unconditional
  const [open, setOpen] = useState(false);
  useEffect(() => {
    document.title = open ? 'Open' : 'Closed';
  }, [open]);
  return <button onClick={() => setOpen((o) => !o)}>Toggle</button>;
}

When to use it

  • A code review tool flags hooks called inside if blocks or loops as lint errors so teams catch rules-of-hooks violations before they create subtle state bugs in production.
  • A component author calls all hooks unconditionally at the top of the function so React's call order remains stable regardless of which code branch executes.
  • A testing library mock verifies that custom hooks are only called inside function components, enforcing the rule that hooks cannot run in class components or plain functions.

More examples

Hooks at the top level

Places both hooks unconditionally at the top; the conditional is inside useEffect, not around it.

Example · jsx
import { useState, useEffect } from 'react';

function Timer({ active }) {
  // GOOD: hooks are always called in the same order
  const [seconds, setSeconds] = useState(0);

  useEffect(() => {
    if (!active) return;
    const id = setInterval(() => setSeconds(s => s + 1), 1000);
    return () => clearInterval(id);
  }, [active]);

  return <p>{seconds}s</p>;
}

Hook inside condition — WRONG

Contrasts a hooks-in-condition violation that crashes at runtime with the correct pattern.

Example · jsx
// BAD — React will throw a 'Rendered more hooks than previous render' error
function BadComponent({ show }) {
  if (show) {
    const [val, setVal] = useState(0); // breaks the rules
  }
  return null;
}

// GOOD — always call the hook, gate the effect inside it
function GoodComponent({ show }) {
  const [val, setVal] = useState(0);
  if (!show) return null;
  return <p>{val}</p>;
}

Custom hook follows the rules

Defines a custom hook that itself calls hooks at the top level, satisfying all rules-of-hooks requirements.

Example · jsx
import { useState, useCallback } from 'react';

// Starts with 'use' — React treats it as a hook
function useToggle(initial = false) {
  const [on, setOn] = useState(initial);
  const toggle = useCallback(() => setOn(v => !v), []);
  return [on, toggle];
}

function Accordion({ title, children }) {
  const [open, toggle] = useToggle();
  return (
    <div>
      <button onClick={toggle}>{title}</button>
      {open && <div>{children}</div>}
    </div>
  );
}

Discussion

  • Be the first to comment on this lesson.