Skip to main content

useEffect: My Mental Model

useEffect runs code after a render, in response to something changing (or once on mount).

The mental model: "synchronize this side effect with this state."

useEffect(setup, dependencies?)

The three forms

// 1. Run once on mount (and cleanup on unmount)
useEffect(() => {
const sub = subscribe();
return () => sub.unsubscribe();
}, []);

// 2. Run when specific values change
useEffect(() => {
fetchData(userId);
}, [userId]);

// 3. Run after every render (almost never what you want)
useEffect(() => {
doSomething();
});

When I actually use it

  • On mount setup: connect to a WebSocket, subscribe to an event, initialize a third-party library
  • Sync with external state: re-fetch data when a filter/ID changes, update the document title
  • Cleanup: unsubscribe, clear timers, disconnect

Common mistakes I've made

Infinite loop: forgot to add a stable dependency or included an object/function that recreates every render.

// Bad: `options` is a new object every render
useEffect(() => { fetchData(options) }, [options]);

// Fix: destructure the primitives you actually need
useEffect(() => { fetchData(filter, page) }, [filter, page]);

Missing cleanup: subscriptions and timers that live past the component's unmount.

useEffect(() => {
const timer = setInterval(tick, 1000);
return () => clearInterval(timer); // always clean up
}, []);

Running fetch directly without aborting:

useEffect(() => {
const controller = new AbortController();
fetch(url, { signal: controller.signal }).then(...)
return () => controller.abort();
}, [url]);

My rule

If I'm tempted to write useEffect to sync two pieces of React state with each other — that's usually a sign I need to restructure. Derive, don't sync.

Real example from production:

function ChatRoom({ roomId }) {
const [serverUrl, setServerUrl] = useState('https://localhost:1234');

useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => connection.disconnect();
}, [serverUrl, roomId]);
}