React useEffect for Data Fetching — Patterns and Best Practices
Learn how to fetch data with useEffect in React. Covers cleanup, race conditions, AbortController, dependency arrays, and best practices for async operations.
Detailed Explanation
Data Fetching with useEffect
useEffect is the primary hook for performing side effects in React function components. Data fetching is one of its most common use cases.
Basic Data Fetching Pattern
useEffect(() => {
let ignore = false;
async function fetchData() {
const response = await fetch(\`/api/users/\${userId}\`);
const data = await response.json();
if (!ignore) {
setUser(data);
}
}
fetchData();
return () => {
ignore = true;
};
}, [userId]);
Why the Cleanup Function Matters
Without cleanup, you risk race conditions. If the user navigates away or the userId changes before the fetch completes, you might set state on an unmounted component or display stale data.
Using AbortController
A more robust approach uses AbortController to cancel in-flight requests:
useEffect(() => {
const controller = new AbortController();
fetch(\`/api/users/\${userId}\`, { signal: controller.signal })
.then(r => r.json())
.then(data => setUser(data))
.catch(err => {
if (err.name !== "AbortError") setError(err);
});
return () => controller.abort();
}, [userId]);
Dependency Array Rules
- Empty array
[]: Fetch once on mount (equivalent to componentDidMount) - With dependencies
[userId]: Re-fetch when userId changes - No array: Runs after every render (usually not what you want for fetching)
Common Mistakes
- Passing async directly:
useEffect(async () => ...)returns a Promise, but React expects void or a cleanup function. Wrap the async call inside the effect. - Missing dependencies: Omitting a dependency causes stale data. Always include all reactive values.
- Infinite loops: Setting state that is also a dependency creates an infinite cycle.
Modern Alternatives
For production applications, consider using React Server Components, TanStack Query, SWR, or similar data fetching libraries that handle caching, deduplication, and background refetching automatically.
Use Case
Use useEffect for data fetching when you need to load data from an API on component mount or when a dependency changes. It is essential for any component that displays data from external sources.