useEffect & Lifecycle
Synchronizing components with external systems - data fetching, subscriptions, timers, and the cleanup function explained properly.
What it is, in plain English: useEffect lets a component synchronize with something outside React's control -- fetching data, setting up a subscription, manually changing the DOM, starting a timer. It runs after the component renders. The dependency array controls when it re-runs: empty array means once on mount, no array means every render, a list of values means whenever those values change. The optional cleanup function (returned from the effect) runs before the next effect and when the component unmounts -- essential for preventing leaks.
Doing things outside of rendering
So far every example just returns JSX based on props and state -- pure rendering.
But real apps need to do things React doesn't handle automatically: fetch data from
a server, set up a timer, subscribe to an event. These are called side effects,
and useEffect is how you handle them.
The basic pattern
React.useEffect(() => {
// code that runs AFTER the component renders
console.log("Component rendered!");
});
// Most of the time, you'll provide a dependency array as the second argument:
React.useEffect(() => {
console.log("This runs once, after the first render");
}, []); // empty array = run once, like componentDidMount in old class componentsThe dependency array controls WHEN it runs
// No array -- runs after EVERY render (rarely what you want):
React.useEffect(() => {
console.log("Runs after every single render");
});
// Empty array -- runs ONCE, after the first render only:
React.useEffect(() => {
console.log("Runs once, on mount");
}, []);
// Array with values -- runs after first render AND whenever those values change:
React.useEffect(() => {
console.log(`userId changed to ${userId}`);
}, [userId]);
function Profile({ userId }) {
React.useEffect(() => {
fetchUser(userId).then(setUser);
}, [userId]); // re-fetch whenever userId changes
}Fetching data -- the most common use case
function UserProfile({ userId }) {
const [user, setUser] = React.useState(null);
React.useEffect(() => {
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(data => setUser(data));
}, [userId]); // re-fetch when userId changes
if (!user) return <p>Loading...</p>;
return <p>{user.name}</p>;
}The cleanup function
// If your effect sets something up, it often needs to tear it down.
// Return a function from the effect -- React calls it before the next
// effect run, and when the component unmounts:
function Timer() {
React.useEffect(() => {
const id = setInterval(() => console.log("tick"), 1000);
return () => {
clearInterval(id); // cleanup -- stop the timer
};
}, []);
}
// Without cleanup, the timer keeps running even after the component
// is removed from the page -- a memory leak.
Try It Yourself (JSX)
Official Sources
- React Documentation — Synchronizing with Effects
- React Documentation — useEffect Reference
- React Documentation — You Might Not Need an Effect
- React Documentation — Lifecycle of Reactive Effects
The Codex links only to official documentation. No third-party tutorials.