Fetching Data in Effects
Load data from an API inside useEffect and store it in state.
Syntax
useEffect(() => { fetchData(); }, [id]);A classic way to fetch data is inside useEffect. When the component mounts (or a dependency changes), you call the API and save the result in state.
Avoiding race conditions
If the input changes quickly, an older request might resolve after a newer one. Use an ignore flag in the cleanup so a stale response is discarded.
Example
import { useState, useEffect } from 'react';
function User({ id }) {
const [user, setUser] = useState(null);
useEffect(() => {
let ignore = false;
fetch('/api/users/' + id)
.then((r) => r.json())
.then((data) => {
if (!ignore) setUser(data);
});
return () => { ignore = true; };
}, [id]);
return <p>{user ? user.name : 'Loading...'}</p>;
}When to use it
- A user profile page fetches account details inside useEffect when the component mounts and re-fetches automatically whenever the userId prop changes.
- A blog post view cancels the in-flight API call via AbortController when the user navigates to a different post before the first request completes, preventing stale data from appearing.
- A search-results page debounces user input and triggers a fresh fetch inside useEffect each time the debounced query string changes.
More examples
Basic fetch on mount
Fetches data once on mount with an empty dependency array and stores the result in state.
import { useState, useEffect } from 'react';
function PostList() {
const [posts, setPosts] = useState([]);
useEffect(() => {
fetch('/api/posts')
.then(r => r.json())
.then(setPosts);
}, []);
return <ul>{posts.map(p => <li key={p.id}>{p.title}</li>)}</ul>;
}Refetch when dependency changes
Re-runs the effect when userId changes and cleans up with AbortController to avoid race conditions.
import { useState, useEffect } from 'react';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
const controller = new AbortController();
fetch(`/api/users/${userId}`, { signal: controller.signal })
.then(r => r.json())
.then(setUser)
.catch(() => {});
return () => controller.abort();
}, [userId]);
return user ? <h2>{user.name}</h2> : <p>Loading...</p>;
}Race condition fix with ignore flag
Uses an ignore flag in the cleanup to discard results from stale fetches when the query changes rapidly.
import { useState, useEffect } from 'react';
function SearchResults({ query }) {
const [results, setResults] = useState([]);
useEffect(() => {
let ignore = false;
fetch(`/api/search?q=${encodeURIComponent(query)}`)
.then(r => r.json())
.then(data => { if (!ignore) setResults(data); });
return () => { ignore = true; };
}, [query]);
return <ul>{results.map(r => <li key={r.id}>{r.title}</li>)}</ul>;
}
Discussion