Chapter 25 β useEffect and the Component Lifecycle
π Definition
useEffect(callback, deps?) runs side effects (data fetching, subscriptions, DOM mutations, timers) after the component renders.
π Explanation
useEffect is React's escape hatch from pure rendering. The callback runs after the DOM has been updated.
Dependency Array
| Form | Runs when⦠|
|---|---|
useEffect(fn) (no array) |
After every render |
useEffect(fn, []) |
Once, after first mount |
useEffect(fn, [a, b]) |
When a or b changes (by Object.is) |
| Returning a function inside | Cleanup β runs before next effect or on unmount |
Lifecycle Map (class β hooks)
| Class | Functional equivalent |
|---|---|
componentDidMount |
useEffect(fn, []) |
componentDidUpdate |
useEffect(fn, [deps]) |
componentWillUnmount |
return () => {} inside effect |
π» Code Example β Mount Only (componentDidMount)
useEffect(() => {
console.log("Mounted once");
}, []);π» Code Example β Cleanup (Unmount)
useEffect(() => {
const id = setInterval(() => console.log("tick"), 1000);
return () => clearInterval(id); // runs on unmount or before next effect
}, []);π» Code Example β Dependent Effect
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
let cancelled = false;
fetch(`/api/users/${userId}`)
.then(r => r.json())
.then(data => { if (!cancelled) setUser(data); });
return () => { cancelled = true; }; // ignore stale response
}, [userId]); // re-run when userId changes
return user ? <h2>{user.name}</h2> : <p>Loadingβ¦</p>;
}π» Code Example β Subscription Pattern
useEffect(() => {
const onResize = () => console.log(window.innerWidth);
window.addEventListener("resize", onResize);
return () => window.removeEventListener("resize", onResize);
}, []);π» Code Example β Common Async Pitfall
// β wrong β useEffect doesn't expect a Promise
useEffect(async () => {
const data = await fetch("/api");
setData(data);
}, []);
// β
right β define an async function inside
useEffect(() => {
async function load() {
const res = await fetch("/api");
setData(await res.json());
}
load();
}, []);π» Code Example β Stale Closure Bug
function Chat({ roomId }) {
const [messages, setMessages] = useState([]);
// β Bug: handler captures the initial `messages = []`
useEffect(() => {
socket.on("message", (msg) => setMessages([...messages, msg])); // stale!
}, []);
// β
Fix: functional update
useEffect(() => {
socket.on("message", (msg) => setMessages(prev => [...prev, msg]));
}, []);
}π» Code Example β useLayoutEffect
import { useLayoutEffect, useRef } from "react";
function Tooltip({ targetRef, children }) {
const tipRef = useRef();
// Runs synchronously AFTER DOM mutation, BEFORE paint
// β no visual flicker
useLayoutEffect(() => {
const { top, left } = targetRef.current.getBoundingClientRect();
tipRef.current.style.top = `${top - 30}px`;
tipRef.current.style.left = `${left}px`;
});
return <div ref={tipRef} className="tooltip">{children}</div>;
}useEffect |
useLayoutEffect |
|
|---|---|---|
| Runs | After paint | After DOM mutation, before paint |
| Blocks paint? | No | Yes |
| Use when | Most cases | You need to measure or mutate layout synchronously |
π» Code Example β Skipping Effect Re-Runs
// β inline object β new reference every render β effect re-runs forever
useEffect(() => {
doSomething(options);
}, [{ x: 1 }]);
// β
memoize the object
const options = useMemo(() => ({ x: 1 }), []);
useEffect(() => {
doSomething(options);
}, [options]);
// β
or extract primitives into deps
useEffect(() => {
doSomething({ x: 1 });
}, []);π Real-World Impact
- Data fetching, websocket subscriptions, third-party DOM integrations.
- Cleanups prevent memory leaks and race conditions on prop changes.
π― Likely Interview Questions
- What does
useEffectdo? - What is the dependency array?
- Difference between
useEffectanduseLayoutEffect? - How do you replicate
componentDidMount/componentDidUpdate/componentWillUnmount? - Why shouldn't you make the effect itself
async? β Effects must return either nothing or a cleanup function. An async function returns a Promise.
β Props vs State | Index | Next: useMemo / useCallback β