Introduction
useEffect lets function components perform side effects: operations that reach outside the pure render, such as fetching data, subscribing to events, manually manipulating the DOM, setting timers, or logging. It unifies the behavior previously split across componentDidMount, componentDidUpdate, and componentWillUnmount in class components into a single API driven by a dependency array.
Cricket analogy: useEffect is like the ground staff's off-field duties (covering the pitch, checking the sightscreen, logging weather) that happen alongside the match but aren't part of the actual batting or bowling, unifying what used to be separate pre-match, mid-match, and post-match checklists into one routine.
Syntax
useEffect(() => {
// effect body: runs after the DOM has been updated
return () => {
// optional cleanup: runs before the next effect and on unmount
};
}, [dependency1, dependency2]); // dependency arrayExplanation
The dependency array controls when the effect re-runs. Omit it entirely and the effect runs after every render. Pass an empty array [] and the effect runs only once, after the first render (mount), mimicking componentDidMount. Pass an array with values ([count] or [userId]) and the effect re-runs only when any of those values changes between renders, using a shallow comparison (Object.is) per item; this mimics a filtered componentDidUpdate. If the effect function returns another function, React treats that returned function as a cleanup routine: it runs right before the effect runs again (using the previous render's values) and also runs once when the component unmounts. This is essential for avoiding memory leaks — canceling subscriptions, clearing timers, removing event listeners, and aborting fetch requests. Regarding timing, useEffect runs asynchronously after the browser has painted the updated DOM, so it does not block visual updates; this differs from useLayoutEffect, which fires synchronously before the browser paints, useful for DOM measurements that must happen before the user sees a flicker. A common pitfall is omitting values used inside the effect from the dependency array, which creates stale closures bugs; the exhaustive-deps ESLint rule from eslint-plugin-react-hooks helps catch these.
Cricket analogy: An empty dependency array is like a toss that happens once at the start of the match and never again (mount); a dependency on [score] re-runs the drinks-break announcement only when the score changes; the cleanup is like retiring the old ball before a new one is brought on, and useEffect firing after the crowd sees the replay mirrors async timing versus useLayoutEffect's instant DRS check before broadcast.
Example
function ChatRoom({ roomId }) {
const [messages, setMessages] = useState([]);
useEffect(() => {
const connection = createConnection(roomId);
connection.connect();
connection.on('message', msg => {
setMessages(prev => [...prev, msg]);
});
// Cleanup: disconnect before reconnecting to a new room or on unmount
return () => connection.disconnect();
}, [roomId]); // re-run only when roomId changes
return (
<ul>
{messages.map((m, i) => <li key={i}>{m}</li>)}
</ul>
);
}Output
When ChatRoom first mounts with roomId='general', the effect connects to the 'general' room and starts listening for messages. If the roomId prop changes to 'random', React first calls the cleanup function to disconnect from 'general', then re-runs the effect to connect to 'random'. When ChatRoom unmounts entirely, the cleanup runs one final time to close the connection, preventing a memory leak or messages being processed for an unmounted component.
Cricket analogy: When a broadcaster switches commentary from the Mumbai match to the Chennai match, they first sign off from Mumbai's feed (cleanup) before connecting to Chennai's live feed (re-run effect), and when the broadcast ends entirely, they disconnect from whichever feed is live, exactly like ChatRoom switching from 'general' to 'random' and cleaning up on unmount.
Key Takeaways
- useEffect handles side effects: data fetching, subscriptions, timers, and manual DOM work.
- No dependency array = runs after every render; [] = runs once on mount; [deps] = runs when any dependency changes.
- Returning a function from the effect defines cleanup, which runs before the next effect execution and on unmount.
- useEffect runs asynchronously after paint; useLayoutEffect runs synchronously before paint for cases needing pre-paint DOM measurement.
- Omitting used values from the dependency array causes stale closures; the exhaustive-deps lint rule helps prevent this bug.
Practice what you learned
1. What happens when useEffect is called with an empty dependency array []?
2. What is the purpose of a function returned from inside useEffect?
3. How does useEffect's timing differ from useLayoutEffect?
4. What problem does the ESLint exhaustive-deps rule primarily help prevent?
Was this page helpful?
You May Also Like
The useState Hook
Master useState, the Hook that adds local state to function components, including initial values, updater functions, and functional updates.
Rules of Hooks
Learn the two Rules of Hooks — only call at the top level, only call from React functions — and why they exist.
The useRef Hook
Understand how useRef creates a persistent, mutable reference that survives renders without causing re-renders.
Related Reading
Related Study Notes in Web Development
Browse all study notesWebSockets Study Notes
Web Development · 30 topics
Web DevelopmentWebAssembly Study Notes
WebAssembly · 30 topics
Web DevelopmentgRPC Study Notes
Protocol Buffers · 30 topics
Web DevelopmentSpring Boot Study Notes
Java · 30 topics
Web DevelopmentFlask Study Notes
Python · 30 topics
Web DevelopmentDjango Study Notes
Python · 30 topics