useEffect lets a component perform side effects — anything that reaches outside of pure rendering, such as fetching data, subscribing to an event, manually touching the DOM, or setting a timer.
import { useState, useEffect } from 'react';
function StudentProfile({ id }) {
const [student, setStudent] = useState(null);
useEffect(() => {
let ignore = false;
fetch(`/api/students/${id}`)
.then(res => res.json())
.then(data => { if (!ignore) setStudent(data); });
return () => { ignore = true; }; // cleanup
}, [id]); // re-run whenever id changes
if (!student) return <p>Loading...</p>;
return <h2>{student.name}</h2>;
}
The dependency array controls when it runs
useEffect(() => { /* ... */ }); // after every render — rarely what you want
useEffect(() => { /* ... */ }, []); // once, after the first render only
useEffect(() => { /* ... */ }, [id]); // whenever `id` changes
React's ESLint plugin (eslint-plugin-react-hooks) will warn if you use a variable inside the effect but forget to list it as a dependency — trust that warning; missing dependencies are one of the most common sources of bugs.
Cleanup functions
Return a function from the effect to clean up before the next run or when the component unmounts — essential for subscriptions, timers, and event listeners:
useEffect(() => {
const id = setInterval(() => console.log('tick'), 1000);
return () => clearInterval(id); // runs on unmount or before the next effect
}, []);
useEffect(() => {
function handleResize() { console.log(window.innerWidth); }
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
Common mistakes
- Fetching without a cleanup/ignore flag can set state on an unmounted component or with stale data if
idchanges quickly. - Using
useEffectto compute derived state — if a value can be calculated directly during render, just calculate it; don't put it in state + an effect. - Object/array/function dependencies that are recreated every render cause the effect to re-run every time — wrap them in
useMemo/useCallbackor move them inside the effect.