NPM Star
Collections
  1. Home
  2. Compare
  3. redux vs zustand
NPM Compare

Compare NPM packages statistics, trends, and features

CollectionsVS Code extensionChrome extensionTermsPrivacyLinkTreeIndiehackersBig Frontendqiuyumi

Redux vs Zustand: State Management Tools Comparison

Both Redux and Zustand are tools that help manage data (state) in React applications, similar to having a central storage place for all your app's information. Redux is the older, more established option with strict rules and more setup required, while Zustand is newer, simpler, and easier to learn. These tools are often compared because developers need to choose between Redux's robust but complex approach and Zustand's modern, lightweight solution.

State Managementreactstate-managementfrontenddata-flow

Detailed Comparison

Technical Analysis

featureComparison

Redux is a predictable, container-based state management system, while Zustand is a small, fast, and scalable state management solution. Redux has more features, such as middleware and DevTools support, but Zustand is more lightweight and easy to use.

typescriptSupport

Both Redux and Zustand have excellent TypeScript support, with type definitions and examples available.

browserCompatibility

Both libraries are compatible with modern browsers, including Chrome, Firefox, Safari, and Edge.

dependencies

Redux has no dependencies, while Zustand depends on immer, a small immutable data structure library.

performance

Zustand is generally faster and more lightweight than Redux, due to its smaller size and simpler architecture.

Ecosystem Analysis

frameworkCompatibility

Both Redux and Zustand are compatible with popular frameworks like React, Angular, and Vue.js.

communityActivity

Redux has a larger and more active community, with more contributors and maintainers.

documentationQuality

Both libraries have high-quality documentation, with clear examples and guides.

maintenanceStatus

Redux is maintained by the React team, while Zustand is maintained by a smaller team of contributors.

Performance Comparison

bundleSizeAnalysis

Zustand has a smaller bundle size (1.3kb) compared to Redux (2.5kb).

runtimePerformance

Zustand is generally faster than Redux, with faster state updates and rendering.

loadingTime

Zustand has a faster loading time due to its smaller size.

memoryUsage

Zustand uses less memory than Redux, due to its more efficient data structures.

Code Examples

Simple Redux Counter

1import { createStore } from 'redux';
2const counterReducer = (state = 0, action) => {
3  switch (action.type) {
4    case 'INCREMENT':
5      return state + 1;
6    default:
7      return state;
8  }
9};
10const store = createStore(counterReducer);
11store.dispatch({ type: 'INCREMENT' });

This example shows a simple Redux counter, with a reducer function and a store instance.

Simple Zustand Counter

1import create from 'zustand';
2const useStore = create((set) => ({
3  count: 0,
4  increment: () => set({ count: (state) => state.count + 1 }),
5}));
6const { count, increment } = useStore();
7increment();

This example shows a simple Zustand counter, with a hook-based API and automatic state updates.

Recommendation

Summary

Redux is a more powerful and feature-rich state management solution, while Zustand is a lightweight and easy-to-use alternative.

Details

  • Redux is better suited for large, complex applications
  • Zustand is ideal for small to medium-sized applications

Similar Packages

Mobx

90%

A simple state management library that makes it easy to manage application data. It uses observable patterns to automatically update your app when data changes, making it less complicated than Redux.

Great for beginners because it requires less code than Redux and feels more like writing normal JavaScript. You don't need to write actions or reducers - just create stores and update them directly.

State Management

Valtio

90%

A proxy-based state management tool that lets you write state updates like normal JavaScript. It makes your objects reactive automatically.

Similar to Zustand but even simpler to use. Perfect for developers who want their state management to feel like regular JavaScript objects.

State Management

Jotai

85%

A tiny state management library inspired by Recoil. It uses atomic patterns but with a much smaller package size and simpler API.

Great alternative when you want atomic state management but don't need all of Recoil's features. It's super lightweight and easy to learn.

State Management

Recoil

80%

Facebook's state management library built specifically for React. It uses a concept called atoms (pieces of state) and selectors (pieces that depend on atoms) to manage data flow.

Perfect for React developers who want something simpler than Redux but more powerful than useState. It's particularly good at handling complex state relationships and async data.

State Management

XState

70%

A state management library based on state machines. It helps manage complex state logic and transitions in a visual and structured way.

Great for apps with complex state flows and business logic. While different from Redux/Zustand, it's excellent for managing application state that follows specific patterns or rules.

State Management

<a href='https://redux.js.org'><img src='https://camo.githubusercontent.com/f28b5bc7822f1b7bb28a96d8d09e7d79169248fc/687474703a2f2f692e696d6775722e636f6d2f4a65567164514d2e706e67' height='60' alt='Redux Logo' aria-label='redux.js.org' /></a>

Redux is a predictable state container for JavaScript apps.

It helps you write applications that behave consistently, run in different environments (client, server, and native), and are easy to test. On top of that, it provides a great developer experience, such as live code editing combined with a time traveling debugger.

You can use Redux together with React, or with any other view library. The Redux core is tiny (2kB, including dependencies), and has a rich ecosystem of addons.

Redux Toolkit is our official recommended approach for writing Redux logic. It wraps around the Redux core, and contains packages and functions that we think are essential for building a Redux app. Redux Toolkit builds in our suggested best practices, simplifies most Redux tasks, prevents common mistakes, and makes it easier to write Redux applications.

GitHub Workflow Status npm version npm downloads redux channel on discord

Installation

Create a React Redux App

The recommended way to start new apps with React and Redux Toolkit is by using our official Redux Toolkit + TS template for Vite, or by creating a new Next.js project using Next's with-redux template.

Both of these already have Redux Toolkit and React-Redux configured appropriately for that build tool, and come with a small example app that demonstrates how to use several of Redux Toolkit's features.

# Vite with our Redux+TS template # (using the `degit` tool to clone and extract the template) npx degit reduxjs/redux-templates/packages/vite-template-redux my-app # Next.js using the `with-redux` template npx create-next-app --example with-redux my-app

We do not currently have official React Native templates, but recommend these templates for standard React Native and for Expo:

  • https://github.com/rahsheen/react-native-template-redux-typescript
  • https://github.com/rahsheen/expo-template-redux-typescript
npm install @reduxjs/toolkit react-redux

For the Redux core library by itself:

npm install redux

For more details, see the Installation docs page.

Documentation

The Redux core docs are located at https://redux.js.org, and include the full Redux tutorials, as well usage guides on general Redux patterns:

  • Introduction
  • Tutorials
  • Usage Guides
  • FAQ
  • API Reference

The Redux Toolkit docs are available at https://redux-toolkit.js.org, including API references and usage guides for all of the APIs included in Redux Toolkit.

Learn Redux

Redux Essentials Tutorial

The Redux Essentials tutorial is a "top-down" tutorial that teaches "how to use Redux the right way", using our latest recommended APIs and best practices. We recommend starting there.

Redux Fundamentals Tutorial

The Redux Fundamentals tutorial is a "bottom-up" tutorial that teaches "how Redux works" from first principles and without any abstractions, and why standard Redux usage patterns exist.

Help and Discussion

The #redux channel of the Reactiflux Discord community is our official resource for all questions related to learning and using Redux. Reactiflux is a great place to hang out, ask questions, and learn - please come and join us there!

Before Proceeding Further

Redux is a valuable tool for organizing your state, but you should also consider whether it's appropriate for your situation. Please don't use Redux just because someone said you should - instead, please take some time to understand the potential benefits and tradeoffs of using it.

Here are some suggestions on when it makes sense to use Redux:

  • You have reasonable amounts of data changing over time
  • You need a single source of truth for your state
  • You find that keeping all your state in a top-level component is no longer sufficient

Yes, these guidelines are subjective and vague, but this is for a good reason. The point at which you should integrate Redux into your application is different for every user and different for every application.

For more thoughts on how Redux is meant to be used, please see:<br>

  • When (and when not) to reach for Redux
  • You Might Not Need Redux<br>
  • The Tao of Redux, Part 1 - Implementation and Intent<br>
  • The Tao of Redux, Part 2 - Practice and Philosophy
  • Redux FAQ

Basic Example

The whole global state of your app is stored in an object tree inside a single store. The only way to change the state tree is to create an action, an object describing what happened, and dispatch it to the store. To specify how state gets updated in response to an action, you write pure reducer functions that calculate a new state based on the old state and the action.

Redux Toolkit simplifies the process of writing Redux logic and setting up the store. With Redux Toolkit, the basic app logic looks like:

import { createSlice, configureStore } from '@reduxjs/toolkit' const counterSlice = createSlice({ name: 'counter', initialState: { value: 0 }, reducers: { incremented: state => { // Redux Toolkit allows us to write "mutating" logic in reducers. It // doesn't actually mutate the state because it uses the Immer library, // which detects changes to a "draft state" and produces a brand new // immutable state based off those changes state.value += 1 }, decremented: state => { state.value -= 1 } } }) export const { incremented, decremented } = counterSlice.actions const store = configureStore({ reducer: counterSlice.reducer }) // Can still subscribe to the store store.subscribe(() => console.log(store.getState())) // Still pass action objects to `dispatch`, but they're created for us store.dispatch(incremented()) // {value: 1} store.dispatch(incremented()) // {value: 2} store.dispatch(decremented()) // {value: 1}

Redux Toolkit allows us to write shorter logic that's easier to read, while still following the original core Redux behavior and data flow.

Logo

You can find the official logo on GitHub.

Change Log

This project adheres to Semantic Versioning. Every release, along with the migration instructions, is documented on the GitHub Releases page.

License

MIT

<p align="center"> <img src="docs/bear.jpg" /> </p>

Build Status Build Size Version Downloads Discord Shield

A small, fast and scalable bearbones state-management solution using simplified flux principles. Has a comfy API based on hooks, isn't boilerplatey or opinionated.

Don't disregard it because it's cute. It has quite the claws, lots of time was spent dealing with common pitfalls, like the dreaded zombie child problem, react concurrency, and context loss between mixed renderers. It may be the one state-manager in the React space that gets all of these right.

You can try a live demo here.

npm install zustand

:warning: This readme is written for JavaScript users. If you are a TypeScript user, be sure to check out our TypeScript Usage section.

First create a store

Your store is a hook! You can put anything in it: primitives, objects, functions. State has to be updated immutably and the set function merges state to help it.

import { create } from 'zustand' const useBearStore = create((set) => ({ bears: 0, increasePopulation: () => set((state) => ({ bears: state.bears + 1 })), removeAllBears: () => set({ bears: 0 }), }))

Then bind your components, and that's it!

Use the hook anywhere, no providers are needed. Select your state and the component will re-render on changes.

function BearCounter() { const bears = useBearStore((state) => state.bears) return <h1>{bears} around here ...</h1> } function Controls() { const increasePopulation = useBearStore((state) => state.increasePopulation) return <button onClick={increasePopulation}>one up</button> }

Why zustand over redux?

  • Simple and un-opinionated
  • Makes hooks the primary means of consuming state
  • Doesn't wrap your app in context providers
  • Can inform components transiently (without causing render)

Why zustand over context?

  • Less boilerplate
  • Renders components only on changes
  • Centralized, action-based state management

Recipes

Fetching everything

You can, but bear in mind that it will cause the component to update on every state change!

const state = useBearStore()

Selecting multiple state slices

It detects changes with strict-equality (old === new) by default, this is efficient for atomic state picks.

const nuts = useBearStore((state) => state.nuts) const honey = useBearStore((state) => state.honey)

If you want to construct a single object with multiple state-picks inside, similar to redux's mapStateToProps, you can use useShallow to prevent unnecessary rerenders when the selector output does not change according to shallow equal.

import { create } from 'zustand' import { useShallow } from 'zustand/react/shallow' const useBearStore = create((set) => ({ nuts: 0, honey: 0, treats: {}, // ... })) // Object pick, re-renders the component when either state.nuts or state.honey change const { nuts, honey } = useBearStore( useShallow((state) => ({ nuts: state.nuts, honey: state.honey })), ) // Array pick, re-renders the component when either state.nuts or state.honey change const [nuts, honey] = useBearStore( useShallow((state) => [state.nuts, state.honey]), ) // Mapped picks, re-renders the component when state.treats changes in order, count or keys const treats = useBearStore(useShallow((state) => Object.keys(state.treats)))

For more control over re-rendering, you may provide any custom equality function (this example requires the use of createWithEqualityFn).

const treats = useBearStore( (state) => state.treats, (oldTreats, newTreats) => compare(oldTreats, newTreats), )

Overwriting state

The set function has a second argument, false by default. Instead of merging, it will replace the state model. Be careful not to wipe out parts you rely on, like actions.

import omit from 'lodash-es/omit' const useFishStore = create((set) => ({ salmon: 1, tuna: 2, deleteEverything: () => set({}, true), // clears the entire store, actions included deleteTuna: () => set((state) => omit(state, ['tuna']), true), }))

Async actions

Just call set when you're ready, zustand doesn't care if your actions are async or not.

const useFishStore = create((set) => ({ fishies: {}, fetch: async (pond) => { const response = await fetch(pond) set({ fishies: await response.json() }) }, }))

Read from state in actions

set allows fn-updates set(state => result), but you still have access to state outside of it through get.

const useSoundStore = create((set, get) => ({ sound: 'grunt', action: () => { const sound = get().sound ...

Reading/writing state and reacting to changes outside of components

Sometimes you need to access state in a non-reactive way or act upon the store. For these cases, the resulting hook has utility functions attached to its prototype.

:warning: This technique is not recommended for adding state in React Server Components (typically in Next.js 13 and above). It can lead to unexpected bugs and privacy issues for your users. For more details, see #2200.

const useDogStore = create(() => ({ paw: true, snout: true, fur: true })) // Getting non-reactive fresh state const paw = useDogStore.getState().paw // Listening to all changes, fires synchronously on every change const unsub1 = useDogStore.subscribe(console.log) // Updating state, will trigger listeners useDogStore.setState({ paw: false }) // Unsubscribe listeners unsub1() // You can of course use the hook as you always would function Component() { const paw = useDogStore((state) => state.paw) ...

Using subscribe with selector

If you need to subscribe with a selector, subscribeWithSelector middleware will help.

With this middleware subscribe accepts an additional signature:

subscribe(selector, callback, options?: { equalityFn, fireImmediately }): Unsubscribe
import { subscribeWithSelector } from 'zustand/middleware' const useDogStore = create( subscribeWithSelector(() => ({ paw: true, snout: true, fur: true })), ) // Listening to selected changes, in this case when "paw" changes const unsub2 = useDogStore.subscribe((state) => state.paw, console.log) // Subscribe also exposes the previous value const unsub3 = useDogStore.subscribe( (state) => state.paw, (paw, previousPaw) => console.log(paw, previousPaw), ) // Subscribe also supports an optional equality function const unsub4 = useDogStore.subscribe( (state) => [state.paw, state.fur], console.log, { equalityFn: shallow }, ) // Subscribe and fire immediately const unsub5 = useDogStore.subscribe((state) => state.paw, console.log, { fireImmediately: true, })

Using zustand without React

Zustand core can be imported and used without the React dependency. The only difference is that the create function does not return a hook, but the API utilities.

import { createStore } from 'zustand/vanilla' const store = createStore((set) => ...) const { getState, setState, subscribe, getInitialState } = store export default store

You can use a vanilla store with useStore hook available since v4.

import { useStore } from 'zustand' import { vanillaStore } from './vanillaStore' const useBoundStore = (selector) => useStore(vanillaStore, selector)

:warning: Note that middlewares that modify set or get are not applied to getState and setState.

Transient updates (for often occurring state-changes)

The subscribe function allows components to bind to a state-portion without forcing re-render on changes. Best combine it with useEffect for automatic unsubscribe on unmount. This can make a drastic performance impact when you are allowed to mutate the view directly.

const useScratchStore = create((set) => ({ scratches: 0, ... })) const Component = () => { // Fetch initial state const scratchRef = useRef(useScratchStore.getState().scratches) // Connect to the store on mount, disconnect on unmount, catch state-changes in a reference useEffect(() => useScratchStore.subscribe( state => (scratchRef.current = state.scratches) ), []) ...

Sick of reducers and changing nested states? Use Immer!

Reducing nested structures is tiresome. Have you tried immer?

import { produce } from 'immer' const useLushStore = create((set) => ({ lush: { forest: { contains: { a: 'bear' } } }, clearForest: () => set( produce((state) => { state.lush.forest.contains = null }), ), })) const clearForest = useLushStore((state) => state.clearForest) clearForest()

Alternatively, there are some other solutions.

Persist middleware

You can persist your store's data using any kind of storage.

import { create } from 'zustand' import { persist, createJSONStorage } from 'zustand/middleware' const useFishStore = create( persist( (set, get) => ({ fishes: 0, addAFish: () => set({ fishes: get().fishes + 1 }), }), { name: 'food-storage', // name of the item in the storage (must be unique) storage: createJSONStorage(() => sessionStorage), // (optional) by default, 'localStorage' is used }, ), )

See the full documentation for this middleware.

Immer middleware

Immer is available as middleware too.

import { create } from 'zustand' import { immer } from 'zustand/middleware/immer' const useBeeStore = create( immer((set) => ({ bees: 0, addBees: (by) => set((state) => { state.bees += by }), })), )

Can't live without redux-like reducers and action types?

const types = { increase: 'INCREASE', decrease: 'DECREASE' } const reducer = (state, { type, by = 1 }) => { switch (type) { case types.increase: return { grumpiness: state.grumpiness + by } case types.decrease: return { grumpiness: state.grumpiness - by } } } const useGrumpyStore = create((set) => ({ grumpiness: 0, dispatch: (args) => set((state) => reducer(state, args)), })) const dispatch = useGrumpyStore((state) => state.dispatch) dispatch({ type: types.increase, by: 2 })

Or, just use our redux-middleware. It wires up your main-reducer, sets the initial state, and adds a dispatch function to the state itself and the vanilla API.

import { redux } from 'zustand/middleware' const useGrumpyStore = create(redux(reducer, initialState))

Redux devtools

Install the Redux DevTools Chrome extension to use the devtools middleware.

import { devtools } from 'zustand/middleware' // Usage with a plain action store, it will log actions as "setState" const usePlainStore = create(devtools((set) => ...)) // Usage with a redux store, it will log full action types const useReduxStore = create(devtools(redux(reducer, initialState)))

One redux devtools connection for multiple stores

import { devtools } from 'zustand/middleware' // Usage with a plain action store, it will log actions as "setState" const usePlainStore1 = create(devtools((set) => ..., { name, store: storeName1 })) const usePlainStore2 = create(devtools((set) => ..., { name, store: storeName2 })) // Usage with a redux store, it will log full action types const useReduxStore1 = create(devtools(redux(reducer, initialState)), { name, store: storeName3 }) const useReduxStore2 = create(devtools(redux(reducer, initialState)), { name, store: storeName4 })

Assigning different connection names will separate stores in redux devtools. This also helps group different stores into separate redux devtools connections.

devtools takes the store function as its first argument, optionally you can name the store or configure serialize options with a second argument.

Name store: devtools(..., {name: "MyStore"}), which will create a separate instance named "MyStore" in the devtools.

Serialize options: devtools(..., { serialize: { options: true } }).

Logging Actions

devtools will only log actions from each separated store unlike in a typical combined reducers redux store. See an approach to combining stores https://github.com/pmndrs/zustand/issues/163

You can log a specific action type for each set function by passing a third parameter:

const useBearStore = create(devtools((set) => ({ ... eatFish: () => set( (prev) => ({ fishes: prev.fishes > 1 ? prev.fishes - 1 : 0 }), undefined, 'bear/eatFish' ), ...

You can also log the action's type along with its payload:

... addFishes: (count) => set( (prev) => ({ fishes: prev.fishes + count }), undefined, { type: 'bear/addFishes', count, } ), ...

If an action type is not provided, it is defaulted to "anonymous". You can customize this default value by providing an anonymousActionType parameter:

devtools(..., { anonymousActionType: 'unknown', ... })

If you wish to disable devtools (on production for instance). You can customize this setting by providing the enabled parameter:

devtools(..., { enabled: false, ... })

React context

The store created with create doesn't require context providers. In some cases, you may want to use contexts for dependency injection or if you want to initialize your store with props from a component. Because the normal store is a hook, passing it as a normal context value may violate the rules of hooks.

The recommended method available since v4 is to use the vanilla store.

import { createContext, useContext } from 'react' import { createStore, useStore } from 'zustand' const store = createStore(...) // vanilla store without hooks const StoreContext = createContext() const App = () => ( <StoreContext.Provider value={store}> ... </StoreContext.Provider> ) const Component = () => { const store = useContext(StoreContext) const slice = useStore(store, selector) ...

TypeScript Usage

Basic typescript usage doesn't require anything special except for writing create<State>()(...) instead of create(...)...

import { create } from 'zustand' import { devtools, persist } from 'zustand/middleware' import type {} from '@redux-devtools/extension' // required for devtools typing interface BearState { bears: number increase: (by: number) => void } const useBearStore = create<BearState>()( devtools( persist( (set) => ({ bears: 0, increase: (by) => set((state) => ({ bears: state.bears + by })), }), { name: 'bear-storage', }, ), ), )

A more complete TypeScript guide is here.

Best practices

  • You may wonder how to organize your code for better maintenance: Splitting the store into separate slices.
  • Recommended usage for this unopinionated library: Flux inspired practice.
  • Calling actions outside a React event handler in pre-React 18.
  • Testing
  • For more, have a look in the docs folder

Third-Party Libraries

Some users may want to extend Zustand's feature set which can be done using third-party libraries made by the community. For information regarding third-party libraries with Zustand, visit the doc.

Comparison with other libraries

  • Difference between zustand and other state management libraries for React
StarsIssuesVersionUpdatedⓘLast publish dateCreatedⓘPackage creation dateSizeⓘMinified + Gzipped size
R
redux
61,260435.0.113 months ago14 years agoinstall size 1.4 KB
Z
zustand
53,10565.0.5a month ago6 years agoinstall size 588 B

Dependencies Comparison

redux

Dependencies

Dev Dependencies

@babel/core^7.19.0
@types/node^18.7.16
@typescript-eslint/eslint-plugin^6
@typescript-eslint/parser^6
cross-env^7.0.3
esbuild-extra^0.1.3
eslint^8.23.0
eslint-config-react-app^7.0.1
eslint-import-resolver-typescript^3.5.1
eslint-plugin-import^2.26.0
eslint-plugin-react^7.31.8
eslint-plugin-react-hooks^4.6.0
glob^8.0.3
netlify-plugin-cache^1.0.3
prettier^2.7.1
rimraf^3.0.2
rxjs^7.5.6
tsup7.0.0
typescript5.2
vitest^0.34.0

Peer Dependencies

zustand

Dependencies

Dev Dependencies

Peer Dependencies

@types/react>=18.0.0
immer>=9.0.6
react>=18.0.0
use-sync-external-store>=1.2.0

Who's Using These Packages

redux

personal-blog
personal-blog

✍️ 个人技术博客

generator-jhipster-react-native
generator-jhipster-react-native

A React Native blueprint for JHipster

lore
lore

Respectfully opinionated convention-driven framework for building React applications. Built on React, Webpack, Redux, and React Router.

baker
baker

Mobile MVP Toolkit

arranger
arranger

Data portal API and component generation

zustand

giessdenkiez-de
giessdenkiez-de

The consequences of climate change, especially the dry and hot summers, are putting a strain on Berlin's ecosystem. Our urban trees are drying out and suffering long-term damage. Gieß den Kiez is made to enable coordinated citizen participation in the irrigation of urban trees.

esboot
esboot

Front-end infrastructure tool, base react, support webpack/vite/rspack.

airbnb-clone
airbnb-clone

This project is an educational exercise focused on cloning certain aspects of Airbnb. It is intended for educational purposes only.

chat-app-3
chat-app-3
ack-tok
ack-tok

Social media video sharing based (using Next.js, TypeScript, GoogleAuth, Sanity and CSS with Tailwind)