useEffect
useEffect — The Side Effect Hook
Section titled “useEffect — The Side Effect Hook”Introduction
Section titled “Introduction”useEffect performs side effects in functional components — fetching data, subscribing to events, setting timers. It runs after React has committed the output to the DOM.
Why do we need this?
Section titled “Why do we need this?”Components must be pure — they can’t fetch or do I/O during rendering. useEffect runs after render, responds to dep changes, and supports cleanup.
Problem Statement
Section titled “Problem Statement”Fetch data on mount: must run after render, need to clean up if component unmounts before response.
Real World Story
Section titled “Real World Story”Before hooks, side effects were split across componentDidMount, componentDidUpdate, componentWillUnmount. Related logic was scattered. useEffect colocated setup and teardown.
Real World Analogy
Section titled “Real World Analogy”A Cleanup Crew — the chef cooks (renders), then calls “Clean up!” The crew works and leaves. When they leave, they clean up after themselves.
Visual Explanation
Section titled “Visual Explanation”flowchart TD subgraph "Class Lifecycle" Mount[componentDidMount] --> Update[componentDidUpdate] Update --> Unmount[componentWillUnmount] end subgraph "useEffect" Effect[useEffect with []] --> EffectDeps[useEffect with [dep]] EffectDeps --> Cleanup[Cleanup on unmount] endInternal Working
Section titled “Internal Working”Render (return JSX) → Commit (update DOM) → Browser paints → Effects run asynchronously.
sequenceDiagram Component->>React: Return JSX React->>Browser: Commit DOM + Paint React->>Effect: Run effect (async) Effect->>Effect: Return cleanupArchitecture
Section titled “Architecture”flowchart TD Type{What deps?} -->|"[]"| Once[Runs once on mount] Type -->|"[deps]"| Change[Runs on mount + when deps change] Type -->|"none"| Every[Runs every render]Step-by-Step Flow
Section titled “Step-by-Step Flow”flowchart TD S1[1. Render] --> S2[2. Commit DOM] --> S3[3. Paint] --> S4[4. Run effect] S4 --> S5[5. Store cleanup] --> S6[6. Re-render: run previous cleanup] S6 --> S7[7. Run new effect] --> S8[8. Unmount: run final cleanup]Syntax
Section titled “Syntax”useEffect(() => { // Side effect return () => { /* Cleanup */ };}, [dependencies]);Basic Example
Section titled “Basic Example”import { useState, useEffect } from 'react';
export default function UserList() { const [users, setUsers] = useState([]); useEffect(() => { fetch('https://jsonplaceholder.typicode.com/users') .then(r => r.json()) .then(setUsers); }, []); return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;}Intermediate Example
Section titled “Intermediate Example”import { useState, useEffect } from 'react';
export default function WindowSize() { const [size, setSize] = useState({ w: window.innerWidth, h: window.innerHeight }); useEffect(() => { const handler = () => setSize({ w: window.innerWidth, h: window.innerHeight }); window.addEventListener('resize', handler); return () => window.removeEventListener('resize', handler); }, []); return <p>{size.w} x {size.h}</p>;}Advanced Example
Section titled “Advanced Example”import { useState, useEffect } from 'react';
export default function SearchResults({ query }) { const [results, setResults] = useState([]); useEffect(() => { const controller = new AbortController(); fetch(`https://api.example.com/search?q=${query}`, { signal: controller.signal }) .then(r => r.json()).then(d => setResults(d.results)) .catch(e => { if (e.name !== 'AbortError') throw e; }); return () => controller.abort(); }, [query]); return <ul>{results.map(r => <li key={r.id}>{r.title}</li>)}</ul>;}Production Example
Section titled “Production Example”import { useState, useEffect, useRef } from 'react';
function useFetch(url) { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const cache = useRef(new Map());
useEffect(() => { if (!url) { setData(null); setLoading(false); return; } if (cache.current.has(url)) { setData(cache.current.get(url)); setLoading(false); return; } const controller = new AbortController(); let cancelled = false; fetch(url, { signal: controller.signal }) .then(r => { if (!r.ok) throw new Error(`HTTP ${r.status}`); return r.json(); }) .then(d => { if (!cancelled) { cache.current.set(url, d); setData(d); } }) .catch(e => { if (!cancelled && e.name !== 'AbortError') setError(e.message); }) .finally(() => { if (!cancelled) setLoading(false); }); return () => { cancelled = true; controller.abort(); }; }, [url]);
return { data, loading, error };}Folder Structure
Section titled “Folder Structure”src/├── hooks/useFetch.js├── components/UserProfile.jsx└── App.jsxBest Practices
Section titled “Best Practices”💡 StrictMode double-invokes effects in dev to catch missing cleanup.
🚀 Always clean up. Include all deps. Use AbortController. Split unrelated logic.
Common Mistakes
Section titled “Common Mistakes”⚠ Missing deps: useEffect(() => { document.title = count; }, []) — stale title.
⚠ Infinite loop: useEffect(() => { setCount(c+1); }) — no deps.
Performance Notes
Section titled “Performance Notes”⚡ Effects run after paint, don’t block visuals. For pre-paint, use useLayoutEffect.
Accessibility Notes
Section titled “Accessibility Notes”♿ Use aria-live="polite" for dynamic content. role="alert" for errors. aria-busy="true" for loading.
Interview Questions
Section titled “Interview Questions”🎯 Q1: Lifecycle mapping? [] → mount. [dep] → didUpdate. Cleanup → willUnmount.
Q2: Omit deps? Runs every render — usually a bug.
- When does useEffect run? A) During render B) After commit + paint C) Before render D) On unmount → B
- Empty array means? A) Every render B) Once on mount C) Never D) On prop change → B
- When does cleanup run? A) Before first effect B) Before re-run + unmount C) Only unmount → B
- Race condition prevention? A) setTimeout B) useState C) AbortController D) useMemo → C
- Why StrictMode runs twice? A) Slow dev B) Catch missing cleanup C) Performance → B
Practice Exercise
Section titled “Practice Exercise”E1: Countdown 10–0 with setInterval. E2: Online/offline status. E3: Debounced search.
Debugging Exercise
Section titled “Debugging Exercise”Memory leak: setInterval without cleanup. Fix: return () => clearInterval(id).
Real-world Scenario
Section titled “Real-world Scenario”Crypto dash WebSocket — close on unmount, pause on tab switch. Use cleanup + visibilitychange.
Interview Coding Question
Section titled “Interview Coding Question”useInterval(callback, delay) — null delay pauses.
Mini Project
Section titled “Mini Project”Live Polling Dashboard — fetch every 3s, display, pause/resume.
Summary
Section titled “Summary”🧠 useEffect = "After Paint" — runs after UI is on screen.
📖 Effects run after commit. Deps control re-runs. Always clean up. Use AbortController.
Cheat Sheet
Section titled “Cheat Sheet”useEffect(() => { fetch(url); }, []); // mountuseEffect(() => { }, [dep]); // depsuseEffect(() => { const id = setInterval(fn, d); return () => clearInterval(id); }, []);useEffect(() => { const c = new AbortController(); fetch(url, {signal: c.signal}); return () => c.abort(); }, [url]);Related Topics
Section titled “Related Topics”useRef | Custom Hooks | useCallback