intermediate 9 min read
React Hooks Internals, Lifecycle, and Dependency Traps
React Hooks store stateful logic and side-effects on Fiber nodes as a singly-linked list of hook objects. Their execution order must remain strictly identical on every render.
Why it matters in interviews
Violating the rules of hooks, misusing useEffect dependencies, causing stale closures, and confusing useEffect with useLayoutEffect account for the vast majority of subtle frontend bugs and interview questions.
Visual & Interactive Explanation
Hook Execution Timeline (useLayoutEffect vs useEffect)
useInsertionEffect
Runs before DOM mutations
TimingBefore DOM mutations
DOM AccessNo DOM access yet
Use CaseInjecting critical <style> tags
Strengths
- •Prevents style recalculation layout thrashing
Trade-offs
- •Should never be used in regular product application code
useLayoutEffect
Runs before browser paint
TimingAfter DOM mutation, BEFORE Paint
BlockingBlocks browser paint synchronously
Use CaseDOM measurements, tooltip positioning
Strengths
- •Zero visual flicker for layout calculations
Trade-offs
- •Slow execution blocks frame budget (causes dropped frames)
useEffect
Runs after browser paint
TimingAFTER Paint (Async passive effect)
BlockingNon-blocking (smooth 60fps)
Use CaseData fetching, subscriptions, timers
Strengths
- •Smooth UI responsiveness without blocking user inputs
Trade-offs
- •DOM measurements here may cause visible 1-frame layout jumps
Code Examples & Implementation
Production Custom Hook with AbortController and Cleanup
import { useState, useEffect } from 'react';
export function useFetchData<T>(url: string) {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
// AbortController cancels fetch if url changes or component unmounts
const controller = new AbortController();
setLoading(true);
fetch(url, { signal: controller.signal })
.then((res) => {
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
})
.then((json) => {
setData(json);
setError(null);
})
.catch((err) => {
if (err.name !== 'AbortError') {
setError(err);
}
})
.finally(() => setLoading(false));
// Cleanup: cancel in-flight request on re-render / unmount
return () => {
controller.abort();
};
}, [url]);
return { data, loading, error };
}Interview-Ready Answers
Hooks rely on a linked list attached to the component's Fiber node. On each render, React advances an internal hook pointer along this list, which is why hook order must never change.
Official Documentation & Specifications
Follow-up Questions & Deep Dives
🎯 Code Output — What Does This Print?
Question 1medium
function Counter() {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
console.log("count in handler:", count);
};
console.log("render:", count);
return <button onClick={handleClick}>+</button>;
}
// User clicks once. What logs?Question 2hard
function Counter() {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount((prev) => prev + 1);
setCount((prev) => prev + 1);
setCount((prev) => prev + 1);
};
console.log("render:", count);
return <button onClick={handleClick}>+</button>;
}
// User clicks once. What logs?Question 3tricky
function App() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log("effect:", count);
return () => console.log("cleanup:", count);
}, [count]);
console.log("render:", count);
return <button onClick={() => setCount(1)}>Click</button>;
}
// Component mounts, then user clicks once.Question 4hard
function Timer() {
const [count, setCount] = useState(0);
useEffect(() => {
const id = setInterval(() => {
setCount(count + 1);
}, 1000);
return () => clearInterval(id);
}, []);
return <div>{count}</div>;
}
// After 5 seconds, what does count display?Common Mistakes & Anti-Patterns
- Using useEffect to sync state from props instead of deriving state during render or using a key to reset state.
- Omission of cleanup functions for subscriptions, leading to memory leaks and setState on unmounted component warnings.
- Using useLayoutEffect on the server during SSR (triggers React warning because SSR has no DOM layout phase).
Real-World Architectural Scenario
A tooltip component briefly flickers in the top-left corner before snapping to the correct target button position. How do you fix it?
Rate Your Readiness
Rate how comfortably you can explain this in an interview to update your global readiness gauge:
Rate your confidence: