Data Fetching
Fetching data sounds simple until you hit the real requirements: caching, loading and error states, refetching on focus, deduping concurrent requests, pagination, and keeping the UI in sync with the server. Hand-rolling all of that with useEffect and useState is where bugs breed.
The key insight is that server data is not your app's state — it's a cache of data that lives elsewhere and can go stale. A query library manages that cache for you, which is why useEffect-based fetching has largely been replaced.
TL;DR
- Use a query library (TanStack Query, SWR) for server state.
- Always handle loading, error, and empty states.
- Tune
staleTime(how long data is fresh) and let the cache refetch. - Use optimistic updates for snappy mutations.
- Don't copy server data into client state — let the cache own it.
Quick Example
A cached query with TanStack Query — deduped, refetched, and cached automatically:
Core Concepts
Server state vs client state
Client state is owned by your app (UI toggles, form input). Server state is a cached copy of remote data with its own concerns — freshness, refetching, invalidation. Mixing them is the most common data-layer mistake. See State Management.
Caching knobs
staleTime— how long fetched data is considered fresh (no refetch).gcTime(cache time) — how long inactive data stays cached before garbage collection.- Refetch triggers — window focus, network reconnect, interval.
The three UI states
Every fetch should render loading, error, and empty branches — not just the happy path:
Patterns
Optimistic updates
Apply the change immediately, roll back on error — the UI feels instant:
Infinite scroll
useInfiniteQuery with a getNextPageParam handles cursor-based pagination and accumulation.
Comparison
Common Mistakes
Fetching in useEffect by hand
Copying server data into client state
FAQ
Why not just use useEffect and fetch?
Because you'd reimplement caching, deduping, refetch-on-focus, retries, and race-condition handling — badly. Query libraries give you all of that declaratively, which is why manual useEffect fetching is now an anti-pattern for server data.
TanStack Query or SWR?
TanStack Query for feature-rich apps (mutations, infinite queries, devtools, fine-grained caching). SWR for simpler, read-heavy apps and tight Next.js integration. Both implement stale-while-revalidate at heart.
What's the difference between staleTime and gcTime?
staleTime is how long data is considered fresh (no background refetch). gcTime is how long unused data stays in the cache before it's garbage-collected. They control freshness and memory respectively.
What are optimistic updates?
Updating the UI immediately on a mutation — before the server responds — then rolling back if it fails. It makes interactions feel instant, at the cost of handling the rollback path.
Related Topics
- State Management — Server vs client state
- React — Where queries run
- REST API Design — The endpoints you fetch from
- Caching — Server-side caching that complements this