Try Live
Add Docs
Rankings
Pricing
Enterprise
Docs
Install
Install
Docs
Pricing
Enterprise
More...
More...
Try Live
Rankings
Add Docs
Zustand
https://github.com/pmndrs/zustand
Admin
Zustand is a small, fast, and scalable state-management solution for React applications, offering a
...
Tokens:
111,499
Snippets:
460
Trust Score:
9.6
Update:
5 days ago
Context
Skills
Chat
Benchmark
65.7
Suggestions
Latest
Show doc for...
Code
Info
Show Results
Context Summary (auto-generated)
Raw
Copy
Link
# Zustand Zustand is a small, fast, and scalable state management library for React applications. It provides a minimalist API based on hooks, eliminating the need for context providers, reducers, or boilerplate code. The library handles common React pitfalls like the zombie child problem, React concurrency issues, and context loss between mixed renderers, making it one of the most robust state managers in the React ecosystem. At its core, Zustand uses a simple `create` function that returns a custom hook for accessing and subscribing to store state. Components only re-render when the specific slice of state they select changes, providing excellent performance out of the box. The library also supports vanilla JavaScript usage without React, middleware composition for features like persistence and devtools integration, and TypeScript with full type inference. ## Core APIs ### create - Create a React Store Hook The `create` function creates a React hook with attached API utilities for state management. It accepts a state creator function that receives `set`, `get`, and `store` arguments, returning an object containing state and actions. ```typescript import { create } from 'zustand' // Define types for TypeScript interface BearState { bears: number increasePopulation: () => void removeAllBears: () => void updateBears: (newBears: number) => void } // Create the store hook const useBearStore = create<BearState>()((set) => ({ bears: 0, increasePopulation: () => set((state) => ({ bears: state.bears + 1 })), removeAllBears: () => set({ bears: 0 }), updateBears: (newBears) => set({ bears: newBears }), })) // Use in React components with selectors function BearCounter() { const bears = useBearStore((state) => state.bears) return <h1>{bears} bears around here...</h1> } function Controls() { const increasePopulation = useBearStore((state) => state.increasePopulation) return <button onClick={increasePopulation}>Add Bear</button> } // Access state outside React components const currentBears = useBearStore.getState().bears useBearStore.setState({ bears: 10 }) // Subscribe to all state changes const unsubscribe = useBearStore.subscribe((state) => { console.log('State changed:', state) }) ``` ### createStore - Create a Vanilla Store The `createStore` function creates a vanilla JavaScript store without React hooks, suitable for use outside React or for sharing state across different frameworks. ```typescript import { createStore } from 'zustand/vanilla' type CounterState = { count: number } type CounterActions = { increment: () => void decrement: () => void reset: () => void } type CounterStore = CounterState & CounterActions // Create vanilla store const counterStore = createStore<CounterStore>()((set) => ({ count: 0, increment: () => set((state) => ({ count: state.count + 1 })), decrement: () => set((state) => ({ count: state.count - 1 })), reset: () => set({ count: 0 }), })) // Use store API directly console.log(counterStore.getState().count) // 0 counterStore.getState().increment() console.log(counterStore.getState().count) // 1 // Subscribe to changes const unsubscribe = counterStore.subscribe((state, prevState) => { console.log(`Count changed from ${prevState.count} to ${state.count}`) }) // Get initial state const initialState = counterStore.getInitialState() ``` ### useStore - Use Vanilla Store in React The `useStore` hook allows you to use a vanilla store created with `createStore` inside React components. ```typescript import { createStore, useStore } from 'zustand' import { createContext, useContext, useState, ReactNode } from 'react' type PositionStore = { position: { x: number; y: number } setPosition: (position: { x: number; y: number }) => void } // Factory function to create stores const createPositionStore = () => { return createStore<PositionStore>()((set) => ({ position: { x: 0, y: 0 }, setPosition: (position) => set({ position }), })) } // Create context for scoped stores const PositionStoreContext = createContext<ReturnType<typeof createPositionStore> | null>(null) // Provider component function PositionStoreProvider({ children }: { children: ReactNode }) { const [store] = useState(() => createPositionStore()) return ( <PositionStoreContext.Provider value={store}> {children} </PositionStoreContext.Provider> ) } // Custom hook to access the store function usePositionStore<U>(selector: (state: PositionStore) => U) { const store = useContext(PositionStoreContext) if (!store) throw new Error('usePositionStore must be used within Provider') return useStore(store, selector) } // Usage in component function MovingDot() { const position = usePositionStore((state) => state.position) const setPosition = usePositionStore((state) => state.setPosition) return ( <div onPointerMove={(e) => setPosition({ x: e.clientX, y: e.clientY })}> <div style={{ transform: `translate(${position.x}px, ${position.y}px)` }} /> </div> ) } ``` ### useShallow - Optimize Re-renders with Shallow Comparison The `useShallow` hook creates a memoized selector that uses shallow comparison to prevent unnecessary re-renders when selecting multiple values. ```typescript import { create } from 'zustand' import { useShallow } from 'zustand/react/shallow' interface UserStore { firstName: string lastName: string email: string age: number updateEmail: (email: string) => void } const useUserStore = create<UserStore>()((set) => ({ firstName: 'John', lastName: 'Doe', email: 'john@example.com', age: 30, updateEmail: (email) => set({ email }), })) // Without useShallow - re-renders on ANY state change function UserInfoBad() { const { firstName, lastName } = useUserStore((state) => ({ firstName: state.firstName, lastName: state.lastName, })) return <p>{firstName} {lastName}</p> } // With useShallow - only re-renders when firstName or lastName change function UserInfoGood() { const { firstName, lastName } = useUserStore( useShallow((state) => ({ firstName: state.firstName, lastName: state.lastName })) ) return <p>{firstName} {lastName}</p> } // Array selection with useShallow function UserNames() { const [firstName, lastName] = useUserStore( useShallow((state) => [state.firstName, state.lastName]) ) return <p>{firstName} {lastName}</p> } // Keys extraction with useShallow function StoreKeys() { const keys = useUserStore(useShallow((state) => Object.keys(state))) return <div>Keys: {keys.join(', ')}</div> } ``` ### shallow - Compare Values with Shallow Equality The `shallow` function performs fast shallow comparisons of top-level properties, useful for custom equality checks in selectors. ```typescript import { shallow } from 'zustand/shallow' // Comparing primitives shallow('hello', 'hello') // true shallow(42, 42) // true // Comparing objects (top-level only) shallow( { name: 'John', age: 30 }, { name: 'John', age: 30 } ) // true // Nested objects fail shallow comparison shallow( { user: { name: 'John' } }, { user: { name: 'John' } } ) // false (different object references) // Comparing Sets and Maps shallow(new Set([1, 2, 3]), new Set([1, 2, 3])) // true shallow( new Map([['a', 1], ['b', 2]]), new Map([['a', 1], ['b', 2]]) ) // true ``` ## Middleware ### persist - Persist State to Storage The `persist` middleware automatically saves and restores store state to/from storage (localStorage by default). ```typescript import { create } from 'zustand' import { persist, createJSONStorage } from 'zustand/middleware' interface SettingsStore { theme: 'light' | 'dark' fontSize: number setTheme: (theme: 'light' | 'dark') => void setFontSize: (size: number) => void } const useSettingsStore = create<SettingsStore>()( persist( (set) => ({ theme: 'light', fontSize: 14, setTheme: (theme) => set({ theme }), setFontSize: (fontSize) => set({ fontSize }), }), { name: 'app-settings', // localStorage key storage: createJSONStorage(() => localStorage), // default partialize: (state) => ({ theme: state.theme, fontSize: state.fontSize }), // only persist these fields version: 1, // schema version for migrations migrate: (persisted: any, version) => { if (version === 0) { // Migrate from version 0 to 1 persisted.fontSize = persisted.fontSize || 14 } return persisted }, onRehydrateStorage: () => (state) => { console.log('Hydration finished:', state) }, } ) ) // Custom storage example (sessionStorage) const useSessionStore = create( persist( (set) => ({ count: 0, increment: () => set((s) => ({ count: s.count + 1 })) }), { name: 'session-store', storage: createJSONStorage(() => sessionStorage), } ) ) // Manual hydration control const useManualHydrationStore = create( persist( (set) => ({ data: null }), { name: 'manual-store', skipHydration: true, // Don't auto-hydrate } ) ) // Later, manually rehydrate useManualHydrationStore.persist.rehydrate() ``` ### devtools - Redux DevTools Integration The `devtools` middleware enables Redux DevTools browser extension support for time-travel debugging. ```typescript import { create } from 'zustand' import { devtools } from 'zustand/middleware' interface TodoStore { todos: Array<{ id: number; text: string; done: boolean }> addTodo: (text: string) => void toggleTodo: (id: number) => void removeTodo: (id: number) => void } const useTodoStore = create<TodoStore>()( devtools( (set) => ({ todos: [], addTodo: (text) => set( (state) => ({ todos: [...state.todos, { id: Date.now(), text, done: false }], }), undefined, // replace flag (undefined = merge) 'todos/addTodo' // action name for DevTools ), toggleTodo: (id) => set( (state) => ({ todos: state.todos.map((todo) => todo.id === id ? { ...todo, done: !todo.done } : todo ), }), undefined, { type: 'todos/toggleTodo', id } // action with payload ), removeTodo: (id) => set( (state) => ({ todos: state.todos.filter((todo) => todo.id !== id), }), undefined, 'todos/removeTodo' ), }), { name: 'TodoStore', // DevTools instance name enabled: process.env.NODE_ENV === 'development', // Disable in production anonymousActionType: 'unknown', // Default action name } ) ) ``` ### immer - Immutable Updates with Mutable Syntax The `immer` middleware allows writing mutable-style updates that are automatically converted to immutable operations. ```typescript import { create } from 'zustand' import { immer } from 'zustand/middleware/immer' interface UserStore { user: { profile: { name: string email: string settings: { notifications: boolean theme: string } } } updateName: (name: string) => void toggleNotifications: () => void updateNestedValue: (path: string[], value: any) => void } const useUserStore = create<UserStore>()( immer((set) => ({ user: { profile: { name: 'John', email: 'john@example.com', settings: { notifications: true, theme: 'light', }, }, }, // Mutable-style updates (converted to immutable by Immer) updateName: (name) => set((state) => { state.user.profile.name = name // Direct mutation! }), toggleNotifications: () => set((state) => { state.user.profile.settings.notifications = !state.user.profile.settings.notifications }), updateNestedValue: (path, value) => set((state) => { let current: any = state for (let i = 0; i < path.length - 1; i++) { current = current[path[i]] } current[path[path.length - 1]] = value }), })) ) ``` ### subscribeWithSelector - Granular Subscriptions The `subscribeWithSelector` middleware enables subscribing to specific state slices outside of React components. ```typescript import { createStore } from 'zustand/vanilla' import { subscribeWithSelector } from 'zustand/middleware' type GameStore = { score: number level: number lives: number addScore: (points: number) => void nextLevel: () => void loseLife: () => void } const gameStore = createStore<GameStore>()( subscribeWithSelector((set) => ({ score: 0, level: 1, lives: 3, addScore: (points) => set((state) => ({ score: state.score + points })), nextLevel: () => set((state) => ({ level: state.level + 1 })), loseLife: () => set((state) => ({ lives: state.lives - 1 })), })) ) // Subscribe to specific state slice const unsubScore = gameStore.subscribe( (state) => state.score, (score, prevScore) => { console.log(`Score changed: ${prevScore} -> ${score}`) if (score >= 1000) console.log('High score!') } ) // Subscribe with equality function const unsubLives = gameStore.subscribe( (state) => state.lives, (lives) => { if (lives === 0) console.log('Game Over!') }, { equalityFn: Object.is } ) // Fire immediately on subscribe gameStore.subscribe( (state) => state.level, (level) => console.log(`Current level: ${level}`), { fireImmediately: true } ) ``` ## Advanced Patterns ### Slices Pattern - Modular Store Organization Split large stores into smaller, reusable slices for better organization and maintainability. ```typescript import { create, StateCreator } from 'zustand' // Define slice types interface BearSlice { bears: number addBear: () => void } interface FishSlice { fishes: number addFish: () => void } interface SharedSlice { addBearAndFish: () => void } type BoundStore = BearSlice & FishSlice & SharedSlice // Create individual slices const createBearSlice: StateCreator<BoundStore, [], [], BearSlice> = (set) => ({ bears: 0, addBear: () => set((state) => ({ bears: state.bears + 1 })), }) const createFishSlice: StateCreator<BoundStore, [], [], FishSlice> = (set) => ({ fishes: 0, addFish: () => set((state) => ({ fishes: state.fishes + 1 })), }) // Slice that uses other slices const createSharedSlice: StateCreator<BoundStore, [], [], SharedSlice> = (set, get) => ({ addBearAndFish: () => { get().addBear() get().addFish() }, }) // Combine slices into bound store const useBoundStore = create<BoundStore>()((...args) => ({ ...createBearSlice(...args), ...createFishSlice(...args), ...createSharedSlice(...args), })) // Usage function App() { const bears = useBoundStore((state) => state.bears) const fishes = useBoundStore((state) => state.fishes) const addBearAndFish = useBoundStore((state) => state.addBearAndFish) return ( <div> <p>Bears: {bears}, Fishes: {fishes}</p> <button onClick={addBearAndFish}>Add Both</button> </div> ) } ``` ### Async Actions Zustand handles async operations naturally - just call `set` when the async operation completes. ```typescript import { create } from 'zustand' interface DataStore { data: any | null loading: boolean error: string | null fetchData: (url: string) => Promise<void> reset: () => void } const useDataStore = create<DataStore>()((set, get) => ({ data: null, loading: false, error: null, fetchData: async (url) => { set({ loading: true, error: null }) try { const response = await fetch(url) if (!response.ok) throw new Error(`HTTP ${response.status}`) const data = await response.json() set({ data, loading: false }) } catch (error) { set({ error: (error as Error).message, loading: false }) } }, reset: () => set({ data: null, loading: false, error: null }), })) // Usage with loading and error states function DataComponent() { const { data, loading, error, fetchData } = useDataStore() if (loading) return <div>Loading...</div> if (error) return <div>Error: {error}</div> return ( <div> <button onClick={() => fetchData('/api/data')}>Fetch Data</button> {data && <pre>{JSON.stringify(data, null, 2)}</pre>} </div> ) } ``` ### Combining Multiple Middlewares Middlewares can be composed together, with the innermost middleware applied first. ```typescript import { create } from 'zustand' import { devtools, persist, subscribeWithSelector } from 'zustand/middleware' import { immer } from 'zustand/middleware/immer' interface AppStore { user: { name: string; preferences: { theme: string } } | null setUser: (name: string) => void setTheme: (theme: string) => void } const useAppStore = create<AppStore>()( devtools( persist( subscribeWithSelector( immer((set) => ({ user: null, setUser: (name) => set((state) => { state.user = { name, preferences: { theme: 'light' } } }), setTheme: (theme) => set((state) => { if (state.user) { state.user.preferences.theme = theme } }), })) ), { name: 'app-store' } ), { name: 'AppStore' } ) ) ``` ## Summary Zustand excels at providing a lightweight yet powerful state management solution for React applications of any size. Its primary use cases include managing global application state without prop drilling, sharing state between unrelated components, handling complex nested state updates with Immer integration, persisting user preferences and session data across page reloads, and debugging state changes with Redux DevTools. The library is particularly well-suited for projects that value simplicity and minimal boilerplate while still requiring robust features like middleware composition and TypeScript support. The integration patterns supported by Zustand range from simple single-store setups to complex multi-slice architectures suitable for large applications. It works seamlessly with React's concurrent features, supports both synchronous and asynchronous state updates, and can be used alongside other state management solutions when needed. The vanilla store API also enables usage in non-React environments like Node.js servers, web workers, or other JavaScript frameworks, making Zustand a versatile choice for modern JavaScript development.