Technology · React
React Lifecycle
Understand React component lifecycle phases and run effects at the right time.
TL;DR
- 01Every component goes through mount, update, and unmount phases.
- 02useEffect with an empty array runs only once on mount.
- 03Return a cleanup function to run code on unmount or before re-run.
Component Lifecycle Phases
- Mount: component is created and inserted into the DOM.
- Update: component re-renders due to state or prop changes.
- Unmount: component is removed from the DOM.
function Component() { // Mount: run once useEffect(() => { console.log("Mounted"); }, []); // Update: run on every render useEffect(() => { console.log("Rendered"); }); // Unmount: cleanup useEffect(() => { return () => console.log("Unmounting"); }, []); } - React 18 Strict Mode mounts, unmounts, then remounts in development.
// In development with Strict Mode, effects run twice // This is intentional — effects must be cleanup-safe - The same component instance updates without dismounting on re-renders.
// Changing props or state updates the component // — it does NOT unmount and remount
Running on Mount
- Run effect once when component mounts with an empty dependency array.
useEffect(() => { fetch('/api/data').then(r => r.json()).then(setData); }, []); - Empty dependency array means run once on mount.
- Set up subscriptions and listeners on mount.
useEffect(() => { const handler = () => setOnline(navigator.onLine); window.addEventListener("online", handler); window.addEventListener("offline", handler); return () => { window.removeEventListener("online", handler); window.removeEventListener("offline", handler); }; }, []); - Log analytics events when a page or component first appears.
useEffect(() => { analytics.track("page_view", { page: "home" }); }, []); - Initialize third-party libraries like maps or charts on mount.
useEffect(() => { const chart = new Chart(canvasRef.current, config); return () => chart.destroy(); }, []);
Running on Update
- Run effect when specific dependencies change.
useEffect(() => { console.log("Count changed:", count); }, [count]); - Runs after the first render and on every dependency change.
- Re-fetch data when a filter or ID changes.
useEffect(() => { setLoading(true); fetchUser(userId).then(setUser).finally(() => setLoading(false)); }, [userId]); - Sync an external system when state changes.
useEffect(() => { document.title = `${unreadCount} new messages`; }, [unreadCount]); - Include all variables used inside the effect in the dependency array.
useEffect(() => { // uses both userId and token — both must be in deps loadProfile(userId, token); }, [userId, token]);
Running on Every Render
- Run effect on every render by omitting the dependency array.
useEffect(() => { console.log("Rendered"); }); - No dependency array means run after every render.
- Can cause performance issues if the effect is expensive.
- Useful for syncing to an external store every time.
useEffect(() => { // Runs every render — only use when every render matters logger.record({ component: "App", renderCount: ++count }); }); - Prefer a specific dependency array to limit when the effect runs.
// Instead of no deps, list what actually needs to trigger the effect useEffect(() => { syncToExternalStore(value); }, [value]); // only syncs when value changes
Cleanup and Unmount
- Return a cleanup function to run before unmount or re-run.
useEffect(() => { const subscription = subscribe(); // Cleanup function runs before unmount or before effect re-runs return () => { subscription.unsubscribe(); }; }, []); - Cleanup runs before unmount or before the effect re-runs.
- Prevents memory leaks and duplicate subscriptions.
- Cancel in-progress fetch requests on cleanup.
useEffect(() => { const controller = new AbortController(); fetch(url, { signal: controller.signal }).then(setData); return () => controller.abort(); }, [url]); - Clear timers in cleanup to prevent stale updates.
useEffect(() => { const id = setTimeout(() => { setDebouncedValue(value); }, 300); return () => clearTimeout(id); // cancel if value changes quickly }, [value]);
Tip: Understand the dependency array — it controls when effects run and is critical for correct behavior.
Warning: Forgetting cleanup functions causes memory leaks — always unsubscribe from subscriptions, remove event listeners, and clear timers.