Comparison
How Zustand stacks up against similar libraries
Zustand is one of many state management libraries for React.On this page we will discuss Zustandin comparison to some of these libraries,including Redux, Valtio, Jotai, and Recoil.
Each library has its own strengths and weaknesses,and we will compare key differences and similarities between each.
Redux
State Model (vs Redux)
Conceptually, Zustand and Redux are quite similar,both are based on an immutable state model.However, Redux requires your app to be wrappedin context providers; Zustand does not.
Zustand
import{ create}from'zustand'typeState={ count:number}typeActions={increment:(qty:number)=>voiddecrement:(qty:number)=>void}const useCountStore=create<State& Actions>((set)=>({ count:0,increment:(qty:number)=>set((state)=>({ count: state.count+ qty})),decrement:(qty:number)=>set((state)=>({ count: state.count- qty})),}))import{ create}from'zustand'typeState={ count:number}typeActions={increment:(qty:number)=>voiddecrement:(qty:number)=>void}typeAction={ type:keyof Actions qty:number}constcountReducer=(state: State, action: Action)=>{switch(action.type){case'increment':return{ count: state.count+ action.qty}case'decrement':return{ count: state.count- action.qty}default:return state}}const useCountStore=create<State& Actions>((set)=>({ count:0,dispatch:(action: Action)=>set((state)=>countReducer(state, action)),}))Redux
import{ createStore}from'redux'import{ useSelector, useDispatch}from'react-redux'typeState={ count:number}typeAction={ type:'increment'|'decrement' qty:number}constcountReducer=(state: State, action: Action)=>{switch(action.type){case'increment':return{ count: state.count+ action.qty}case'decrement':return{ count: state.count- action.qty}default:return state}}const countStore=createStore(countReducer)import{ createSlice, configureStore}from'@reduxjs/toolkit'const countSlice=createSlice({ name:'count', initialState:{ value:0}, reducers:{incremented:(state, qty:number)=>{// Redux Toolkit does not mutate the state, it uses the Immer library// behind scenes, allowing us to have something called "draft state". state.value+= qty},decremented:(state, qty:number)=>{ state.value-= qty},},})const countStore=configureStore({ reducer: countSlice.reducer})Render Optimization (vs Redux)
When it comes to render optimizations within your app,there are no major differences in approach between Zustand and Redux.In both libraries it is recommendedthat you manually apply render optimizations by using selectors.
Zustand
import{ create}from'zustand'typeState={ count:number}typeActions={increment:(qty:number)=>voiddecrement:(qty:number)=>void}const useCountStore=create<State& Actions>((set)=>({ count:0,increment:(qty:number)=>set((state)=>({ count: state.count+ qty})),decrement:(qty:number)=>set((state)=>({ count: state.count- qty})),}))constComponent=()=>{const count=useCountStore((state)=> state.count)const increment=useCountStore((state)=> state.increment)const decrement=useCountStore((state)=> state.decrement)// ...}Redux
import{ createStore}from'redux'import{ useSelector, useDispatch}from'react-redux'typeState={ count:number}typeAction={ type:'increment'|'decrement' qty:number}constcountReducer=(state: State, action: Action)=>{switch(action.type){case'increment':return{ count: state.count+ action.qty}case'decrement':return{ count: state.count- action.qty}default:return state}}const countStore=createStore(countReducer)constComponent=()=>{const count=useSelector((state)=> state.count)const dispatch=useDispatch()// ...}import{ useSelector}from'react-redux'importtype{ TypedUseSelectorHook}from'react-redux'import{ createSlice, configureStore}from'@reduxjs/toolkit'const countSlice=createSlice({ name:'count', initialState:{ value:0}, reducers:{incremented:(state, qty:number)=>{// Redux Toolkit does not mutate the state, it uses the Immer library// behind scenes, allowing us to have something called "draft state". state.value+= qty},decremented:(state, qty:number)=>{ state.value-= qty},},})const countStore=configureStore({ reducer: countSlice.reducer})const useAppSelector: TypedUseSelectorHook<typeof countStore.getState>= useSelectorconstuseAppDispatch:()=>typeof countStore.dispatch= useDispatchconstComponent=()=>{const count=useAppSelector((state)=> state.count.value)const dispatch=useAppDispatch()// ...}Valtio
State Model (vs Valtio)
Zustand and Valtio approach state managementin a fundamentally different way.Zustand is based on theimmutable state model,while Valtio is based on themutable state model.
Zustand
import{ create}from'zustand'typeState={ obj:{ count:number}}const store=create<State>(()=>({ obj:{ count:0}}))store.setState((prev)=>({ obj:{ count: prev.obj.count+1}}))Valtio
import{ proxy}from'valtio'const state=proxy({ obj:{ count:0}})state.obj.count+=1Render Optimization (vs Valtio)
The other difference between Zustand and Valtiois Valtio makes render optimizations through property access.However, with Zustand, it is recommended thatyou manually apply render optimizations by using selectors.
Zustand
import{ create}from'zustand'typeState={ count:number}const useCountStore=create<State>(()=>({ count:0,}))constComponent=()=>{const count=useCountStore((state)=> state.count)// ...}Valtio
import{ proxy, useSnapshot}from'valtio'const state=proxy({ count:0,})constComponent=()=>{const{ count}=useSnapshot(state)// ...}Jotai
State Model (vs Jotai)
There is one major difference between Zustand and Jotai.Zustand is a single store,while Jotai consists of primitive atoms that can be composed together.
Zustand
import{ create}from'zustand'typeState={ count:number}typeActions={ updateCount:( countCallback:(count: State['count'])=> State['count'],)=>void}const useCountStore=create<State& Actions>((set)=>({ count:0,updateCount:(countCallback)=>set((state)=>({ count:countCallback(state.count)})),}))Jotai
import{ atom}from'jotai'const countAtom=atom<number>(0)Render Optimization (vs Jotai)
Jotai achieves render optimizations through atom dependency.However, with Zustand it is recommended thatyou manually apply render optimizations by using selectors.
Zustand
import{ create}from'zustand'typeState={ count:number}typeActions={ updateCount:( countCallback:(count: State['count'])=> State['count'],)=>void}const useCountStore=create<State& Actions>((set)=>({ count:0,updateCount:(countCallback)=>set((state)=>({ count:countCallback(state.count)})),}))constComponent=()=>{const count=useCountStore((state)=> state.count)const updateCount=useCountStore((state)=> state.updateCount)// ...}Jotai
import{ atom, useAtom}from'jotai'const countAtom=atom<number>(0)constComponent=()=>{const[count, updateCount]=useAtom(countAtom)// ...}Recoil
State Model (vs Recoil)
The difference between Zustand and Recoilis similar to that between Zustand and Jotai.Recoil depends on atom string keysinstead of atom object referential identities.Additionally, Recoil needs to wrap your app in a context provider.
Zustand
import{ create}from'zustand'typeState={ count:number}typeActions={ setCount:(countCallback:(count: State['count'])=> State['count'])=>void}const useCountStore=create<State& Actions>((set)=>({ count:0,setCount:(countCallback)=>set((state)=>({ count:countCallback(state.count)})),}))Recoil
import{ atom}from'recoil'const count=atom({ key:'count',default:0,})Render Optimization (vs Recoil)
Similar to previous optimization comparisons,Recoil makes render optimizations through atom dependency.Whereas with Zustand, it is recommended thatyou manually apply render optimizations by using selectors.
Zustand
import{ create}from'zustand'typeState={ count:number}typeActions={ setCount:(countCallback:(count: State['count'])=> State['count'])=>void}const useCountStore=create<State& Actions>((set)=>({ count:0,setCount:(countCallback)=>set((state)=>({ count:countCallback(state.count)})),}))constComponent=()=>{const count=useCountStore((state)=> state.count)const setCount=useCountStore((state)=> state.setCount)// ...}Recoil
import{ atom, useRecoilState}from'recoil'const countAtom=atom({ key:'count',default:0,})constComponent=()=>{const[count, setCount]=useRecoilState(countAtom)// ...}