React useEffect
useEffect lets a function component run side effects — data fetching, subscriptions, timers, manual DOM work — after React commits changes to the screen.
Running Code After Render
useEffect takes a callback function and runs it after React has painted the component to the screen, not during rendering itself. This keeps rendering pure and predictable while giving you a safe place to reach outside React — to the network, the DOM, timers, or browser APIs.
The Dependency Array
The second argument to useEffect, the dependency array, controls how often the effect re-runs. Omit it entirely and the effect runs after every single render. Pass an empty array and it runs exactly once, right after mount. Pass an array of values and it re-runs after mount and again whenever any of those values changes between renders.
- Fetching data from an API when a component mounts or a query parameter changes
- Subscribing to an external event source (WebSocket, DOM event, third-party library)
- Setting up timers or intervals
- Syncing with browser APIs, like setting document.title
- Logging or sending analytics events
Fetching data on mount
import { useState, useEffect } from "react";
function UserList() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
let cancelled = false;
async function loadUsers() {
const response = await fetch("/api/users");
const data = await response.json();
if (!cancelled) {
setUsers(data);
setLoading(false);
}
}
loadUsers();
return () => {
cancelled = true;
};
}, []);
if (loading) return <p>Loading...</p>;
return (
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
export default UserList;
An effect can return a cleanup function. React calls it right before the effect runs again (to tear down the previous run) and one final time when the component unmounts. This is how you cancel subscriptions, clear timers, or ignore results that arrive after a component no longer cares about them.
Interval with cleanup
import { useState, useEffect } from "react";
function Stopwatch() {
const [seconds, setSeconds] = useState(0);
const [running, setRunning] = useState(true);
useEffect(() => {
if (!running) return;
const id = setInterval(() => {
setSeconds((prev) => prev + 1);
}, 1000);
return () => clearInterval(id);
}, [running]);
return (
<div>
<p>{seconds}s</p>
<button onClick={() => setRunning((prev) => !prev)}>
{running ? "Pause" : "Resume"}
</button>
</div>
);
}
export default Stopwatch;
Subscribing to a browser event
import { useState, useEffect } from "react";
function WindowWidth() {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
function handleResize() {
setWidth(window.innerWidth);
}
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);
return <p>Window width: {width}px</p>;
}
export default WindowWidth;
Exercise: React useEffect
With an empty dependency array [], when does the effect run?