Zustand vs Redux — React State Management

Photo: Mathias Reding / Pexels
State management is the longest-running argument in the React ecosystem, and by 2026 it has a fairly settled answer. Weekly npm downloads put Zustand ahead of Redux for the first time — roughly 41 to 43 million weekly downloads for Zustand against 36 million for Redux, according to npm trends data cited by DEV Community. This article explains why that shift happened, where each library still wins, and how the rest of the state management landscape — Jotai, Valtio, MobX, Recoil, and TanStack Query — fits around them.
The problem React state actually solves
React components own local state through useState. That works fine until two components that are not next to each other need to read or change the same value — a shopping cart icon in the header and a cart page three routes away, for example. At that point you have three real options.
Lift state up. Move the state to the nearest shared parent and pass it down as props. This is the correct first move for small trees, but it degrades once you are three or four levels deep — a pattern developers call "prop drilling," where components in the middle of the tree pass props they never use just to relay them further down.
Use React Context. Context is built into React and solves the drilling problem directly: wrap a subtree in a Provider, and any descendant can read the value with useContext. The tradeoff is a performance footgun — every component that consumes a context re-renders whenever any value in that context changes, even if the component only cares about one field of it.
Reach for a state library. Redux, Zustand, Jotai, MobX, and Valtio all exist to solve the same underlying problem — sharing and updating state across a component tree — with different tradeoffs around boilerplate, mental model, and render performance.
There is a second axis that used to get lumped in with all of this and now, correctly, does not: client state versus server state. Client state is UI-owned data — is a modal open, what theme is active, what's in a multi-step form draft. Server state is data that lives somewhere else — an API, a database — and has its own lifecycle problems: caching, background refetching, deduplication of in-flight requests, and invalidation when the underlying data changes. State libraries handle server state poorly because they were not designed for it. That gap is what TanStack Query (formerly React Query) exists to fill, and recognizing that split is arguably the single biggest architecture decision a 2026 React team makes.
Redux — the library that defined the category
Redux was created by Dan Abramov, originally to demonstrate time-travel debugging in a 2015 conference talk before it became a production library. It borrowed its core idea — a single, immutable state tree updated by pure functions — from the Elm architecture, and it became the dominant state management choice in the React ecosystem from roughly 2016 through 2020.
The original pattern was strict by design:
- Store — one global object that holds the entire application's client state.
- Actions — plain JavaScript objects that describe what happened, not what should change ("USER_LOGGED_IN", not "set user.loggedIn to true").
- Reducers — pure functions that take the current state plus an action and return a new state object. No mutation, no side effects, same input always produces the same output.
- Middleware — the extension point for side effects like API calls, since reducers themselves have to stay pure. Redux Thunk and Redux Saga were the two dominant approaches.
That strictness bought predictability — you could log every action, replay them in order, and reconstruct any past state, which is what made time-travel debugging possible in the Redux DevTools. It also bought a well-documented amount of boilerplate: a typical feature needed action type constants, action creators, a reducer with a switch statement, and manual connect() wiring to components.
Redux Toolkit (RTK), released by the Redux team in 2019, was a direct response to that complaint. createSlice collapses action types, action creators, and the reducer into one function. RTK bundles Immer under the hood, so you can write code that looks like it mutates state (state.value += 1) while Immer produces a correctly immutable update behind the scenes. RTK Query, added later, gives Redux users a built-in data-fetching and caching layer that covers much of what TanStack Query does for non-Redux apps.
// Redux Toolkit
import { createSlice, configureStore } from '@reduxjs/toolkit';
const counter = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
increment: (state) => { state.value += 1; },
},
});
const store = configureStore({ reducer: { counter: counter.reducer } });
// In a component:
const value = useSelector((s) => s.counter.value);
const dispatch = useDispatch();
dispatch(counter.actions.increment());
Redux Toolkit is now the officially recommended way to write Redux — the plain, un-toolkit-ed pattern from 2015 is considered legacy even by the Redux maintainers themselves. Despite that, plain Redux patterns persist in older codebases: one analysis of the State of React 2025 survey found "Redux (plain)" usage at 75.5% among respondents who use any Redux variant at all, down from 80.5% in 2023, while Redux Toolkit usage has held roughly steady around 54%. Read those numbers as measuring existing codebases more than new-project starting points — most greenfield Redux projects in 2026 use RTK from day one.
Zustand — the library that took its place
Zustand (German for "state") was released in 2019 by Daishi Kato, working within the Poimandres open-source collective — the same group behind Jotai, Valtio, react-three-fiber, and several other widely used React tools. Zustand's entire pitch is that most apps do not need Redux's ceremony to get Redux's core benefit — a predictable, centralized store outside the component tree.
The core library is roughly 3 KB gzipped. There is no Provider to wrap your app in, no action creators, no reducers, and no required middleware. A store is just a hook.
// Zustand
import { create } from 'zustand';
const useStore = create((set) => ({
count: 0,
increment: () => set((s) => ({ count: s.count + 1 })),
}));
// In a component:
const count = useStore((s) => s.count);
const increment = useStore((s) => s.increment);
That snippet is close to the entire API surface a typical team uses day to day. Selectors (the (s) => s.count function passed to the hook) are what give Zustand its render performance: a component only re-renders when the specific slice it selected actually changes, not whenever anything in the store changes. This is opt-in rather than automatic — you get the performance benefit by writing narrow selectors, and a team that always does const state = useStore() without a selector loses that advantage and re-renders on every store change, same as an unoptimized Context consumer would.
Zustand also ships an official middleware layer for the features Redux users expect but don't want the whole framework for: persist (localStorage/sessionStorage sync), devtools (hooks into the Redux DevTools browser extension, so you get the same time-travel UI without writing Redux), and immer (mutate-looking updates, same idea as RTK's built-in Immer).
Why did Zustand's downloads pass Redux's?
Three factors converged: the shrinking need for global client state as React Server Components and framework-level data fetching absorbed more of what state libraries used to handle, a strong preference among developers for less boilerplate documented in the State of React 2025 survey, and Zustand's small bundle size mattering more as teams optimized for Core Web Vitals. Zustand usage in that survey rose from roughly 28% to 41% to 50% of respondents across 2023, 2024, and 2025 — a near-doubling in two years, alongside it scoring highest on both satisfaction and retention among state libraries measured.
Side-by-side comparison
| Feature | Redux Toolkit | Zustand |
|---|---|---|
| Bundle size | ~13 KB + RTK Query if used | ~3 KB core |
| Boilerplate | Moderate (createSlice reduced it significantly from plain Redux) |
Minimal — one create() call |
| Provider required | Yes (<Provider store={store}>) |
No |
| Async / side effects | RTK Query, thunks, or sagas | Plain async functions inside the store |
| DevTools | Best-in-class, built in | Via devtools middleware, hooks into same extension |
| Time-travel debugging | Built in | Available via middleware, not automatic |
| TypeScript inference | Good, some manual typing needed | Strong, close to automatic with create<T>() |
| Learning curve | A few hours to get comfortable with the pattern | Minutes — the API is small enough to read in one sitting |
| Best fit | Large teams, strict process, heavy middleware needs | Most apps, especially small-to-mid client state footprints |
Table 1: Redux Toolkit vs. Zustand across the dimensions that actually change a team's day-to-day work.
How the render model actually differs
The comparison table above covers surface features, but the more important difference is architectural: how each library decides when a component re-renders, because that decision is what determines whether your app stays fast as it grows.
Redux (with react-redux) uses useSelector, which subscribes a component to the store and re-renders it whenever the selected slice's reference changes according to a shallow equality check by default. This is essentially the same idea as Zustand's selector pattern — narrow, correctly written selectors avoid unnecessary re-renders in both libraries. The difference in practice is more social than technical: Redux's ecosystem has spent a decade building conventions (memoized selectors via reselect, normalized state shape, the "ducks" file-organization pattern) around getting this right at scale. Zustand's smaller surface area means a team has fewer footguns to hit in the first place, but also fewer battle-tested conventions to lean on once an app gets genuinely large — that gap is filled by community middleware and by teams inventing their own conventions per project.
Context, by contrast, has no selector mechanism at all in the vanilla API. Every consumer of a context re-renders whenever the provided value changes, full stop, unless you manually split contexts by concern or wrap consumers in React.memo. That structural limitation is the real reason Context is a poor Redux/Zustand replacement for anything beyond small, infrequently-changing values like theme or locale — it is not that Context is "worse" in the abstract, it is that it was designed for dependency injection, not high-frequency state updates.
Does Zustand support middleware the way Redux does?
Yes. Zustand ships official middleware for persist (storage sync), devtools (Redux DevTools integration), immer (mutation-style updates), and subscribeWithSelector (fine-grained subscriptions outside React). Custom middleware is also straightforward to write because a Zustand store is just a function, not a framework with a plugin system to learn.
Can Zustand do time-travel debugging like Redux?
Only through the devtools middleware, and it's less turnkey. Redux's action-log architecture makes time-travel a natural fit because every state change is already an explicit, named, serializable event. Zustand can replicate this by logging state snapshots through the same middleware, but it is an added capability rather than a structural guarantee, so teams that depend heavily on replaying production bug reports action-by-action often still prefer Redux.
The rest of the field
Redux and Zustand dominate the conversation, but they are not the only serious options, and picking between them without knowing what else exists is how teams end up re-litigating this decision every 18 months.
- Jotai — Poimandres, same author as Zustand, built around an atomic model. Instead of one big store object, state is composed of small
atomunits, and components subscribe only to the atoms they actually read. This gives you Zustand-style narrow subscriptions by default rather than as an opt-in discipline, which is why Jotai tends to win in apps with deeply nested, fine-grained UI state (complex forms, editors, dashboards with many independent widgets). - Recoil — Meta's atomic library, conceptually close to Jotai. Its GitHub repository was archived by Meta on January 1, 2025, and it is no longer actively maintained; the community has largely converged on Jotai as its successor for new atomic-state projects. Do not start a new project on Recoil in 2026.
- Valtio — Also Poimandres, built on JavaScript
Proxyobjects. You write code that looks like direct mutation (state.user.name = 'New Name'), and Valtio's proxy traps detect the change and notify subscribers. It reads almost like not using a state library at all, which some teams love for ergonomics and others find harder to reason about, since "what changed and why" is less explicit than in Redux's action-log model. - MobX — Predates all of the above (2015), built on the observable pattern: you mark state as observable, mark functions that read it as "reactions," and MobX wires the dependency graph automatically. It embraces mutation and OOP-style classes more than idiomatic React, which is why it's more common in enterprise codebases with a pre-existing OOP culture (often teams with Angular or backend Java/C# experience) than in React-native greenfield projects.
- Context +
useReducer— Nothing to install. Genuinely the right call for state that changes rarely and is read by a small, well-defined slice of the tree — theme, locale, feature flags, the current authenticated user object. The wrong call for anything that updates frequently across a wide tree. - TanStack Query — Not a competitor to any of the above; it solves server state, a different problem entirely. It pairs with Zustand, Redux, Jotai, or nothing, depending on how much genuine client-only state your app still has after you move API data out of your global store and into TanStack Query's cache.
| Library | Mental model | Bundle size (approx.) | Best fit |
|---|---|---|---|
| Redux Toolkit | Reducers + actions | ~13 KB | Large teams, strict process, heavy middleware |
| Zustand | Hooks + plain functions | ~3 KB | Most apps, small-to-mid client state |
| Jotai | Atoms, fine-grained | ~3 KB | Complex nested UI, forms, editors |
| Recoil | Atoms (archived Jan 2025) | ~20 KB | Do not use for new projects |
| Valtio | Proxies, mutable-feeling | ~3 KB | Teams that prefer mutation-style code |
| MobX | Observables, OOP | ~16 KB | Enterprise, class-heavy codebases |
| TanStack Query | Server-state cache | ~13 KB | API data, complementary to the above |
| Context + useReducer | Built into React | 0 KB | Small, infrequently-changing shared state |
Table 2: The full 2026 React state management landscape, not just the two headline options.
A closer look: what a real migration looks like
Comparisons like this one tend to stay abstract, so it's worth walking through what actually happens when a team moves an existing Redux app toward Zustand, because the honest answer is "incrementally, and often only partially."
Most teams do not do a big-bang rewrite. A typical path: new features get built in Zustand from day one, while the existing Redux store keeps running untouched for the modules that already work. Both can coexist in the same React tree indefinitely — Zustand does not require a Provider, so there's no wiring conflict with an existing <Provider store={reduxStore}> higher in the tree. Over time, teams migrate individual slices as they touch that code anyway for unrelated feature work, rather than scheduling a dedicated migration sprint that delivers no user-facing value.
The parts that tend to get migrated first are the simplest: UI-only state like "is the sidebar collapsed" or "which tab is active," because there's no async logic or cross-slice dependency to untangle. The parts that stay in Redux longest are usually the ones wired into RTK Query for server-state caching, because moving those means also standing up a TanStack Query equivalent, which is a second migration bundled into the first. A pragmatic sequencing many teams use: migrate client-only UI state to Zustand first, then separately migrate server state from RTK Query to TanStack Query, treating them as two independent projects rather than one.
One thing that surprises teams doing this for the first time: the DevTools story does not have to regress. Zustand's devtools middleware attaches to the same browser extension Redux uses, so during a mixed-library period, both stores can show up as separate instances in the same DevTools panel, which keeps debugging workflows familiar for a team that has spent years trained on that UI.
Where Zustand wins
Zustand has led the State of React 2025 survey on both satisfaction and retention among state management options, and the reasons line up with what teams report anecdotally.
- A learning curve measured in minutes, not hours. The entire API is
create,set, and a hook call. A new hire can be productive with Zustand on day one without reading documentation beyond the README. - No provider boilerplate. Nothing to wrap the app tree in, nothing that breaks when a store is created outside the render tree (a common source of Redux setup bugs in server-rendered apps).
- A small API surface means fewer ways to do something wrong. Redux's flexibility (multiple middleware options, several ways to structure a reducer, optional normalization) is a source of inconsistency across a codebase if the team doesn't enforce conventions. Zustand's narrowness removes most of those forks in the road.
- Strong TypeScript inference.
create<StoreType>()((set) => ({...}))gives you accurate autocomplete onsetand the returned hook without the manual type wiring Redux sometimes needs for actions and selectors. - Plays cleanly with the Next.js App Router and React Server Components, provided stores are scoped correctly — created per-request on the server, not as a shared module-level singleton, to avoid leaking state between users. This is a real footgun (see below) but a well-documented, solvable one.
- Easy to test. A Zustand store is a plain function you can call directly in a test file without mounting a component or wrapping it in a test
Provider.
Where Redux still wins
None of this makes Redux the wrong choice in every case. Redux Toolkit remains the better call in specific, real situations.
- Large teams that benefit from enforced structure. Redux's opinionated pattern means fifteen engineers across three feature teams tend to write state logic the same way, because the framework doesn't offer many alternate paths. Zustand's flexibility can turn into inconsistency at that scale without a team-imposed style guide filling the gap Redux fills automatically.
- Heavy middleware requirements. Analytics event piping through every state change, audit logging for compliance, custom side-effect orchestration across many async flows — Redux's middleware architecture was built for exactly this, and Redux Saga in particular handles complex async choreography (race conditions, cancellation, sequencing) that would need to be hand-rolled in a Zustand store.
- Time-travel debugging as a core workflow. Teams that regularly reproduce production bugs by replaying a user's exact action sequence get that almost for free in Redux's action-log model.
- An existing Redux codebase. Migration cost is real. "We should use Zustand because it's more popular now" is not, by itself, a reason to rewrite a working, well-understood Redux store — see the migration section above for what that actually costs.
- Teams already committed to RTK Query. If your server-state caching already runs through RTK Query, you already carry Redux in your bundle; layering Zustand on top for the remaining client state is often not worth a second library's cognitive overhead versus just keeping everything in the Redux store you already have.
What people get wrong about this comparison
The most common mistake is treating this as a strict either/or decision, when in reality most 2026 React apps need less global client state than teams assume, because so much of what used to live in Redux is data that came from an API in the first place — and that data belongs in TanStack Query or RTK Query, not in a plain state store at all. Teams that migrate their server data into a proper query cache often find their "state management problem" mostly disappears; what's left is a handful of genuinely client-only values that fit comfortably in Zustand, Jotai, or even Context.
A second common error is assuming smaller bundle size automatically means better runtime performance. Bundle size affects load time, not render performance. A Zustand store built with wide, un-narrowed selectors can re-render just as wastefully as a naive Context implementation; a well-structured Redux store with memoized selectors can outperform a carelessly written Zustand store at scale. The library choice sets the defaults and the guardrails, but a team that ignores selector discipline will hit render-performance problems in either one.
A third mistake, specific to the Next.js App Router era: creating a Zustand store as a module-level singleton (export const useStore = create(...) at the top of a file imported by server-rendered code) can leak state between concurrent requests on the server, because Node.js reuses the module instance across requests unless the store is explicitly scoped per-request via a factory function and a request-scoped Provider. This is a genuine, documented gotcha in React Server Components setups, not a hypothetical one, and it's the single most common Zustand-in-Next.js bug report.
Finally, people sometimes treat "Redux is declining in usage share" as "Redux is dead" — it isn't. Redux Toolkit usage held roughly steady around 54% year over year in the State of React 2025 survey even as Zustand grew fast; that's a large installed base still actively maintaining, not walking away from, existing Redux applications.
A practical recommendation for 2026
For a new React application, this is the boring-but-correct default stack most teams should start from:
- TanStack Query or RTK Query for all server state — anything that originates from an API call, including its caching, refetching, and invalidation logic.
- Zustand for the genuinely client-only global state that remains after server data is properly separated out — UI mode, the current authenticated user object, modal visibility, multi-step form drafts that need to survive route changes.
- Local component state (
useState) for anything that doesn't need to be shared outside a single component or its immediate children. - Redux Toolkit, deliberately, only when a team's size, middleware needs, or existing codebase specifically call for it — not by default, and not because it used to be the default.
- Jotai in place of Zustand specifically when the state is deeply nested and highly granular, such as a form builder or a spreadsheet-like editor where hundreds of independent cells each need their own subscription.
Skip Recoil entirely for new work given its archived status, and treat MobX and Valtio as legitimate but niche picks worth evaluating only if your team's existing patterns (heavy OOP, or a strong preference for mutation-style code) specifically call for them.
For more on the ecosystem this decision sits inside, see our pieces on React Server Components, tRPC and end-to-end typed APIs, and React vs. Vue vs. Svelte.
Frequently Asked Questions
Q: Should I use Zustand or Redux in 2026? A: For most new projects, Zustand is the better default: less boilerplate, no provider setup, and a learning curve measured in minutes. Redux Toolkit remains the right call for large teams that need enforced structure, heavy middleware, or already have production Redux code that isn't worth migrating.
Q: Is Redux dead? A: No. Redux Toolkit usage held roughly steady around 54% year over year in the State of React 2025 survey even as Zustand's usage nearly doubled over two years. Redux has a smaller share of new projects than it used to, but it remains actively maintained and widely used in production.
Q: What is the difference between client state and server state? A: Client state is UI-owned data your app controls directly — open modals, theme, form drafts. Server state is data that originates from an API and has its own lifecycle needs: caching, background refetching, and invalidation when the underlying data changes on the server. TanStack Query and RTK Query handle server state; Zustand, Redux, Jotai, and similar libraries handle client state.
Q: Is Zustand fast enough for large applications?
A: Yes, when selectors are written narrowly. Zustand only re-renders a component when the specific slice it subscribes to changes, the same mechanism Redux's useSelector uses. Performance problems in either library usually trace back to wide, un-narrowed selectors rather than to the library itself.
Q: Do I need a state library at all with the Next.js App Router? A: Often less than you'd think. React Server Components move a lot of data-fetching and rendering logic to the server, reducing how much genuinely global client state a typical app needs. Many App Router projects only need TanStack Query for client-side data fetching plus Zustand for a small amount of UI state.
Q: What about Jotai or Valtio instead of Zustand?
A: Both are legitimate alternatives from the same Poimandres collective that built Zustand. Jotai's atomic model is a better fit for deeply nested, fine-grained state like form builders or editors. Valtio's proxy-based mutation style suits teams that prefer writing state.x = y directly over calling set().
Q: Is Recoil still worth using in 2026? A: No, not for new projects. Meta archived the Recoil GitHub repository on January 1, 2025, and it is no longer actively maintained. Jotai has become the community's de facto successor for atomic state management in React.
Q: Can I use Zustand and Redux in the same app?
A: Yes, and many teams do during a migration. Zustand doesn't require a provider, so it doesn't conflict with an existing Redux <Provider> higher in the tree. A common pattern is building new features in Zustand while leaving working Redux modules untouched until they need to change for other reasons.
Q: What's the biggest Zustand mistake teams make with the Next.js App Router? A: Creating a Zustand store as a module-level singleton in code that runs on the server. Node.js can reuse that module instance across concurrent requests, leaking one user's state into another user's response unless the store is explicitly scoped per-request through a factory function.
Q: Does bundle size difference between Zustand and Redux actually matter? A: It affects initial load time more than runtime performance. Zustand's core is roughly 12 times smaller than Redux Toolkit, which is a meaningful win for Core Web Vitals on content-heavy sites, but it doesn't by itself guarantee faster re-renders — that still depends on how carefully selectors are written in either library.
The takeaway
The Redux-versus-Zustand debate looks like a library preference, but it's really a proxy for a bigger architectural shift: most React apps in 2026 have far less genuine client-only state than teams assumed a few years ago, because server data now belongs in TanStack Query or RTK Query rather than a general-purpose store. Once that data moves out, what's left is small enough that Zustand's minimal API is usually the better fit, while Redux Toolkit earns its keep specifically where scale, middleware, or an existing codebase demand its structure. Pick based on what your state actually is, not on which library used to be the default.
Read next:
Follow new articles
Add SciHub101 to your feed reader to see new science and technology stories.
About the Author
SciHub101 is an independent science and technology publication founded and edited by Buğra Sözeri, a software engineer and app developer based in Türkiye. AI tools assist with drafting and research. Source documentation in the existing archive is uneven, and articles are being reviewed and corrected. The Editorial Team byline identifies editorial responsibility; it does not certify specialist or independent human review. Where we compare products, anything built by our own team is labelled as such — see the editorial guidelines and About page for how corrections and disclosures work.
Related Articles

FastAPI vs Flask vs Django — Python Frameworks
Compare Django, Flask, and FastAPI on speed, features, async support, and use cases. Code examples and a 10-dimension comparison table.


React Hooks Explained — useState and useEffect
React Hooks explained from scratch: how useState and useEffect work, the dependency array, cleanup, Strict Mode, common bugs, and when not to use an effect.

Next.js vs React 2026 — Which Should You Learn First?
Next.js is a framework built on React, not a rival. What each does, Server Components, App vs Pages Router, and when to pick which, per the official docs.

React vs Vue vs Svelte 2026 — Which Framework to Learn?
Compare React, Vue, Svelte side-by-side. Job market demand, learning curve, salary, real-world usage. Which one to choose.

Next.js 15: The Developer's Guide to App Router, Server Components, and Turbopack
Next.js 15 shipped in October 2024 and made the App Router the official default for new projects, stabilized Turbopack for development builds, and introduced async request APIs that changed how developers access cookies, headers, and search parameters. Next.js 16 (October 2025) extended Turbopack to production. This guide covers the concepts, architecture decisions, and practical trade-offs of the Next.js 15 era, which remains the most widely deployed major version.