Skip to solution
hardFrontend

What underlying mechanism makes the Rules of Hooks mandatory?

659 views
01

Understand the problem

Question presented to candidate: "The Rules of Hooks say call them at the top level and never conditionally. What is the actual implementation reason?"

What a strong answer should cover:

  • React stores a component hook state in an ordered list on its fiber, and matches each call to its slot by position, not by name.
  • There is no identifier available: useState(0) gives React nothing to key on. The index in the call sequence is the identity.
  • A conditional call shifts every later hook onto the wrong slot — so a useState can land on a useEffect slot, reading another hook's data.
  • React detects the count mismatch and throws: "Rendered fewer hooks than expected. This may be caused by an accidental early return statement."
  • The same reasoning covers early returns, loops with variable counts, and hooks inside nested functions or conditions.
  • Why the design: it keeps hooks a plain function call with no registration, no keys, and no boilerplate — an explicit trade of flexibility for ergonomics.
  • The tooling angle: this constraint is what makes hooks statically analysable, which is why the ESLint rule can verify them and the React Compiler can memoise safely.
  • The workaround: put the condition inside the hook, or extract a component.
  • use() is the deliberate exception — it may be called conditionally because it does not own a state slot.

Clarifying questions expected:

  • "Do you want the failure mode, or why React chose positional matching over named slots?"

Code / implementation expected: Optional. A minimal hook implementation using an array and a cursor is the clearest way to show it.

reacthooksinternals
02

Attempt it yourself

Sketch your approach before reading the solution — that's what interviews test.

Nudge consolestandby

Stuck? Beam a request up — the console returns a conceptual nudge that guides your logic without spoiling the implementation.

03

Study the solution

Target Audience: Engineers preparing for senior React interviews — assumes hooks fluency. Difficulty: Hard

How to read this doc: Concepts are explained in plain language first, then tagged with 📌 Interview term:. The error in section 3 is React actual message, produced by deliberately calli

Solution ready — 2 min read

Classified // press E to declassify

04

Explore the playground snippets

A 15-line reimplementation of hook slots, and the rule it forces
Run Playground
import { useState } from "react";

// ── A miniature of React's mechanism: an array of slots and a cursor. ──────
// This is the whole reason for the Rules of Hooks.
const slots = [];
let cursor = 0;
let rerender = () => {};

function miniUseState(initial) {
  const i = cursor++;                          // THIS position is the identity
  if (!(i in slots)) slots[i] = initial;
  const set = (v) => { slots[i] = v; rerender(); };
  return [slots[i], set, i];
}

function runMiniComponent(includeMiddle) {
  cursor = 0;                                  // reset before every "render"
  const trace = [];
  const [a, , ia] = miniUseState("name");
  trace.push({ hook: "useState(name)", slot: ia, value: a });

  if (includeMiddle) {
    const [b, , ib] = miniUseState("age");
    trace.push({ hook: "useState(age)", slot: ib, value: b });
  }

  const [c, , ic] = miniUseState("effect-data");
  trace.push({ hook: "useEffect-ish", slot: ic, value: c });
  return trace;
}

export default function App() {
  const [renderNo, setRenderNo] = useState(0);
  const [includeMiddle, setIncludeMiddle] = useState(true);
  const trace = runMiniComponent(includeMiddle);
  rerender = () => setRenderNo((n) => n + 1);

  const misaligned = trace.some((t) => t.hook === "useEffect-ish" && t.value !== "effect-data");

  const th = { textAlign: "left", padding: "4px 14px 4px 0", borderBottom: "1px solid #ddd", fontSize: 13 };
  const td = { padding: "4px 14px 4px 0", fontFamily: "ui-monospace, monospace", fontSize: 13 };

  return (
    <div style={{ padding: 24, fontFamily: "system-ui", lineHeight: 1.6 }}>
      <p>
        <label>
          <input
            type="checkbox"
            checked={includeMiddle}
            onChange={(e) => setIncludeMiddle(e.target.checked)}
          />{" "}
          call the middle hook
        </label>
      </p>

      <table style={{ borderCollapse: "collapse" }}>
        <thead>
          <tr><th style={th}>hook call</th><th style={th}>slot</th><th style={th}>value it read</th></tr>
        </thead>
        <tbody>
          {trace.map((t, i) => (
            <tr key={i}>
              <td style={td}>{t.hook}</td>
              <td style={td}>{t.slot}</td>
              <td style={{ ...td, color: t.hook === "useEffect-ish" && t.value !== "effect-data" ? "crimson" : "#161" }}>
                {String(t.value)}
              </td>
            </tr>
          ))}
        </tbody>
      </table>

      <p style={{ marginTop: 12, color: misaligned ? "crimson" : "#161" }}>
        {misaligned
          ? "Misaligned: the third hook slid onto slot 1 and is now reading the age state."
          : "Aligned: every hook is reading its own slot."}
      </p>

      <p style={{ color: "#666", fontSize: 13 }}>
        Untick the box. Real React would throw "Rendered fewer hooks than
        expected" here — this miniature has no such check, so it shows you the
        corruption the check exists to prevent.
      </p>
    </div>
  );
}
05

Join the discussion

Discussion (0)

Sign in to join the discussion.

No responses yet. Be the first to share what you think.

Transmission complete // awaiting log

KEEP THE
STREAK ALIVE.

Dossier 93 of 119 decoded in the React.js track. One more won't hurt.

Back to track