#useEffect
Rendering in React must be pure: a component takes props and state and returns JSX, nothing else. But real apps have to talk to things outside React — set document.title, open a WebSocket, start a timer, subscribe to a store, call a non-React widget. useEffect is the place for that code. React runs it after it has rendered and committed the result to the screen, so the effect sees the up-to-date DOM and doesn't slow down the render itself.
The mental model the React docs push, and the one that prevents most effect bugs: an effect is not "code that runs on mount"; it's a synchronisation. You describe "keep this external thing in sync with these values" (the setup), and "here's how to stop" (the cleanup). React runs setup after the first render, runs cleanup + setup again whenever the values change, and runs cleanup when the component goes away.
From my notes, the shape is:
useEffect(() => {
// Your side effect logic here
return () => {
// Cleanup logic here (optional)
};
}, [dependencies]);My notes listed three "parameters": the effect function, the cleanup function and the dependency array. Correcting my notes: useEffect takes two arguments — the setup function and the optional dependency array. The cleanup isn't a parameter; it's the return value of the setup function. The descriptions themselves were right: the cleanup runs before the effect runs again and when the component unmounts, and the dependency array tells React when to re-run.
import { useEffect, useState } from "react";
function TitleCounter() {
const [count, setCount] = useState(0);
console.log(`render ${count}`);
useEffect(() => {
document.title = `Clicked ${count} times`;
console.log(`effect ${count} (title is now "${document.title}")`);
}, [count]);
return <button onClick={() => setCount(count + 1)}>Clicked {count}</button>;
}
// render 0
// effect 0 (title is now "Clicked 0 times")
// click:
// render 1
// effect 1 (title is now "Clicked 1 times")- First render: the function body runs and logs
render 0. CallinguseEffecthere does not run the effect — it only hands React a function to run later, together with the dependency array[0]. - React commits the
<button>to the DOM. Then, after the browser has had a chance to paint, it runs the effect: the title becomesClicked 0 timesand the effect logs it. Render always comes first, effect second. - A click calls
setCount(1). React re-renders:render 1. This render creates a new effect function — a closure overcount = 1— and new dependencies[1]. - React compares
[1]with the previous[0]; they differ, so after committing it runs the new effect:effect 1. - The effect reads
countfrom its own render, like every function created during rendering. That's why the title is never stale: each render's effect sees that render's values.
Where effects belong in real code: subscriptions (WebSocket, EventSource, browser events, a store), timers, integrating non-React code (a map library, a chart, focus management), and analytics that should fire because a component is shown. Where they don't belong: computing values from props or state, and reacting to user events — see You might not need an effect.