Claude-skill-registry following-the-rules-of-hooks
Fix React Rules of Hooks violations - conditional calls, hooks in loops/callbacks/classes
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/following-the-rules-of-hooks" ~/.claude/skills/majiayu000-claude-skill-registry-following-the-rules-of-hooks && rm -rf "$T"
skills/data/following-the-rules-of-hooks/SKILL.mdRules of Hooks
React enforces two invariants on Hook usage. Violating these causes state corruption and unpredictable behavior.
The Rules
- Top-level only - Never call Hooks inside loops, conditions, nested functions, or try/catch/finally
- React functions only - Call Hooks exclusively from function components or custom Hooks
Why: Consistent call order across renders; conditional/dynamic invocation breaks state tracking.
Valid Hook Locations
✅ Top level of function components ✅ Top level of custom Hooks (
use* functions)
function Counter() { const [count, setCount] = useState(0); return <div>{count}</div>; } function useWindowWidth() { const [width, setWidth] = useState(window.innerWidth); return width; }
Common Violations
| Violation | Why Invalid | Fix |
|---|---|---|
| Inside if/else | Skipped on some renders | Move to top; use conditional rendering |
| Inside loops | Variable call count | Move to top; manage array state |
| After early return | Unreachable on some paths | Move Hook before return |
| In event handlers | Called outside render | Move to top; use state from closure |
| In class components | Classes don't support Hooks | Convert to function component |
| Inside callbacks | Nested function context | Move Hook to top level |
Common Fixes
Conditional Hooks
❌ Wrong:
function Profile({ userId }) { if (userId) { const user = useUser(userId); } }
✅ Right:
function Profile({ userId }) { const user = useUser(userId); if (!userId) return null; return <div>{user.name}</div>; }
Pattern: Always call Hook, use conditional rendering for output.
Hooks in Loops
❌ Wrong:
function List({ items }) { return items.map(item => { const [selected, setSelected] = useState(false); return <Item selected={selected} />; }); }
✅ Right:
function List({ items }) { const [selected, setSelected] = useState({}); return items.map(item => ( <Item key={item.id} selected={selected[item.id]} onToggle={() => setSelected(s => ({...s, [item.id]: !s[item.id]}))} /> )); }
Pattern: Single Hook managing collection, not per-item Hooks.
Hooks in Event Handlers
❌ Wrong:
function Form() { function handleSubmit() { const [loading, setLoading] = useState(false); setLoading(true); } return <button onClick={handleSubmit}>Submit</button>; }
✅ Right:
function Form() { const [loading, setLoading] = useState(false); function handleSubmit() { setLoading(true); } return <button onClick={handleSubmit} disabled={loading}>Submit</button>; }
Pattern: Hook at component level, setter in handler.
Hooks in Classes
❌ Wrong:
function BadCounter() { const [count, setCount] = useState(0); return <div>{count}</div>; }
✅ Right:
function Counter() { const [count, setCount] = useState(0); return <div>{count}</div>; }
Pattern: Use function components for Hooks.
Hooks in Callbacks
❌ Wrong:
function Theme() { const style = useMemo(() => { const theme = useContext(ThemeContext); return createStyle(theme); }, []); }
✅ Right:
function Theme() { const theme = useContext(ThemeContext); const style = useMemo(() => createStyle(theme), [theme]); }
Pattern: Call Hook at top level, reference in callback.
Hooks After Early Returns
❌ Wrong:
function User({ userId }) { if (!userId) return null; const user = useUser(userId); return <div>{user.name}</div>; }
✅ Right:
function User({ userId }) { const user = useUser(userId || null); if (!userId) return null; return <div>{user.name}</div>; }
Pattern: Call all Hooks before any returns.
Custom Hooks
Custom Hooks may call other Hooks because they execute during render phase:
function useDebounce(value, delay) { const [debounced, setDebounced] = useState(value); useEffect(() => { const timer = setTimeout(() => setDebounced(value), delay); return () => clearTimeout(timer); }, [value, delay]); return debounced; }
Requirements: Name starts with
use; called from function component or another custom Hook; follows same Rules of Hooks.
Quick Diagnostic
ESLint error: "React Hook cannot be called..."
- Check location: Is Hook inside if/loop/try/handler/class?
- Move Hook to top level of component/custom Hook
- Keep conditional logic, move Hook call outside it
- Use conditional rendering, not conditional Hooks