Zustand is worth choosing for shared client state when you want explicit subscriptions and a small amount of store code. Keep a working Redux Toolkit setup unless you can name a problem the change would solve. Our source-install attempt stopped after 5 seconds, so the recommendation rests on documented behavior and attributed application reports, alongside that failed setup finding.
What people use Zustand for
Developers in the supplied case studies used Zustand for authentication, interface preferences and reusable selection controls, with different reasons for choosing it.
| Who used it | What they changed | Reported outcome |
|---|---|---|
| The Frontend Undefined author | Replaced Context for authentication and profile storage in the Timo sample app, retaining TanStack Query | Added selectors to stop unnecessary profile-screen renders. Timo review |
| Alexcloudstar | Rewrote the Bored portfolio app with DarkMode and Activity state in one file | Found the store straightforward to write with little boilerplate. Bored account |
| TkDodo and colleagues | Moved a design-system multi-selection control from local state plus Context to Zustand | Reported fixing a performance problem with the same amount of code. Design-system account |
Timo is the most useful example for someone choosing a stack. The author changed the way components accessed authentication and profile data while leaving the existing server-data tool in place. Some shared components depended on Context, so they also needed refactoring into pure components and Context-specific wrappers. The work extended beyond replacing the store declaration.
The selection control explains why you might scope a store to part of an application. TkDodo describes a control that became sluggish with fifty or more items. Multiple instances of that control also needed independent state. His solution shares a stable store instance through Context and lets Zustand handle the subscriptions inside that subtree. Those are his application observations, rather than our benchmark.
What happened when we ran it
Our November 11, 2026 source-install attempt at commit 9fed2eb failed with exit 1 after 5 seconds. The later November 12 hands-on attempt stopped at a safety check requiring a Compose file.
| Recorded check | Result | What it tells a reader |
|---|---|---|
| Source dependency installation | Failed, exit 1, 5 seconds | This checkout did not complete setup in our sandbox |
| Sandbox configuration | 3 CPUs, 8 GB RAM, image label lab-node:22 |
These are configured resources and an image label |
| Install log tail | ERR_UNKNOWN_BUILTIN_MODULE, followed by Node.js v20.20.2 |
The tail identifies the actual Node runtime for the failed step |
| Package-manager stack | Paths under Corepack's pnpm/11.3.0/dist/pnpm.mjs |
The exposed stack runs through pnpm internals |
| Repository scan | 144 files, approximately 11,338 source lines, 1.9 MB | This describes the checked-out repository |
| Repository signals | 8 CI workflow files, a tests directory, workspaces, no Dockerfile | These are structural observations, not successful execution results |
| November 12 hands-on safety step | Failed: no compose.yaml, compose.yml, docker-compose.yaml or docker-compose.yml |
The runner required a Compose file before continuing |
| November 12 teardown | teardown: ok |
The cleanup record reports completion |
The source log shows a package-manager stack ending in an unknown built-in-module error. It does not identify the missing module in the supplied tail. Calling this a Zustand application crash would turn a setup failure into a different finding. The discrepancy between the image label and the printed Node version also matters: lab-node:22 cannot serve as evidence that this step ran on Node 22.
The hands-on safety record labels the repository unsafe because none of the accepted Compose filenames exists. That wording belongs to the runner's deployment rule. Zustand is a library you add to an application, so the rule's refusal is not a security judgment about the library.
The November 12 summary says it was deployed with its own Docker Compose and ports bound to localhost. That conflicts with the failed prerequisite. We treat the explicit failed step as the outcome, rather than claim a successful deployment. The teardown record says cleanup was okay.
Where people get stuck: subscriptions, hydration and store lifetime
The strongest failure reports concern fresh selector results and incorrectly shared stores, with fixes that change how the application uses Zustand.
| Symptom and source | What the thread supports | Practical response |
|---|---|---|
| v5 RC migration crashes, issue #2741 | A fresh array selector produced an update loop, and the reporter confirmed the suggested fix worked | Wrap a grouped selection with useShallow, or select values separately |
| Persisted Next.js UI disagrees with server HTML, issue #938 | The original reporter confirmed a hydration hook solved it, while another participant needed a different workaround | Coordinate persisted-state restoration with the first client render |
| Session values appear in another browser, issue #182 | A maintainer identified the imported store as a singleton and accepted Context as a way to avoid it | Create isolated store instances for request-specific data |
| Derived getter stays stale, issue #108 | Maintainers recommend calculating derived values in components, hooks or selectors | Subscribe to the inputs through a selector instead of expecting MobX-style getters |
The v5 RC story is particularly easy to recognize in an upgrade. A developer selected a search value and its setter by returning a new array. Opening the page produced “Maximum update depth exceeded.” A maintainer identified the new array on each render and suggested useShallow. After trying the grouped wrapper, the reporter said v5 worked. Another participant described the warning “The result of getSnapshot should be cached to avoid an infinite loop” and also reported success with useShallow. RC discussion
That is more actionable than advice to memoize everything. Inspect what your selector returns. A primitive selection and a newly allocated array have different reference behavior. Keep related values grouped when that makes the component clearer, but use the grouping pattern intended for your version.
Hydration needs a separate diagnosis. The initial HTML can describe default state while the browser restores saved state. Issue #938 contains successful mounting or hydration gates, plus a user for whom the earlier suggestions failed. Treat those as application workarounds with different scopes, rather than a universal configuration switch.
Store scope determines who can see session data
A module-level server store can share request-specific session data across clients, as the reporter in issue #182 discovered.
The reporter logged in using one browser and saw the same session data when loading the page in another. The maintainer's explanation was direct: the exported hook was also the store itself, making it a singleton. Later discussion accepted creating a store instance and distributing it with Context to avoid that global singleton. The thread records an architectural workaround, rather than an automatic isolation guarantee. Session discussion
For a reusable dashboard or selection control, the same scoping choice affects correctness even without session data. TkDodo's provider creates the vanilla store once in a component initializer, supplies its initial props, then passes that stable instance through Context. Each provider instance gets its own store. Recreating the store on every render would defeat that design. Scoped-store example
I would make store ownership part of a code review: who creates it, who reads it, and what ends its lifetime? “Global” is a poor default answer for user-specific server data. A small API does not make that ownership decision for you.
More contributors need an agreed async pattern
One multi-product account reports maintenance trouble from inconsistent asynchronous code after initially smooth growth.
The author of Context vs Zustand vs Redux: A Senior Engineer's Story describes colleagues mixing callbacks, promises and custom middleware patterns. The useful lesson is the disagreement between implementations. His approximate component counts describe those projects, not a maximum size Zustand can support. Team account
Before adopting it across a team, agree where fetched data lives, how actions expose errors and who handles cancellation or stale responses. That is my recommendation from the reported maintenance problem. It is much cheaper to settle those conventions while reviewing the first store than after every feature has invented its own approach.
Derived values deserve a convention too. Issue #108 distinguishes a function that reads current state on demand from a component subscription that updates when its inputs change. The maintainer recommends selectors or hooks, with memoization for expensive computations. A function stored in the state object does not by itself express which inputs a consuming component subscribes to. Derived-state discussion
Zustand vs Redux Toolkit, TanStack Query, Context and Jotai
Choose by the state you own and the conventions you need, rather than treating every alternative as a slower version of Zustand.
| Alternative | Reason to choose it | Where Zustand fits |
|---|---|---|
| Redux Toolkit | Keep an established Redux workflow, or use its included RTK Query data-fetching capability | Shared client stores with direct action functions and explicit selectors |
| TanStack Query | Fetch, cache, synchronize and update server state | Client state alongside the server-data cache, as in Timo |
| React Context | Distribute a few rarely changing values without another dependency | Frequently changing shared state with subscriptions chosen by components |
| Jotai | Compose primitive atoms and their dependencies | Keep a store model and select values from it |
Zustand vs Redux Toolkit is a choice about application structure, not a requirement to write switch statements. The official Redux Toolkit introduction presents it as the standard way to write Redux logic and includes optional RTK Query. Zustand's own comparison shows both Redux Toolkit and traditional Redux examples. Compare against the Toolkit code your team would actually write.
Zustand vs TanStack Query usually calls for dividing responsibilities. TanStack's overview describes server-state fetching, caching, synchronization and updates. Timo kept TanStack Query during its Zustand migration. Moving an API response into a store does not settle cache freshness or the behavior after a mutation.
Zustand vs React Context depends on update patterns and scope. The supplied side-project discussion weighs easier shared-state handling against keeping dependencies out of a small project. Context also remains useful for distributing a Zustand store instance. Removing every provider is not a sensible success criterion for scoped components.
For Zustand vs Jotai, the official comparison describes store selection versus composable atoms and atom dependencies. It also contrasts Zustand's immutable updates and manual selectors with Valtio's mutable state and property-access optimization. Those mechanisms help explain preferences without claiming a universal speed winner. Official comparison
What the other reviews get wrong
Several supplied tutorials overstate what Zustand handles automatically, especially subscriptions and server rendering.
Lijo N S's Deep Dive into Zustand describes React Context as its foundation. Zustand's official comparison says a provider is not required. Optional Context integration for store ownership does not make Context the underlying subscription mechanism. Original claim, official comparison
Syncfusion's Redux vs Zustand says components automatically subscribe only to the data they use. That skips the selector. Destructuring a field after subscribing to the whole store does not narrow that subscription. The same article's advice to create stores outside components is too broad: TkDodo shows a store created once inside a provider for isolated instances. Syncfusion article, scoping example
GeeksforGeeks presents SSR integration without the request-isolation and hydration conditions documented in issues #182 and #938. Those conditions belong in the recommendation, especially when authentication data is involved. GeeksforGeeks article
Older grouped-selector examples also need version labels. The v5 RC discussion supplies a confirmed useShallow fix, so a fresh object or array example should not be presented as a version-independent recipe. Likewise, Redux comparisons that rely entirely on hand-written reducers miss what Redux Toolkit supplies today.
The November release changes persistence and DevTools
Zustand v5.1.0 adds PromiseLike handling to persistence and action dispatch from Redux DevTools.
The November 6, 2026 release says storage reads, migrations and migration writes accept PromiseLike results. It also says persist.rehydrate() returns an awaitable native promise, and the DevTools middleware gains an actionCreators option with nested actions and arguments. These are upstream release statements.
The supplied maintainer announcement asks persistence and DevTools users to check the changelog even though typical uses should not break. Maintainer announcement That is a better upgrade habit than carrying middleware assumptions forward from an older tutorial. The repository API records a November 6 push alongside this release, and the RC threads show maintainers addressing migration reports. That combination supports ongoing maintenance, without using stars as a support guarantee.
Questions people ask
Zustand is a selector-based state library with a React hook interface, and its name is a German word for state or condition.
What is Zustand, and what does its name mean in German?
Zustand state management lets components share values and actions through a store. The German noun means “state” or “condition.” The project has English documentation despite the German name. Official introduction, dictionary
How do you pronounce Zustand?
Zustand pronunciation in German is approximately “TSOO-shtahnt.” Wiktionary gives the IPA /ˈt͡suːˌʃtant/ and provides an audio recording. Pronunciation entry
How do I set and get state in a Zustand store?
Create a store, update it through set in an action, and read reactive values with selectors. The README also exposes getState() for reading outside React and setState() for updates. A nonreactive read is different from subscribing a component. For Zustand npm installation, the documented package command is npm install zustand. README
Is Zustand or Redux Toolkit the better choice for my application?
Pick Zustand for direct shared-client-state stores when the team wants to define its conventions. Favor Redux Toolkit when its established workflow or RTK Query is useful. Both use selectors for render optimization, according to Zustand's official comparison. Comparison
What would justify switching an already working Redux Toolkit application to Zustand?
A specific maintenance problem or unwanted subscription behavior is a reason to investigate a change. A working Redux Toolkit app does not need a rewrite simply because Zustand's example is shorter. The supplied Redux discussion includes users who prefer RTK's existing workflow and others who prefer Zustand's setup. User discussion
Should I use Zustand instead of React Context?
Use it when a shared store with selected subscriptions solves your update problem. A few rarely changing Context values can remain simple. You can also use Context to distribute isolated Zustand stores. Scoped-store account
Should I use Zustand, TanStack Query, or both?
Use TanStack Query for server-data caching and Zustand for shared client state when your app needs both. Timo retained TanStack Query while moving authentication and profile storage to Zustand. Timo migration
Can I use Zustand in React Native?
The official docs describe React Native support. During v5 alpha testing, a reporter resolved a Metro module-resolution problem by setting unstable_conditionNames to ['require']. That is a historical workaround for the reported setup, rather than a change every native app needs. Official docs, alpha discussion #2329
What we have not tested yet
Our evidence stops before a successful source installation or a running application, so this review supplies no measured render-speed advantage, build result or passing-test total.
We have not reproduced the case-study migrations, compared alternatives on the same application, tested current React Native integration, exercised Next.js request isolation, or measured hydration behavior on v5.1.0. No dependency or vulnerability audit result is supplied. The install tail cannot establish the exact missing module or a supported-environment fix. The November 12 record contains no completed application task, question-and-answer results or screenshots.
Community outcomes remain their authors' reports. The contrary business-app account in the supplied Reddit evidence describes dissatisfaction followed by a move to Jotai+Bunshi, without comparable measurements or implementation details. Neither that report nor favorable migration stories establishes a universal performance ranking. Discussion
Setup, community support quality and production maturity are unassessed in the scorecard. Documentation receives an editorial 4/5 for covering selection, scoping, middleware and migration choices: on this documentation-only scale, 1 means no usable guidance, 3 means core usage is covered and 5 requires independently reproduced instructions across these paths. Our failed source setup prevents the highest rating. That rating concerns coverage, not application reliability.
Before a team-wide change, take one troublesome shared-state feature and write down its store owner, selectors and server-data boundary. Those decisions are the useful acceptance criteria for adopting Zustand.

