Vivek Jalondhara. All writing

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),
});
How the example fits together
  1. Server record → Query cache
  2. Open panel → component state
  3. 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.

Related project: SmackDab CRM →

Related posts