Everything so far happens either during render (computing JSX) or in response to a user event (a click, a keystroke). Effects are for a third case: code that needs to run because a component rendered — talking to something outside React entirely, like an API, a timer, or the browser's document.
import { useState, useEffect } from 'react';
function PageTitle({ title }) {
useEffect(() => {
document.title = title;
});
return <h1>{title}</h1>;
}With no second argument, this effect runs after every render — usually more often than actually needed.
A second argument — an array of values — tells React to only re-run the effect when one of those values has actually changed since the last render:
useEffect(() => {
document.title = title;
}, [title]); // only re-runs when title changes, not on every render| Dependency array | Runs |
|---|---|
| (none) | After every single render |
| [] | Once, right after the first render, never again |
| [title] | After the first render, and again whenever title changes |
Don't silence the exhaustive-deps warning
ESLint's React rules will flag a dependency array that's missing a value the effect actually uses — this isn't pedantry, it's catching a real bug: the effect would keep using a stale, outdated value from an earlier render instead of the current one. Include everything the effect reads from outside itself.
Returning a function from an effect gives React something to run before the effect runs again, and when the component is removed from the page — the standard place to cancel a timer, close a connection, or remove an event listener:
function Timer() {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
const id = setInterval(() => setSeconds((s) => s + 1), 1000);
return () => clearInterval(id); // cleanup — runs when the component unmounts
}, []);
return <p>{seconds} seconds elapsed</p>;
}Without this cleanup, the interval would keep running forever, even after the Timer component is gone — a genuine memory leak, not just a style issue.
If a value can be calculated directly from props or state during render, calculate it during render — don't reach for useEffect to sync one piece of state from another. The next lesson, Fetching Data, is useEffect's single most common legitimate use.