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 }), []); // goodReact 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