const hooks = [];
let cursor = 0;
function useState(init) {
const i = cursor++;
if (hooks[i] === undefined) hooks[i] = init;
const set = v => { hooks[i] = v; };
return [hooks[i], set];
}
function resetCursor() { cursor = 0; }
// Bug: early return skips hooks — shifts cursor on next render
function BuggyComponent(userId) {
if (!userId) return 'no user'; // EARLY RETURN — skips useState below
const [name, setName] = useState('Alice'); // sometimes slot 0, sometimes never
return name;
}
// Render 1: userId provided — useState gets slot 0
resetCursor();
const r1 = BuggyComponent(1);
// Render 2: no userId — early return, cursor stays at 0
resetCursor();
const r2 = BuggyComponent(null);
// Render 3: userId again — but slot 0 state is correct
resetCursor();
const r3 = BuggyComponent(1);
console.log(r1);
console.log(r2);
console.log(r3);const hooks = [];
let cursor = 0;
function useState(init) {
const i = cursor++;
if (hooks[i] === undefined) hooks[i] = init;
const set = v => { hooks[i] = v; };
return [hooks[i], set];
}
function resetCursor() { cursor = 0; }
// Fix: hooks ALWAYS called before any conditional return
function FixedComponent(userId) {
const [name, setName] = useState('Alice'); // ALWAYS called — slot 0
if (!userId) return 'no user'; // conditional AFTER hooks
return name;
}
resetCursor();
const r1 = FixedComponent(1);
resetCursor();
const r2 = FixedComponent(null);
resetCursor();
const r3 = FixedComponent(1);
console.log(r1);
console.log(r2);
console.log(r3);Bug: Early return before useState is legal in this simulation but violates React's Rules of Hooks. In React, skipping a hook shifts all subsequent hooks to wrong slots, causing mismatched state.
Explanation: useState always runs — cursor is always at 1 after each render. The conditional is AFTER hooks, so slot 0 is always correctly mapped to name.
Key Insight: Rules of Hooks: NEVER call hooks conditionally, after an early return, or in loops. Always call them at the top level. This is why React relies on call order for state tracking.