Server State Belongs in a Cache Layer, Not Global State
You're keeping backend data in global state? That's a bug factory. Every fetched list you copy into a store is a second source of truth that starts rotting the moment it lands — and every AI-generated component that reads it inherits the rot.
Server state is not client state
Client state is data your app owns: the open tab, a form draft, dark mode. It changes only when your code changes it, so a store models it perfectly.
Server state is data you've borrowed. As Dominik Dorfmeister (TanStack Query maintainer) puts it: "your app does not own it… It is the server who owns the data." It lives remotely, arrives asynchronously, can be changed by other people without your knowledge, and can go stale in your app at any moment. The Redux docs themselves draw the line: "data fetching and caching" is really a different set of concerns than "state management."
Treating both the same is the root bug. A store snapshot of /api/todos is correct for exactly as long as nobody else touches the backend.
What a cache layer buys you
Hand-rolling fetch into a store means re-implementing, badly, everything a data-fetching cache layer — TanStack (React) Query, SWR, RTK Query, or equivalents in other ecosystems — ships by default:
| Concern | Hand-rolled store | Cache layer |
|---|---|---|
| Caching | Manual copy, no expiry | Keyed cache; inactive entries garbage-collected (default gcTime: 5 min in TanStack Query) |
| Deduplication | Two components = two requests | Same key = one request, shared result |
| Staleness | Unknown until a bug report | staleTime marks data fresh/stale; stale data refetches in the background |
| Invalidation | Hunt every reducer that holds a copy | One call: invalidateQueries({ queryKey: ['todos'] }) |
| Loading/error state | Boilerplate flags per endpoint | Returned per query, automatically |
Even Redux's own tutorial concedes the boilerplate: async thunk, request, loading flags, pending/fulfilled/rejected handlers — per endpoint — and recommends RTK Query "as the default approach for data fetching in Redux apps."
The refactor in one snippet
// Before: server data smeared into a global store
useEffect(() => {
fetch('/api/todos')
.then(r => r.json())
.then(data => dispatch(setTodos(data))); // now stale forever
}, []);
// After: the cache layer owns it
const { data, isPending, error } = useQuery({
queryKey: ['todos'],
queryFn: () => fetch('/api/todos').then(r => r.json()),
});
// After any mutation, invalidate — every reader refetches:
queryClient.invalidateQueries({ queryKey: ['todos'] });
Ten components can call that same useQuery and the request is deduped to one. Delete the slice, the thunk, and the loading flags.
Why this matters more with AI-generated code
Coding agents pattern-match on your codebase. If server data lives in a store, every generated component invents its own fetch-then-dispatch dance, each with its own staleness bugs — components drift out of sync with the backend and each other. If the convention is "server data comes from the cache layer, keyed by resource," generated code converges on one correct pattern: a query key in, fresh data out. The architecture becomes the guardrail.
Power moves
- Tune
staleTime, leavegcTimealone.staleTimedefaults to 0 (instantly stale). Raise it for data that rarely changes;gcTime(5 min) rarely needs touching. - Invalidate by prefix.
invalidateQueries({ queryKey: ['todos'] })hits['todos'],['todos', {type: 'done'}], all of it. Addexact: trueto narrow, or pass a predicate for custom logic. - Keep the store for what's left. After the migration, global state usually shrinks to genuinely client-owned data — often small enough for context or a tiny store.
- Revalidate on focus. SWR-style libraries refetch on tab focus and network reconnect — free "always fresh" behavior you'd never hand-roll.
Resources
- TanStack Query — Overview (server state motivation)
- TanStack Query — Query invalidation guide
- Practical React Query — TkDodo on server vs client state
- SWR — Getting started (dedup, caching, revalidation)
- Redux Essentials Part 7 — RTK Query basics
Watched the reel? This is the deep-dive for Yeda AI Tips #111. Yeda AI designs, audits, and ships production AI-assisted engineering workflows.