Pick a depth. Each prompt opens in your AI pre-loaded with the lesson. Click a row to preview the prompt.
React stores hook state in a linked list attached to each fiber node. On every render the runtime advances a cursor through that list, matching each hook call to its stored state by position. This is the mechanical reason hooks cannot be called conditionally or inside loops — skipping a call shifts every subsequent hook's cursor position, silently mapping the wrong state to the wrong hook. Understanding the call queue turns the Rules of Hooks from arbitrary convention into an inevitable consequence of the data structure.
A fiber holds an ordered list of 'memo cells'. useState, useEffect, and every other hook consumes one cell per call, in call order.
show from true to false. Note the exact console error — React reports 'Rendered fewer hooks than expected'. Read the message: it tells you exactly which render had the wrong count.console.log('hook call', n) inside each useState initializer. Wrap them in a component and toggle show. Observe how the log count changes between renders — that's the cursor advancing differently each time.if block. Confirm the error disappears. Now the cursor always advances two cells before hitting the conditional.eslint-plugin-react-hooks and add 'react-hooks/rules-of-hooks': 'error' to your ESLint config. Confirm the static analysis catches BrokenCounter before you run the code.import { useState } from 'react';
// CORRECT: hooks always called in the same order
function Counter({ label }: { label: string }) {
const [count, setCount] = useState(0); // cell #1
const [step, setStep] = useState(1); // cell #2
return (
<button onClick={() => setCount(c => c + step)}>
{label}: {count}
</button>
);
}
// BROKEN: conditional hook shifts every subsequent cell
function BrokenCounter({ show }: { show: boolean }) {
if (show) {
// When show flips to false this cell is skipped —
// cell #2 below becomes cell #1, corrupting the state mapping.
const [extra] = useState('extra'); // ← conditional hook
}
const [name] = useState('Alice'); // reads wrong cell when show=false
return <div>{name}</div>;
}
// FIX: call unconditionally, use conditionally
function FixedCounter({ show }: { show: boolean }) {
const [extra] = useState('extra'); // always cell #1
const [name] = useState('Alice'); // always cell #2
return <div>{show ? extra : name}</div>;
}