Overview
useEffect lets a React component synchronize with systems outside React: data fetching, subscriptions, timers, and direct DOM manipulation. It is also the source of many bugs — infinite loops, stale closures, and memory leaks. This tutorial explains how to use it correctly.
Basic Syntax
import { useEffect } from 'react';
useEffect(() => {
// side effect logic
}, [dependencies]);
The first argument is the effect function. The second argument is the dependency array that controls when the effect runs.
Three Behaviors Based on the Dependency Array
| Dependency array | When the effect runs |
|---|---|
| Omitted entirely | After every render |
[] (empty) | Only after the first render |
[value] | After first render and whenever value changes |
Example: Fetching Data
import { useState, useEffect } from 'react';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
let cancelled = false;
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(data => {
if (!cancelled) setUser(data);
});
return () => {
cancelled = true;
};
}, [userId]);
if (!user) return <p>Loading...</p>;
return <h1>{user.name}</h1>;
}
The cancelled flag prevents setting state on an unmounted component, which avoids React warnings and memory leaks.
Cleanup Functions
Return a function from the effect to clean up subscriptions, timers, or event listeners:
useEffect(() => {
const interval = setInterval(() => {
console.log('tick');
}, 1000);
return () => clearInterval(interval);
}, []);
React runs the cleanup before the next effect execution and when the component unmounts.
Common Bug: Infinite Loop
// BUG: object in dependency array
useEffect(() => {
setCount(count + 1);
}, [count]); // runs forever
// FIX: use functional update and empty dependencies
useEffect(() => {
setCount(c => c + 1);
}, []);
The infinite loop happens because setCount changes count, which triggers the effect again. Use functional updates when the new state depends on the previous state.
When Not to Use useEffect
- Deriving state from props — compute it during render instead.
- Transforming data for rendering — use
useMemo. - Handling user events — put logic in the event handler, not in an effect.
- Fetching data in React 18+ — consider React Query, SWR, or framework-level loaders.
Dependency Array Checklist
| Question | Action |
|---|---|
| Does the effect use a prop or state? | Include it in the dependency array |
| Does the effect use a function defined in the component? | Wrap it in useCallback or move it inside the effect |
| Does the effect use an object or array? | Use a primitive value or memoize the object |
