React
TanStack Query vs. Redux: Choosing Where State Lives
By Vivek Jalondhara · 1 min read
Published Sep 2025 · Updated
Most 'state' is really server cache. Separate the two and the hard problem mostly disappears.
The biggest unlock in modern React is recognizing that most state isn't client state — it's a cache of server data. TanStack Query owns that: fetching, caching, invalidation, and background refresh, all with far less code than hand-rolled Redux thunks.
Redux still earns its keep for genuinely global client state — complex UI orchestration, multi-step flows, cross-cutting concerns. The mistake is using it for server data it was never meant to hold.
My default is server cache via Query plus local component state, reaching for a global store only when shared client state truly demands it.
Keep record fetching separate from panel state
In a CRM detail view, the record comes from the server while the open panel is local interaction state. Give those different owners instead of copying the fetched record into another global store.
Illustrative example · TypeScript · Simplified for explanation
const [panel, setPanel] = useState("details");
const record = useQuery({
queryKey: ["record", tenantId, recordId],
queryFn: () => fetchRecord(tenantId, recordId),
});- Server record → Query cache
- Open panel → component state
- Mutation → invalidate affected query
Conceptual flow for the example above.
Tradeoff to keep in mind
A shared client store can still help with cross-screen UI state. A query key must include every parameter that changes the returned data.