Back to blog
·6 min read

React Query vs Redux: When I Use Each

A practical decision guide from production React work — when server state belongs in React Query, when Redux or Zustand still makes sense, and what I avoid.

  • React
  • React Query
  • Redux
  • State Management

One of the most common questions I hear on teams shipping React at scale: should this be React Query or Redux?

The honest answer is that they solve different problems. Confusion usually starts when both get treated as interchangeable "state management" tools.

The split I use in production

React Query (or TanStack Query) is for server state — data that lives on an API, has a cache lifetime, and needs refetching, invalidation, and loading/error states.

Redux or Zustand is for client state — UI decisions, selections, filters, wizard steps, and cross-component state that does not belong on the server.

When teams mix these up, they end up storing API responses in Redux with hand-rolled loading flags. That was the right pattern in 2018. It is rarely the best pattern now.

When React Query is the default

I reach for React Query first when:

  • Data comes from REST or GraphQL and multiple components need it
  • Stale data, background refetch, and cache invalidation matter
  • Pagination, infinite scroll, or dependent queries are involved
  • Optimistic updates need clean rollback on failure

At Bakson, Apollo and mocked providers were part of the testing story for API-driven features. The same principle applies with React Query: let the data layer own fetch status, caching, and retries. Components should consume data, isLoading, and isError — not orchestrate fetch timing themselves.

A useful rule: if deleting your component should not require manually clearing a global store entry, the data probably should not live in Redux.

When Redux still earns its place

Redux is heavier, but it is not obsolete. I still consider it when:

  • Client state is complex, event-driven, and shared across many unrelated features
  • You need predictable state transitions with middleware (logging, analytics, side effects)
  • The team already has a mature Redux architecture and migration cost is high
  • Offline-first or complex sync workflows need a single source of truth on the client

That said, on recent projects I have often chosen Zustand instead of Redux for shared client state. It is less ceremony for the same outcome when your state model is straightforward.

When Zustand is my sweet spot

Zustand worked well when introducing shared state across frontend apps without importing Redux boilerplate. Good fits:

  • Feature-level UI state shared between sibling components
  • Auth-adjacent client flags that are not the session itself
  • Modal stacks, sidebar state, table filters
  • Small global preferences (theme, density, locale)

The key is scope. I keep stores small and named by feature — not one appStore that grows forever.

What I avoid

These patterns consistently create pain on teams I have joined or led:

  • Putting API responses in Redux with custom loading / error fields per slice
  • Using global state for form field values that never leave one page
  • Choosing Redux "because the app is large" without mapping actual state needs
  • Adopting React Query for purely local UI toggles

Large apps do not automatically need Redux. They need clear boundaries.

A simple decision checklist

Ask these in order:

  1. Does this data come from an API? → React Query / Apollo
  2. Is it UI-only and local to one component?useState
  3. Is it shared inside one feature? → Context or small Zustand store
  4. Is it truly global client state with complex transitions? → Redux or Zustand with discipline

If you are unsure between Redux and Zustand, I default to Zustand first. You can always escalate if the state machine genuinely outgrows it.

How this looks in a real codebase

On enterprise apps with multiple frontends, my typical layout:

  • lib/api/ — fetch clients and query keys
  • hooks/queries/ — React Query hooks per domain
  • stores/ — small Zustand stores for client-only state
  • Components — consume hooks, avoid direct fetch

This keeps server cache concerns out of component trees and prevents Redux from becoming a second backend.

Closing thought

React Query vs Redux is not a loyalty contest. It is a boundary decision.

If you are reviewing architecture today, list every piece of state and label it server or client. That exercise alone usually makes the right tool obvious — and saves your team months of unnecessary complexity.

Want the broader picture? Read How I approach building scalable React applications or grab the production checklist.

Designed & built by Nikola Kikanovic·with ❤️ & ☕