Rules of Hooks

Difficulty: Intermediate

React hooks follow two fundamental rules that must never be broken: (1) Only call hooks at the top level - never inside loops, conditions, or nested functions; and (2) Only call hooks from React function components or custom hooks - never from regular JavaScript functions. These rules exist because of how React internally tracks hook state using a linked list tied to call order.

The first rule - calling hooks at the top level - is the most critical. React does not identify hooks by name. Instead, it relies on the order in which hooks are called during each render. On the first render, React creates a linked list of hook states. On subsequent renders, React walks through this list in the same order, matching each hook call to its stored state. If you put a hook inside a conditional, the order of hook calls might change between renders, causing React to match the wrong state to the wrong hook.

Consider what happens when a useState hook is inside an if statement. On the first render, the condition might be true, so React records: [useState, useEffect]. On the next render, the condition might be false, skipping the useState. React now sees: [useEffect], but it tries to match the first call to the first stored state (which was useState's state). This mismatch corrupts the entire hook state for that component, leading to unpredictable bugs.

The second rule - only calling hooks from React functions - ensures that hooks have access to the component's fiber node (React's internal representation). Regular functions don't have a fiber context, so hooks can't store or retrieve their state. Custom hooks work because they are ultimately called from within a component's render, which has the fiber context.

The eslint-plugin-react-hooks package provides two rules: 'rules-of-hooks' (enforces the two rules above) and 'exhaustive-deps' (warns about missing dependencies in useEffect, useMemo, and useCallback). These rules are essential for catching hook-related bugs at development time. The rules-of-hooks ESLint rule is considered so important that it should always be set to 'error', not 'warn'.

Code examples

Correct vs incorrect hook usage

// CORRECT: Hooks at the top level
function GoodComponent({ showExtra }) {
  const [name, setName] = React.useState('Alice');
  const [age, setAge] = React.useState(25);

  // Conditional logic AFTER hooks
  const displayAge = showExtra ? age : 'hidden';

  console.log('Name:', name);
  console.log('Age:', displayAge);
  return null;
}

GoodComponent({ showExtra: true });

// BAD: Hook inside condition (DO NOT DO THIS)
// function BadComponent({ showExtra }) {
//   const [name] = React.useState('Alice');
//   if (showExtra) {
//     const [age] = React.useState(25); // BREAKS RULES
//   }
// }

Always call all hooks at the top of your component, then use conditional logic afterward. Never skip hook calls based on conditions.

Hook call order demonstration

function HookOrderDemo() {
  // React internally tracks: slot 0 = useState('first')
  const [first] = React.useState('first');
  // slot 1 = useState('second')
  const [second] = React.useState('second');
  // slot 2 = useRef(0)
  const counter = React.useRef(0);

  counter.current += 1;

  console.log('Hook slot 0:', first);
  console.log('Hook slot 1:', second);
  console.log('Hook slot 2 (ref):', counter.current);
  console.log('Total hooks called: 3');

  return null;
}

HookOrderDemo();

React tracks hooks by their call order (0, 1, 2...). The same hooks must be called in the same order on every render for React to correctly match each call to its stored state.

Valid hook contexts

// Custom hook (valid - starts with 'use')
function useCounter(initial) {
  const [count, setCount] = React.useState(initial);
  console.log('useCounter hook called, count:', count);
  return count;
}

// Function component (valid)
function MyComponent() {
  const count = useCounter(10);
  console.log('MyComponent rendered, count:', count);
  return null;
}

// Regular function (INVALID - do not call hooks here)
// function helper() {
//   const [val] = React.useState(0); // BREAKS RULES
// }

MyComponent();

Hooks can be called from function components and custom hooks (functions starting with 'use'). They cannot be called from regular functions, class components, or outside of React's render cycle.

Key points

Concepts covered

rules of hooks, hook call order, eslint-plugin-react-hooks, conditional hooks, hook internals