The browser's native fetch API loads data over HTTP. Combined with useEffect and useState, you can build loading/error/success UI — though TanStack Query simplifies this for most apps.
fetch + useEffect Pattern
Classic pattern: store data, loading, and error in state; fetch in useEffect when an ID dependency changes; abort on cleanup with AbortController.
Understand this pattern even if you adopt TanStack Query — it clarifies what libraries automate.
fetch resolves on HTTP errors — check response.ok.
Always handle JSON parse errors and network failures.
Pass AbortSignal for cancellable requests.
Prefer TanStack Query for deduplication and cache.
fetch Quick Reference
Common fetch operations in React.
Task
Code
GET JSON
fetch(url).then(r => r.json())
POST JSON
fetch(url, { method, body, headers })
Check status
if (!res.ok) throw ...
Abort
{ signal: controller.signal }
FormData POST
body: new FormData(form)
Headers auth
Authorization: Bearer token
Avoiding Race Conditions
When dependencies change quickly, older responses may arrive after newer ones. AbortController or an ignore flag prevents stale data overwriting fresh results.
When to Migrate to TanStack Query
Repeated fetch effects across components, manual cache invalidation pain, and background refetch needs signal it's time for TanStack Query.
Common Mistakes
Not checking response.ok before parsing JSON.
Missing abort cleanup on dependency change.
Fetching in render body.
Duplicating identical fetches in sibling components.
Key Takeaways
fetch + useEffect + useState is the manual data loading pattern.
Use AbortController for cleanup and race safety.
Always handle loading, error, and success UI states.
TanStack Query replaces most manual fetch effects.
Pro Tip
Extract fetch logic to async functions — test them without React, use them inside effects or query hooks.
You can fetch data with native fetch. Next, compare axios for HTTP client features.