javascript-today

React State Management in 2026: Why Redux Lost the Default Slot

For a long time, the answer to “how do you manage state in React?” was Redux. Not “maybe Redux,” not “it depends” — just Redux. It was the industry default the way semicolons and var were before the language moved on. You added it to every project, you wrote the boilerplate, and you accepted the overhead as the price of doing things properly.

That consensus has collapsed. In 2026, Redux is not the answer most developers reach for first, and the projects that still lean on it heavily tend to be codebases that started five or more years ago and haven’t had the bandwidth to revisit the architecture. The question worth understanding is not just “what replaced it” but why the shift happened — because the reason is more interesting than “a shinier tool came out.”


What Redux Was Actually Good At

It helps to start with some fairness. Redux solved a real problem. Before hooks, before Context, before any of the current generation of state libraries, React’s built-in primitives for sharing state across a component tree were limited. You either drilled props through five layers of components or you reached for Redux.

Redux gave you a single store as the source of truth, a deterministic update model through pure reducer functions, and the Redux DevTools — time-travel debugging that let you replay every state change in your application. For teams building complex applications with a lot of developers touching the same codebase, that predictability was genuinely valuable.

The problem wasn’t that Redux was bad at what it did. The problem was what it cost, and the fact that what it did was often not what you actually needed.


The Boilerplate Problem

The criticism everyone has heard is that Redux required too much ceremony. This is true and worth being specific about.

To add a single feature — say, loading a list of users from an API — the traditional Redux pattern required:

  • An action type constant
  • An action creator function
  • A reducer with a switch statement handling three cases (loading, success, error)
  • Middleware (thunk or saga) to handle the async call itself
  • A selector to read the data back out of the store
  • Connecting the component to the store

Redux Toolkit addressed the worst of this. createSlice folded the action type, action creator, and reducer into one place. createAsyncThunk handled async flows without needing a separate middleware setup. If you’re on Redux today, RTK is the minimum viable approach — plain Redux without it is a legacy pattern.

But even RTK doesn’t solve the underlying issue, which is that most of what you’re managing with Redux in a typical application isn’t actually client state at all.


The Real Insight: Server State and Client State Are Different Problems

This is the shift that changed everything, and it’s more conceptual than technical.

Server state is data that lives on a server and that your application fetches, displays, and occasionally mutates. User lists, posts, orders, products, messages — the vast majority of what most React applications display. It is inherently async. It goes stale. Multiple parts of your UI might show the same data and need to stay in sync. Users sitting on the page for ten minutes might be looking at data that changed two minutes ago.

Client state is everything else — the state that exists only in your application’s memory and has no server representation. Whether a modal is open. Which tab is active. The current value of a form field before it’s submitted. Whether the sidebar is collapsed.

Redux treated both as the same problem. You put your API responses in the Redux store alongside your UI flags, wrote reducers for both, and managed the whole thing the same way. The result was stores full of isLoading, hasError, lastFetched, and data fields, each one manually managed, each one a source of bugs when the async lifecycle got complicated.

The question “why fetch this again? we already have it in the store” became surprisingly tricky to answer correctly, because “already have it” does not tell you whether the data is fresh.

TanStack Query named this problem clearly and then solved it.


TanStack Query: The Tool That Ended the Server State Debate

TanStack Query (formerly React Query) hit 68% usage among React developers in 2026. That is a remarkable number for a library that is essentially telling you to stop doing something the ecosystem spent years teaching.

What it does: you give it a query key and a fetch function, and it handles everything else.

const { data, isLoading, error } = useQuery({
  queryKey: ['users'],
  fetchFn: () => fetch('/api/users').then(r => r.json()),
})

Behind that single hook is a cache, a background refetch scheduler, a deduplication layer that ensures the same query in ten components fires only one network request, automatic retry with exponential backoff, stale-while-revalidate semantics, and invalidation logic that you trigger explicitly when a mutation changes the underlying data.

The equivalent in Redux — with RTK and createAsyncThunk — is a createSlice with loading/error/data fields, a thunk, a selector, and then you still have to decide when to refetch, how to handle stale data, and what to do when the same data is displayed in two places.

TanStack Query handles all of that by default, and its defaults are correct for most applications. Where you need to deviate — different stale times, optimistic updates, dependent queries — the API is there and it’s composable.

The 4 billion downloads milestone the TanStack ecosystem crossed in 2026 is not hype. It reflects that once developers used it for one project, they brought it to every project.


Zustand: What Fills the Client State Gap

Once you pull server state out of your Redux store and hand it to TanStack Query, what’s left in the store is usually small enough to make you question whether you needed Redux at all.

That’s the opening Zustand occupies. It’s a minimal client-side state library built around a single create function.

import { create } from 'zustand'

const useSidebarStore = create((set) => ({
  isOpen: false,
  toggle: () => set((state) => ({ isOpen: !state.isOpen })),
}))

That’s the entire store. No action types. No reducers. No provider wrapping your application root. No connect(). You call the hook in any component that needs it, select the slice of state you care about, and React re-renders only that component when it changes.

The numbers tell the story clearly: Zustand is 486 bytes gzipped. Redux Toolkit is 13.6 KB gzipped — roughly 28x larger. Zustand pulls around 7 million weekly npm downloads, which has put it at parity with or ahead of Redux in many measures of new project adoption.

The Zustand mental model is close enough to useState that it doesn’t require a mindset shift. The store is just state and functions that update state. TypeScript inference works without explicit type declarations on every action. The DevTools integration is there if you need it. Middleware exists for persistence and immer-style immutable updates if the project calls for it.

Where Zustand wins is any application where you want shared client state without ceremony. Where it’s less clear-cut is a very large codebase with many developers touching the same store and strict requirements around action traceability — that’s where Redux’s explicitness earns its cost.


Jotai: When State Has Complex Dependencies

Jotai takes a different angle. Where Zustand gives you a single store object, Jotai starts from individual atoms — the smallest units of state — and lets you compose them.

import { atom, useAtom } from 'jotai'

const countAtom = atom(0)
const doubledAtom = atom((get) => get(countAtom) * 2)

function Counter() {
  const [count, setCount] = useAtom(countAtom)
  const [doubled] = useAtom(doubledAtom)
  // ...
}

Derived atoms re-compute only when their dependencies change. Components subscribe to individual atoms, so a component reading doubledAtom only re-renders when countAtom changes — not when any other piece of state in the application updates.

This is the right tool when your state has explicit dependency relationships: spreadsheet-like interfaces where one value derives from several others, configuration wizards where later steps depend on earlier answers, or list-based UIs where each row manages independent state via atomFamily. For those patterns, Jotai’s model is cleaner and more explicit than Zustand’s.

For most applications, Zustand is simpler to reach for and easier to onboard new developers to. Jotai’s value becomes clear when you have genuinely complex reactivity requirements.


URL State: The Third Bucket Nobody Talks About Enough

The two-bucket model of server state vs. client state misses a third category that’s easy to overlook: URL state.

Current filters on a product listing page. The active tab. Pagination. Search terms. Whether a modal is open and which item it’s showing. A significant amount of what developers put in Redux or Zustand actually belongs in the URL, because it represents user-navigable application state — state that should survive a page refresh, be shareable via link, and work correctly with the browser’s back button.

Pulling this out of your state library and into the URL (via useSearchParams in React Router or TanStack Router’s type-safe search params) simplifies your store considerably and gives you a better UX for free.

The full picture of what most modern React apps actually need:

State Type Where it lives Tool
Server/async data TanStack Query cache TanStack Query
Shared client UI state In-memory store Zustand
Component-local state Component useState / useReducer
Navigation-relevant state URL Search params / router

That’s the 95% solution. It covers almost every real application without Redux in sight.


Where Redux Still Makes Sense

Redux is not obsolete. There are contexts where it remains the right call, and it’s worth being honest about them.

Large, multi-team codebases — If you have ten developers working on the same application with strict requirements around how state updates flow, Redux’s explicit action model gives you a paper trail that Zustand doesn’t enforce. You can trace exactly which action caused every state change.

Time-travel debugging — Redux DevTools’ time-travel feature is still unmatched. For applications where reproducing complex user interactions is important for debugging, it’s a genuine differentiator.

Already-existing Redux projects — Migrating a large working Redux application to Zustand for the sake of it is not a good use of engineering time. RTK is a significant improvement over legacy Redux and is a reasonable place to stay.

RTK Query — Redux Toolkit ships its own data fetching layer that competes with TanStack Query. For teams already invested in the Redux ecosystem, RTK Query is capable and well-integrated. It’s not as broadly adopted as TanStack Query, but it’s not a second-class option.

The honest framing is that Redux went from being the obvious default to being one legitimate choice among several — chosen deliberately for specific reasons rather than inherited automatically.


The Bottom Line

The reason Redux lost the default slot is not that a shinier library showed up. It’s that the ecosystem got precise about the actual problem.

State management used to mean “a place to put things so components can share them.” That framing lumped together fundamentally different problems — caching remote data, managing UI interactions, persisting navigation state — and then applied one solution to all of them. Redux was that solution, and it served adequately for years.

Once the community named server state as a distinct problem with distinct requirements, tools built specifically for it made the Redux approach to async data look like using a spreadsheet to track a grocery list. TanStack Query handles caching, stale data, background updates, and optimistic mutations in ways that Redux never prioritized because it was never designed for them.

What’s left after server state goes to TanStack Query is usually small enough that Zustand handles it in thirty lines. What’s left after navigation state goes to the URL is smaller still.

Redux set the standard for what predictable, debuggable state management looks like in React. The tools that followed it took that standard seriously and built on top of it. The ecosystem is better for it.


Sources: