
Zustand State
- 232 installs
- 74 repo stars
- Updated July 21, 2026
- existential-birds/beagle
Design Zustand stores with slices, selectors, middleware, and persistence patterns that avoid unnecessary re-renders in React and React Native apps.
About
Frontend build skill for Zustand: helps model global and feature stores, memoized selectors, devtools middleware, and persist rehydration so React web and native clients stay predictable without Redux boilerplate.
- Store slice design
- Selector optimization
- Middleware patterns
- Persist hydration
- React Native parity
Zustand State by the numbers
- 232 all-time installs (skills.sh)
- Ranked #818 of 2,245 Frontend Development skills by installs in the Skillselion catalog
- Data as of Jul 28, 2026 (Skillselion catalog sync)
npx skills add https://github.com/existential-birds/beagle --skill zustand-stateAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 232 |
|---|---|
| repo stars | ★ 74 |
| Last updated | July 21, 2026 |
| Repository | existential-birds/beagle ↗ |
What it does
Design Zustand stores with slices, selectors, middleware, and persistence patterns that avoid unnecessary re-renders in React and React Native apps.
Files
Zustand State Management
Minimal state management - no providers, minimal boilerplate.
Quick Reference
import { create } from 'zustand'
interface BearState {
bears: number
increase: (by: number) => void
}
const useBearStore = create<BearState>()((set) => ({
bears: 0,
increase: (by) => set((state) => ({ bears: state.bears + by })),
}))
// In component - select only what you need
const bears = useBearStore((state) => state.bears)
const increase = useBearStore((state) => state.increase)State Updates
// Flat updates (auto-merged at one level)
set({ bears: 5 })
set((state) => ({ bears: state.bears + 1 }))
// Nested objects (manual spread required)
set((state) => ({
nested: { ...state.nested, count: state.nested.count + 1 }
}))
// Replace entire state (no merge)
set({ bears: 0 }, true)Selectors & Performance
// Good - subscribes only to bears
const bears = useBearStore((state) => state.bears)
// Bad - rerenders on any change
const state = useBearStore()
// Multiple values with useShallow (prevents rerenders with shallow comparison)
import { useShallow } from 'zustand/react/shallow'
const { bears, fish } = useBearStore(
useShallow((state) => ({ bears: state.bears, fish: state.fish }))
)
// Array destructuring also works
const [bears, fish] = useBearStore(
useShallow((state) => [state.bears, state.fish])
)Access Outside Components
// Get current state (non-reactive)
const state = useBearStore.getState()
// Update state
useBearStore.setState({ bears: 5 })
// Subscribe to changes
const unsub = useBearStore.subscribe((state) => console.log(state))
unsub() // unsubscribeVanilla Store (No React)
import { createStore } from 'zustand/vanilla'
const store = createStore((set) => ({
bears: 0,
increase: (by) => set((state) => ({ bears: state.bears + by })),
}))
store.getState().bears
store.setState({ bears: 10 })
store.subscribe((state) => console.log(state))Additional Documentation
- Middleware: See references/middleware.md for persist, devtools, immer
- Patterns: See references/patterns.md for slices, testing, best practices
- TypeScript: See references/typescript.md for advanced typing patterns
Key Patterns
| Pattern | When to Use |
|---|---|
| Single selector | One piece of state needed |
useShallow | Multiple values, avoid rerenders |
getState() | Outside React, event handlers |
subscribe() | External systems, logging |
| Vanilla store | Non-React environments |
Gates (persist and devtools)
Before merging code that uses these middlewares (see references/middleware.md for setup):
1. `persist` — Pass when persisted keys are explicit (partialize or a reviewed full-state snapshot) and the JSON written to storage cannot include auth tokens, API keys, or other secrets. 2. `devtools` in shipped bundles — Pass when DevTools runs only in development (env gate), or you leave a one-line note in the PR explaining why it stays enabled in production.
Middleware
Persist (localStorage)
import { persist, createJSONStorage } from 'zustand/middleware'
const useStore = create<State>()(
persist(
(set) => ({
bears: 0,
increase: () => set((s) => ({ bears: s.bears + 1 })),
}),
{
name: 'bear-storage',
storage: createJSONStorage(() => localStorage),
}
)
)
// Partial persistence
const useStore = create(
persist(
(set) => ({ bears: 0, fish: 0 }),
{
name: 'storage',
partialize: (state) => ({ bears: state.bears }), // only persist bears
}
)
)DevTools
import { devtools } from 'zustand/middleware'
const useStore = create<State>()(
devtools(
(set) => ({
bears: 0,
increase: () => set(
(s) => ({ bears: s.bears + 1 }),
undefined,
'bear/increase' // Action name for DevTools
),
}),
{ name: 'BearStore' }
)
)Immer (Mutable Updates)
import { immer } from 'zustand/middleware/immer'
const useStore = create<State>()(
immer((set) => ({
nested: { count: 0 },
increment: () =>
set((state) => {
state.nested.count += 1 // mutate directly with immer
}),
}))
)Combine Middleware
import { create } from 'zustand'
import { devtools, persist } from 'zustand/middleware'
import { immer } from 'zustand/middleware/immer'
const useStore = create<State>()(
devtools(
persist(
immer((set) => ({
// store implementation
})),
{ name: 'storage' }
),
{ name: 'DevToolsName' }
)
)SubscribeWithSelector
import { subscribeWithSelector } from 'zustand/middleware'
const useStore = create(
subscribeWithSelector((set) => ({ bears: 0, fish: 0 }))
)
// Subscribe to specific field changes
const unsub = useStore.subscribe(
(state) => state.bears,
(bears, prevBears) => console.log(bears, prevBears),
{ fireImmediately: true }
)Next.js / SSR Hydration
import { useEffect, useState } from 'react'
const useHydration = () => {
const [hydrated, setHydrated] = useState(false)
useEffect(() => {
setHydrated(useBearStore.persist.hasHydrated())
return useBearStore.persist.onFinishHydration(() => setHydrated(true))
}, [])
return hydrated
}
function Component() {
const hydrated = useHydration()
const bears = useBearStore((state) => state.bears)
if (!hydrated) return <div>Loading...</div>
return <div>{bears} bears</div>
}Async Persistence
const useStore = create(
persist(
(set) => ({ data: null }),
{
name: 'async-storage',
storage: createJSONStorage(() => ({
getItem: async (name) => {
const value = await asyncStorage.getItem(name)
return value
},
setItem: async (name, value) => {
await asyncStorage.setItem(name, value)
},
removeItem: async (name) => {
await asyncStorage.removeItem(name)
},
})),
}
)
)Patterns & Best Practices
Slices Pattern (Store Composition)
// fishSlice.ts
const createFishSlice = (set) => ({
fish: 0,
addFish: () => set((state) => ({ fish: state.fish + 1 })),
})
// bearSlice.ts
const createBearSlice = (set) => ({
bears: 0,
addBear: () => set((state) => ({ bears: state.bears + 1 })),
})
// Combined store
import { create } from 'zustand'
const useBoundStore = create((...a) => ({
...createBearSlice(...a),
...createFishSlice(...a),
}))TypeScript Slices
import { StateCreator } from 'zustand'
interface BearSlice {
bears: number
addBear: () => void
}
interface FishSlice {
fish: number
addFish: () => void
}
const createBearSlice: StateCreator<
BearSlice & FishSlice,
[],
[],
BearSlice
> = (set) => ({
bears: 0,
addBear: () => set((state) => ({ bears: state.bears + 1 })),
})
const useBoundStore = create<BearSlice & FishSlice>()((...a) => ({
...createBearSlice(...a),
...createFishSlice(...a),
}))Testing
Mock Store Setup (Vitest)
// __mocks__/zustand.ts
import { act } from '@testing-library/react'
import type * as ZustandExportedTypes from 'zustand'
export * from 'zustand'
const { create: actualCreate } =
await vi.importActual<typeof ZustandExportedTypes>('zustand')
export const storeResetFns = new Set<() => void>()
export const create = (<T>(stateCreator) => {
const store = actualCreate(stateCreator)
const initialState = store.getInitialState()
storeResetFns.add(() => store.setState(initialState, true))
return store
}) as typeof actualCreate
afterEach(() => {
act(() => storeResetFns.forEach((fn) => fn()))
})Component Tests
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
test('increments count', async () => {
const user = userEvent.setup()
render(<Counter />)
expect(await screen.findByText('0')).toBeInTheDocument()
await user.click(screen.getByRole('button', { name: /increment/i }))
expect(await screen.findByText('1')).toBeInTheDocument()
})
test('reads store directly', () => {
expect(useCountStore.getState().count).toBe(0)
})Reset Store
const useStore = create<State & Actions>()((set, get, store) => ({
bears: 0,
fish: 0,
reset: () => set(store.getInitialState()),
}))Computed Values (Derived State)
// Don't store computed values - derive in selectors
const totalFood = useBearStore((s) => s.bears * s.foodPerBear)
// Or in the component
const totalFood = bears * foodPerBearTransient Updates (No Rerender)
function Component() {
const scratchRef = useRef(useScratchStore.getState().scratches)
useEffect(() =>
useScratchStore.subscribe(
(state) => { scratchRef.current = state.scratches }
), []
)
// Use scratchRef.current without causing rerenders
}Best Practices
Single Store Per Domain
Use one store for each domain (user, cart, ui). Split large stores with slices.
Colocate Actions
// Good - actions in store
const useStore = create((set) => ({
count: 0,
increment: () => set((s) => ({ count: s.count + 1 })),
}))
// Avoid - external actions
const increment = () => useStore.setState((s) => ({ count: s.count + 1 }))Selector Stability
// Bad - creates new function every render
const action = useBearStore((state) => () => state.increase(1))
// Good - select function directly
const increase = useBearStore((state) => state.increase)Pitfalls to Avoid
Don't Mutate State
// Wrong
set((state) => {
state.count += 1 // Mutation!
return state
})
// Correct (without immer)
set((state) => ({ count: state.count + 1 }))Avoid Fetching Entire Store
// Bad - rerenders on any change
const { bears, fish, cats } = useBearStore()
// Good - subscribe only to needed values
const bears = useBearStore((state) => state.bears)React Context (Dependency Injection)
import { createContext, useContext, useRef } from 'react'
import { createStore, useStore } from 'zustand'
const StoreContext = createContext(null)
const StoreProvider = ({ children }) => {
const storeRef = useRef()
if (!storeRef.current) {
storeRef.current = createStore((set) => ({
bears: 0,
increase: () => set((s) => ({ bears: s.bears + 1 })),
}))
}
return (
<StoreContext.Provider value={storeRef.current}>
{children}
</StoreContext.Provider>
)
}
const useBearStore = (selector) => {
const store = useContext(StoreContext)
return useStore(store, selector)
}Middleware
Contents
- Persist (localStorage)
- DevTools
- Immer (Mutable Updates)
- Combine Middleware
- SubscribeWithSelector
- Next.js / SSR Hydration
- Async Persistence
---
Persist (localStorage)
import { persist, createJSONStorage } from 'zustand/middleware'
const useStore = create<State>()(
persist(
(set) => ({
bears: 0,
increase: () => set((s) => ({ bears: s.bears + 1 })),
}),
{
name: 'bear-storage',
storage: createJSONStorage(() => localStorage),
}
)
)
// Partial persistence
const useStore = create(
persist(
(set) => ({ bears: 0, fish: 0 }),
{
name: 'storage',
partialize: (state) => ({ bears: state.bears }), // only persist bears
}
)
)DevTools
import { devtools } from 'zustand/middleware'
const useStore = create<State>()(
devtools(
(set) => ({
bears: 0,
increase: () => set(
(s) => ({ bears: s.bears + 1 }),
undefined,
'bear/increase' // Action name for DevTools
),
}),
{ name: 'BearStore' }
)
)Immer (Mutable Updates)
import { immer } from 'zustand/middleware/immer'
const useStore = create<State>()(
immer((set) => ({
nested: { count: 0 },
increment: () =>
set((state) => {
state.nested.count += 1 // mutate directly with immer
}),
}))
)Combine Middleware
import { create } from 'zustand'
import { devtools, persist } from 'zustand/middleware'
import { immer } from 'zustand/middleware/immer'
const useStore = create<State>()(
devtools(
persist(
immer((set) => ({
// store implementation
})),
{ name: 'storage' }
),
{ name: 'DevToolsName' }
)
)SubscribeWithSelector
import { subscribeWithSelector } from 'zustand/middleware'
const useStore = create(
subscribeWithSelector((set) => ({ bears: 0, fish: 0 }))
)
// Subscribe to specific field changes
const unsub = useStore.subscribe(
(state) => state.bears,
(bears, prevBears) => console.log(bears, prevBears),
{ fireImmediately: true }
)Next.js / SSR Hydration
import { useEffect, useState } from 'react'
const useHydration = () => {
const [hydrated, setHydrated] = useState(false)
useEffect(() => {
setHydrated(useBearStore.persist.hasHydrated())
return useBearStore.persist.onFinishHydration(() => setHydrated(true))
}, [])
return hydrated
}
function Component() {
const hydrated = useHydration()
const bears = useBearStore((state) => state.bears)
if (!hydrated) return <div>Loading...</div>
return <div>{bears} bears</div>
}Async Persistence
const useStore = create(
persist(
(set) => ({ data: null }),
{
name: 'async-storage',
storage: createJSONStorage(() => ({
getItem: async (name) => {
const value = await asyncStorage.getItem(name)
return value
},
setItem: async (name, value) => {
await asyncStorage.setItem(name, value)
},
removeItem: async (name) => {
await asyncStorage.removeItem(name)
},
})),
}
)
)Patterns & Best Practices
Contents
- Slices Pattern (Store Composition)
- TypeScript Slices
- Testing
- Mock Store Setup (Vitest)
- Component Tests
- Reset Store
- Computed Values (Derived State)
- Transient Updates (No Rerender)
- Best Practices
- Single Store Per Domain
- Colocate Actions
- Selector Stability
- Pitfalls to Avoid
- Don't Mutate State
- Avoid Fetching Entire Store
- React Context (Dependency Injection)
---
Slices Pattern (Store Composition)
// fishSlice.ts
const createFishSlice = (set) => ({
fish: 0,
addFish: () => set((state) => ({ fish: state.fish + 1 })),
})
// bearSlice.ts
const createBearSlice = (set) => ({
bears: 0,
addBear: () => set((state) => ({ bears: state.bears + 1 })),
})
// Combined store
import { create } from 'zustand'
const useBoundStore = create((...a) => ({
...createBearSlice(...a),
...createFishSlice(...a),
}))TypeScript Slices
import { StateCreator } from 'zustand'
interface BearSlice {
bears: number
addBear: () => void
}
interface FishSlice {
fish: number
addFish: () => void
}
const createBearSlice: StateCreator<
BearSlice & FishSlice,
[],
[],
BearSlice
> = (set) => ({
bears: 0,
addBear: () => set((state) => ({ bears: state.bears + 1 })),
})
const useBoundStore = create<BearSlice & FishSlice>()((...a) => ({
...createBearSlice(...a),
...createFishSlice(...a),
}))Testing
Mock Store Setup (Vitest)
// __mocks__/zustand.ts
import { act } from '@testing-library/react'
import type * as ZustandExportedTypes from 'zustand'
export * from 'zustand'
const { create: actualCreate } =
await vi.importActual<typeof ZustandExportedTypes>('zustand')
export const storeResetFns = new Set<() => void>()
export const create = (<T>(stateCreator) => {
const store = actualCreate(stateCreator)
const initialState = store.getInitialState()
storeResetFns.add(() => store.setState(initialState, true))
return store
}) as typeof actualCreate
afterEach(() => {
act(() => storeResetFns.forEach((fn) => fn()))
})Component Tests
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
test('increments count', async () => {
const user = userEvent.setup()
render(<Counter />)
expect(await screen.findByText('0')).toBeInTheDocument()
await user.click(screen.getByRole('button', { name: /increment/i }))
expect(await screen.findByText('1')).toBeInTheDocument()
})
test('reads store directly', () => {
expect(useCountStore.getState().count).toBe(0)
})Reset Store
const useStore = create<State & Actions>()((set, get, store) => ({
bears: 0,
fish: 0,
reset: () => set(store.getInitialState()),
}))Computed Values (Derived State)
// Don't store computed values - derive in selectors
const totalFood = useBearStore((s) => s.bears * s.foodPerBear)
// Or in the component
const totalFood = bears * foodPerBearTransient Updates (No Rerender)
function Component() {
const scratchRef = useRef(useScratchStore.getState().scratches)
useEffect(() =>
useScratchStore.subscribe(
(state) => { scratchRef.current = state.scratches }
), []
)
// Use scratchRef.current without causing rerenders
}Best Practices
Single Store Per Domain
Use one store for each domain (user, cart, ui). Split large stores with slices.
Colocate Actions
// Good - actions in store
const useStore = create((set) => ({
count: 0,
increment: () => set((s) => ({ count: s.count + 1 })),
}))
// Avoid - external actions
const increment = () => useStore.setState((s) => ({ count: s.count + 1 }))Selector Stability
// Bad - creates new function every render
const action = useBearStore((state) => () => state.increase(1))
// Good - select function directly
const increase = useBearStore((state) => state.increase)Pitfalls to Avoid
Don't Mutate State
// Wrong
set((state) => {
state.count += 1 // Mutation!
return state
})
// Correct (without immer)
set((state) => ({ count: state.count + 1 }))Avoid Fetching Entire Store
// Bad - rerenders on any change
const { bears, fish, cats } = useBearStore()
// Good - subscribe only to needed values
const bears = useBearStore((state) => state.bears)React Context (Dependency Injection)
import { createContext, useContext, useRef } from 'react'
import { createStore, useStore } from 'zustand'
const StoreContext = createContext(null)
const StoreProvider = ({ children }) => {
const storeRef = useRef()
if (!storeRef.current) {
storeRef.current = createStore((set) => ({
bears: 0,
increase: () => set((s) => ({ bears: s.bears + 1 })),
}))
}
return (
<StoreContext.Provider value={storeRef.current}>
{children}
</StoreContext.Provider>
)
}
const useBearStore = (selector) => {
const store = useContext(StoreContext)
return useStore(store, selector)
}TypeScript Patterns
Contents
- Curried Create Syntax
- Separate State and Actions
- Extract Store Type
- Async Actions
- Typed Slices with StateCreator
- Middleware Type Parameters
- Typed Selectors
- Custom Equality Functions
- Vanilla Store with Types
---
Curried Create Syntax
// Required for TypeScript - note the double parentheses
const useStore = create<State>()((set) => ({
// implementation
}))
// Wrong - type inference fails with middleware
create<State>((set) => ({ ... }))
// Correct
create<State>()((set) => ({ ... }))Separate State and Actions
interface State {
bears: number
fish: number
}
interface Actions {
increase: () => void
reset: () => void
}
const useStore = create<State & Actions>()((set, get, store) => ({
bears: 0,
fish: 0,
increase: () => set((state) => ({ bears: state.bears + 1 })),
reset: () => set(store.getInitialState()),
}))Extract Store Type
import { type ExtractState } from 'zustand'
const useBearStore = create((set) => ({
bears: 3,
increase: (by: number) => set((s) => ({ bears: s.bears + by })),
}))
// Extract type for reuse
export type BearState = ExtractState<typeof useBearStore>Async Actions
interface State {
data: Data | null
loading: boolean
}
interface Actions {
fetchData: () => Promise<void>
}
const useStore = create<State & Actions>((set) => ({
data: null,
loading: false,
fetchData: async () => {
set({ loading: true })
try {
const response = await fetch('/api/data')
const data = await response.json()
set({ data, loading: false })
} catch (error) {
set({ loading: false })
}
},
}))Typed Slices with StateCreator
import { StateCreator } from 'zustand'
interface BearSlice {
bears: number
addBear: () => void
}
interface FishSlice {
fish: number
addFish: () => void
}
type BoundStore = BearSlice & FishSlice
const createBearSlice: StateCreator<
BoundStore, // Full store type
[], // Middleware applied before
[], // Middleware applied after
BearSlice // This slice's type
> = (set) => ({
bears: 0,
addBear: () => set((state) => ({ bears: state.bears + 1 })),
})
const createFishSlice: StateCreator<
BoundStore,
[],
[],
FishSlice
> = (set) => ({
fish: 0,
addFish: () => set((state) => ({ fish: state.fish + 1 })),
})
const useBoundStore = create<BoundStore>()((...a) => ({
...createBearSlice(...a),
...createFishSlice(...a),
}))Middleware Type Parameters
import { create } from 'zustand'
import { devtools, persist } from 'zustand/middleware'
interface State {
count: number
increment: () => void
}
const useStore = create<State>()(
devtools(
persist(
(set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
}),
{ name: 'count-storage' }
),
{ name: 'CountStore' }
)
)Typed Selectors
interface BearState {
bears: number
increase: (by: number) => void
}
const useBearStore = create<BearState>()((set) => ({
bears: 0,
increase: (by) => set((state) => ({ bears: state.bears + by })),
}))
// Selector is automatically typed
const bears = useBearStore((state) => state.bears) // number
const increase = useBearStore((state) => state.increase) // (by: number) => voidCustom Equality Functions
import { createWithEqualityFn } from 'zustand/traditional'
import { shallow } from 'zustand/shallow'
const useStore = createWithEqualityFn<State>()(
(set) => ({
// store implementation
}),
shallow // Use shallow comparison by default
)
// Note: For most cases, use useShallow instead
// useShallow is imported from 'zustand/react/shallow' or 'zustand/shallow'
// The former is React-specific, the latter includes both shallow and useShallowVanilla Store with Types
import { createStore } from 'zustand/vanilla'
interface CounterState {
count: number
increment: () => void
decrement: () => void
}
const counterStore = createStore<CounterState>((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
}))
// Typed access
const count: number = counterStore.getState().countTypeScript Patterns
Curried Create Syntax
// Required for TypeScript - note the double parentheses
const useStore = create<State>()((set) => ({
// implementation
}))
// Wrong - type inference fails with middleware
create<State>((set) => ({ ... }))
// Correct
create<State>()((set) => ({ ... }))Separate State and Actions
interface State {
bears: number
fish: number
}
interface Actions {
increase: () => void
reset: () => void
}
const useStore = create<State & Actions>()((set, get, store) => ({
bears: 0,
fish: 0,
increase: () => set((state) => ({ bears: state.bears + 1 })),
reset: () => set(store.getInitialState()),
}))Extract Store Type
import { type ExtractState } from 'zustand'
const useBearStore = create((set) => ({
bears: 3,
increase: (by: number) => set((s) => ({ bears: s.bears + by })),
}))
// Extract type for reuse
export type BearState = ExtractState<typeof useBearStore>Async Actions
interface State {
data: Data | null
loading: boolean
}
interface Actions {
fetchData: () => Promise<void>
}
const useStore = create<State & Actions>((set) => ({
data: null,
loading: false,
fetchData: async () => {
set({ loading: true })
try {
const response = await fetch('/api/data')
const data = await response.json()
set({ data, loading: false })
} catch (error) {
set({ loading: false })
}
},
}))Typed Slices with StateCreator
import { StateCreator } from 'zustand'
interface BearSlice {
bears: number
addBear: () => void
}
interface FishSlice {
fish: number
addFish: () => void
}
type BoundStore = BearSlice & FishSlice
const createBearSlice: StateCreator<
BoundStore, // Full store type
[], // Middleware applied before
[], // Middleware applied after
BearSlice // This slice's type
> = (set) => ({
bears: 0,
addBear: () => set((state) => ({ bears: state.bears + 1 })),
})
const createFishSlice: StateCreator<
BoundStore,
[],
[],
FishSlice
> = (set) => ({
fish: 0,
addFish: () => set((state) => ({ fish: state.fish + 1 })),
})
const useBoundStore = create<BoundStore>()((...a) => ({
...createBearSlice(...a),
...createFishSlice(...a),
}))Middleware Type Parameters
import { create } from 'zustand'
import { devtools, persist } from 'zustand/middleware'
interface State {
count: number
increment: () => void
}
const useStore = create<State>()(
devtools(
persist(
(set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
}),
{ name: 'count-storage' }
),
{ name: 'CountStore' }
)
)Typed Selectors
interface BearState {
bears: number
increase: (by: number) => void
}
const useBearStore = create<BearState>()((set) => ({
bears: 0,
increase: (by) => set((state) => ({ bears: state.bears + by })),
}))
// Selector is automatically typed
const bears = useBearStore((state) => state.bears) // number
const increase = useBearStore((state) => state.increase) // (by: number) => voidCustom Equality Functions
import { createWithEqualityFn } from 'zustand/traditional'
import { shallow } from 'zustand/shallow'
const useStore = createWithEqualityFn<State>()(
(set) => ({
// store implementation
}),
shallow // Use shallow comparison by default
)Vanilla Store with Types
import { createStore } from 'zustand/vanilla'
interface CounterState {
count: number
increment: () => void
decrement: () => void
}
const counterStore = createStore<CounterState>((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
}))
// Typed access
const count: number = counterStore.getState().count