React: From Components to Production · Fetching and Caching Server Data
Fetching Data: Basics and Pitfalls
Why fetching in effects is tricky, and how to do it safely when you must.
Fetching in useEffect requires handling loading and error states, race conditions (responses arriving out of order), cancellation, caching, deduplication and refetching. If you do it manually, use an AbortController or an ignore flag in cleanup. In real apps, prefer a data library (TanStack Query, SWR) or your framework's data loading (React Router loaders, Next.js Server Components).
function useUser(id: string) {
const [state, setState] = useState<{ data?: User; error?: Error; loading: boolean }>({ loading: true });
useEffect(() => {
const controller = new AbortController();
setState({ loading: true });
fetch(`/api/users/${id}`, { signal: controller.signal })
.then((r) => (r.ok ? r.json() : Promise.reject(new Error(`HTTP ${r.status}`))))
.then((data) => setState({ data, loading: false }))
.catch((error) => {
if (error.name !== "AbortError") setState({ error, loading: false });
});
return () => controller.abort(); // cancels stale requests when id changes
}, [id]);
return state;
}
Common pitfalls
- Race conditions when the id changes quickly.
- Not checking
response.ok— fetch doesn't reject on 4xx/5xx. - Refetching the same data in many components with no cache.
Key points
- Handle loading, error and races
- fetch doesn't throw on HTTP errors
- Prefer a data library or framework loaders
Further reading: react.dev: Fetching data in effects
Practice questions
What race condition can occur when fetching in an effect keyed by id?
Answer
If id changes from 1 to 2, the response for 1 may arrive after 2's and overwrite it. Abort or ignore stale requests in the effect cleanup.
Does fetch reject on a 404?
Answer
No. fetch only rejects on network errors; you must check response.ok or status to handle HTTP errors.
Open in the app to mark lessons complete, take notes and track your progress.