Although Zustand is unopinionated, we recommend patterns inspired by Flux and Redux . If you’re coming from another library, these patterns should feel familiar.
Zustand differs from Flux and Redux in some fundamental ways, so terminology may not perfectly align with other libraries.
Recommended Patterns
Single Store
Your application’s global state should be located in a single Zustand store.
For large applications, Zustand supports splitting the store into slices .
import { create } from 'zustand'
const useStore = create (( set ) => ({
// All your global state in one place
user: null ,
cart: [],
theme: 'light' ,
// ... actions
}))
Use set / setState to Update the Store
Always use set (or setState) to perform updates to your store. This ensures updates are correctly merged and listeners are properly notified.
Good - Using set
Bad - Direct mutation
const useStore = create (( set ) => ({
count: 0 ,
increment : () => set (( state ) => ({ count: state . count + 1 })),
}))
Colocate Store Actions
In Zustand, state can be updated without dispatched actions and reducers. Store actions can be added directly to the store:
const useBoundStore = create (( set ) => ({
storeSliceA: { value: 0 },
storeSliceB: { value: '' },
storeSliceC: { value: [] },
updateX : () => set (( state ) => ({ storeSliceA: { value: state . storeSliceA . value + 1 } })),
updateY : () => set ({ storeSliceB: { value: 'updated' } }),
}))
Redux-Like Patterns
If you prefer Redux-style reducers, you can define a dispatch function:
Manual Dispatch
Redux Middleware
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 })
Use the built-in redux middleware: import { redux } from 'zustand/middleware'
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 initialState = { grumpiness: 0 }
const useReduxStore = create ( redux ( reducer , initialState ))
The redux middleware wires up your reducer, sets initial state, and adds a dispatch function to both the state and the vanilla API.
Comparison with Flux/Redux
Single Store vs Multiple Stores
Flux/Redux : Encourages a single storeZustand : Also recommends a single store, but supports multiple stores if needed// Single store (recommended)
const useStore = create (( set ) => ({ /* all state */ }))
// Multiple stores (if needed)
const useUserStore = create (( set ) => ({ /* user state */ }))
const useCartStore = create (( set ) => ({ /* cart state */ }))
Flux/Redux : Uses action creators and reducersZustand : Actions are just functions in the store// Redux-style
const increment = () => ({ type: 'INCREMENT' })
const reducer = ( state , action ) => {
switch ( action . type ) {
case 'INCREMENT' : return { count: state . count + 1 }
}
}
// Zustand-style
const useStore = create (( set ) => ({
count: 0 ,
increment : () => set (( state ) => ({ count: state . count + 1 })),
}))
Flux/Redux : Requires immutable updates with spread operatorsZustand : Automatically merges top-level state, but you still need to handle nested objects immutably// Both require immutability for nested objects
set (( state ) => ({
nested: { ... state . nested , updated: true }
}))
Side Effects and Async Actions
Another way to update the store is through functions that wrap state updates and handle side effects:
import { create } from 'zustand'
const useStore = create (( set , get ) => ({
user: null ,
loading: false ,
error: null ,
fetchUser : async ( id ) => {
set ({ loading: true , error: null })
try {
const response = await fetch ( `/api/users/ ${ id } ` )
const user = await response . json ()
set ({ user , loading: false })
} catch ( error ) {
set ({ error: error . message , loading: false })
}
},
}))
Best Practices Summary
Single Store Keep global state in one store, use slices for organization
Use set/setState Always update state through set for proper reactivity
Colocate Actions Define actions alongside state for better encapsulation
Handle Side Effects Wrap async operations in actions for centralized logic
Slices Pattern Split large stores into modular slices
No Store Actions Alternative pattern with external actions
Immutable State Understanding state merging and immutability
Redux Middleware Use Redux patterns with middleware