useStateuseEffectuseContextuseMemouseCallbackuseRefuseReduceruseLayoutEffectuseIduseDeferredValueuseTransitionuseImperativeHandleCustom Hooks

React Hooks Deep Dive

All 13 React hooks + custom hook patterns — with live interactive demos.

📌 Render Count: This component has rendered 1 times
useState

useState — Functional Updates & Lazy Initialization

The foundation hook. Supports functional updates and lazy initialization.

🔢 Count: 0

Functional updates are safer in async scenarios (closures capture stale state).

💡 Lazy Init Count: 100

Check console: the expensive initializer only ran once on mount, not on every render.

// ❌ Avoid: direct state in updater (stale closure risk) setCount(count + 1); // ✅ Prefer: functional update (always uses fresh state) setCount(c => c + 1); // 🚀 Lazy init: runs expensive logic ONCE const [state, setState] = useState( () => expensiveComputation() );
🎤 Interview: "Functional updates are critical when the next state depends on the previous state, especially in async callbacks or event handlers. Lazy initialization is useful for reading from localStorage or performing expensive initial calculations."
useEffect

useEffect — Side Effects & Cleanup

Runs after render. Handles subscriptions, timers, network requests. Always cleanup!

⏱️ Timer: 0s (cleanup on unmount)
📏 Window Width: 0px (listener cleanup)
// Runs AFTER every render (no deps) useEffect(() => { /* side effect */ }); // Runs ONCE on mount (empty deps) useEffect(() => { const timer = setInterval(...); return () => clearInterval(timer); // cleanup }, []); // Runs when [id] changes useEffect(() => { fetchUser(id); }, [id]);
🎤 Interview: "The deps array tells React when to re-run the effect. Empty deps = run once on mount. Missing deps = runs every render (usually a bug). Cleanup functions prevent memory leaks from subscriptions or timers."
useContext

useContext — Consume Context Without Prop Drilling

Access context value from nearest provider. Re-renders when context changes.

🎨 Current Theme: dark
Theme: dark
// 1. Create context const ThemeContext = createContext({}); // 2. Provide value <ThemeContext.Provider value={{theme, setTheme}}> <ChildComponent /> </ThemeContext.Provider> // 3. Consume in child (no props!) const { theme, setTheme } = useContext(ThemeContext);
🎤 Interview: "useContext solves prop drilling. But be careful: every consumer re-renders when context changes. For performance, split contexts or use useMemo to stabilize the provider value."
useMemo

useMemo — Memoize Expensive Computations

Only re-runs when dependencies change. Skips re-computation on unrelated renders.

⚙️ Sorted: [1, 2, 5, 8, 9]
// ❌ Bad: sorts on EVERY render const sorted = [...nums].sort(); // ✅ Good: only sorts when [nums] changes const sorted = useMemo( () => [...nums].sort((a, b) => a - b), [nums] );
🎤 Interview: "useMemo caches a computed value. Use for expensive calculations like filtering large arrays. Don't over-memoize — the memo itself has overhead."
useCallback

useCallback — Stable Function References

Prevents child re-renders when passing callbacks as props (use with React.memo).

🔢 Callback clicked: 0 times
// ❌ Without: new function → child re-renders const handleClick = () => doSomething(); // ✅ With: same ref → child skips re-render const handleClick = useCallback( () => doSomething(id), [id] );
useRef

useRef — DOM Access + Persistent Values

Mutable ref that persists across renders WITHOUT triggering re-render when changed.

// DOM ref const inputRef = useRef(null); <input ref={inputRef} /> inputRef.current.focus(); // Persistent value (doesn't cause re-render) const renderCount = useRef(0); renderCount.current++;
🎤 Interview: "useRef has two uses: (1) accessing DOM nodes, and (2) storing mutable values that persist across renders without causing re-renders. Unlike state, changing ref.current doesn't trigger a render."
useReducer

useReducer — Complex State Logic

Alternative to useState for complex state with multiple sub-values or transitions.

🛒 Shopping Cart
Cart is empty
function reducer(state, action) { switch (action.type) { case "ADD": return [...state, action.item]; case "REMOVE": return state.filter(i => i.id !== action.id); default: return state; } } const [state, dispatch] = useReducer(reducer, initialState);
🎤 Interview: "useReducer is better than useState when: (1) state has multiple sub-values, (2) next state depends on previous, or (3) update logic is complex. It's Redux in miniature."
useLayoutEffect

useLayoutEffect vs useEffect

Fires synchronously BEFORE browser paint. Use for DOM measurements to avoid flicker.

📐 Measured Height: 0px

This height was measured in useLayoutEffect before the browser painted. With useEffect, you'd see a flicker as the height updates.

// useEffect: async, after paint (typical) useEffect(() => { /* DOM is painted */ }); // useLayoutEffect: sync, BEFORE paint useLayoutEffect(() => { const height = ref.current.offsetHeight; setHeight(height); // no flicker });
🎤 Interview: "99% of the time use useEffect. Use useLayoutEffect only when you need to measure DOM nodes or mutate the DOM before paint to avoid visual flicker."
useId

useId — SSR-Safe Unique IDs

Generates unique IDs that match between server and client (hydration-safe).

// ❌ Bad in SSR: Math.random() causes hydration mismatch const id = "field-" + Math.random(); // ✅ Good: SSR-safe unique ID const id = useId(); <label htmlFor={id}>Name</label> <input id={id} />
🎤 Interview: "useId solves the hydration mismatch problem when generating unique IDs for accessibility attributes. The server and client generate the same ID, preventing React warnings."
useDeferredValue

useDeferredValue — Defer Non-Urgent Updates

Lets urgent updates (typing) render first, defers expensive updates (filtering 5000 items).

Showing 5000 of 5000 items
Item 1
Item 2
Item 3
Item 4
Item 5
Item 6
Item 7
Item 8
Item 9
Item 10
Item 11
Item 12
Item 13
Item 14
Item 15
Item 16
Item 17
Item 18
Item 19
Item 20
Item 21
Item 22
Item 23
Item 24
Item 25
Item 26
Item 27
Item 28
Item 29
Item 30
Item 31
Item 32
Item 33
Item 34
Item 35
Item 36
Item 37
Item 38
Item 39
Item 40
Item 41
Item 42
Item 43
Item 44
Item 45
Item 46
Item 47
Item 48
Item 49
Item 50
const [input, setInput] = useState(""); const deferred = useDeferredValue(input); // Input updates immediately (stays responsive) // Expensive filter uses deferred value (updates later) const filtered = items.filter(i => i.includes(deferred));
🎤 Interview: "useDeferredValue keeps the UI responsive by deferring expensive computations. The input stays snappy while the heavy filter catches up. It's automatic concurrent rendering."
useTransition

useTransition — Mark State Updates as Non-Urgent

Wraps state updates in startTransition to keep UI responsive during expensive updates.

✅ Rendered 0 items
const [isPending, startTransition] = useTransition(); const handleChange = (value) => { setInput(value); // urgent (input stays responsive) startTransition(() => { setList(expensiveUpdate(value)); // non-urgent }); };
🎤 Interview: "useTransition lets you mark certain state updates as non-urgent. React keeps the UI responsive by prioritizing urgent updates (typing) over non-urgent ones (rendering 10k items). isPending tells you when the transition is active."
useImperativeHandle

useImperativeHandle — Customize Ref Exposure

Customize the instance value that gets exposed to parent when using forwardRef.

const FancyInput = forwardRef((props, ref) => { const inputRef = useRef(); useImperativeHandle(ref, () => ({ focus: () => inputRef.current.focus(), shake: () => /* custom animation */ })); return <input ref={inputRef} />; });
🎤 Interview: "useImperativeHandle customizes the ref value exposed to parents. Instead of exposing the raw DOM node, you expose a custom API (focus, shake). Rarely needed, but useful for reusable components."
Custom Hooks

Custom Hooks — Reusable Logic Patterns

Extract and reuse stateful logic. Must start with "use" to follow Rules of Hooks.

🔍 Debounced Search
Immediate: (empty)
Debounced (500ms): (empty)
// Custom hook: useDebounce function useDebounce(value, delay) { const [debounced, setDebounced] = useState(value); useEffect(() => { const timer = setTimeout(() => setDebounced(value), delay); return () => clearTimeout(timer); }, [value, delay]); return debounced; } // Usage const [search, setSearch] = useState(""); const debouncedSearch = useDebounce(search, 500);
🎤 Interview: "Custom hooks let you extract component logic into reusable functions. Common patterns: useLocalStorage, useFetch, useDebounce, useMediaQuery. They must start with 'use' and can call other hooks."