debugging-react-hooks
SkillWeb & browsingDebug React hooks infinite loops, stale closures, and missing dependencies. Systematic approach.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the debugging-react-hooks skill
What this skill tells your AI
The instructions your AI receives, as published by porcupine-md/anoa-browser in .claude/skills/library/frontend/debugging-react-hooks/SKILL.md and read by ahel’s review.
The Most Common Hook Bugs
Bug 1: Infinite useEffect Loop
Symptom: Component re-renders endlessly, console floods with logs.
Cause Pattern:
// BUG: object/array created in render → new reference every render
useEffect(() => {
setData(processData(rawData));
}, [processData]); // processData re-created every render!
Fix:
// Move stable values outside component, or use useCallback/useMemo
const processData = useCallback((data) => {
return data.map(item => ({ ...item, processed: true }));
}, []); // empty deps = stable reference
useEffect(() => {
setData(processData(rawData));
}, [processData, rawData]);
Bug 2: Stale Closure
Symptom: Effect reads old state value. The count is always 0 in a timer.
// BUG: count is captured at effect creation time (0)
useEffect(() => {
const id = setInterval(() => {
console.log(count); // always 0!
setCount(count + 1); // always 1!
}, 1000);
return () => clearInterval(id);
}, []); // empty deps = stale closure
Fix:
// Option A: functional update (doesn't need count in deps)
useEffect(() => {
const id = setInterval(() => {
setCount(prev => prev + 1); // reads current value
}, 1000);
return () => clearInterval(id);
}, []);
// Option B: include count in deps (but re-registers interval)
useEffect(() => {
const id = setInterval(() => setCount(count + 1), 1000);
return () => clearInterval(id);
}, [count]);
Bug 3: Missing Dependency Warning → Incorrect Fix
Bad fix:
// Silencing ESLint by disabling rule — WRONG
useEffect(() => {
fetchUser(userId); // depends on userId but it's not in deps
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
Correct fix:
useEffect(() => {
fetchUser(userId);
}, [userId]); // proper dependency
Debugging Checklist
-
Add console.log to identify the loop:
useEffect(() => { console.log('Effect ran, deps:', dep1, dep2); }, [dep1, dep2]); -
Check if a dep is an object/array:
// Each render creates a new object! → new reference → triggers effect const options = { limit: 10 }; // bad const options = useMemo(() => ({ limit: 10 }), []); // good -
React DevTools Profiler:
- "Highlight updates when components render" — shows what's re-rendering
-
Why Did You Render library:
npm install @welldone-software/why-did-you-render
Rules of Hooks Compliance
- Only call hooks at the top level (not inside loops/conditions)
- Only call hooks from React functions
- Install
eslint-plugin-react-hooks— let the linter catch issues
Signals
- GitHub stars
- 23
- Forks
- 2
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
debugging-react-hooks- Source
- github.com/porcupine-md/anoa-browser