| name | zustand-store-patterns |
| user-invocable | false |
| description | Use when creating and managing Zustand stores for React state management. Covers store creation, selectors, actions, and basic usage patterns. |
| allowed-tools | ["Read","Write","Edit","Bash","Grep","Glob"] |
Zustand - Store Patterns
Zustand is a small, fast, and scalable state management solution for React. It uses a simplified flux principles with a hooks-based API.
Key Concepts
Store Creation
A Zustand store is created using the create function:
import { create } from 'zustand'
interface BearStore {
bears: number
increasePopulation: () => void
removeAllBears: () => void
}
const useBearStore = create<BearStore>((set) => ({
bears: 0,
increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
removeAllBears: () => set({ bears: 0 }),
}))
Using the Store in Components
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}>Add bear</button>
}
State Updates
Zustand provides two ways to update state:
set({ bears: 5 })
set((state) => ({ bears: state.bears + 1 }))
Best Practices
1. Use Selectors for Performance
Select only the state you need to prevent unnecessary re-renders:
function BadComponent() {
const store = useBearStore()
return <div>{store.bears}</div>
}
function GoodComponent() {
const bears = useBearStore((state) => state.bears)
return <div>{bears}</div>
}
2. Separate Actions from State
Keep your store organized by separating data from actions:
interface TodoStore {
todos: Todo[]
filter: 'all' | 'active' | 'completed'
addTodo: (text: string) => void
toggleTodo: (id: string) => void
removeTodo: (id: string) => void
setFilter: (filter: TodoStore['filter']) => void
}
const useTodoStore = create<TodoStore>((set) => ({
todos: [],
filter: 'all',
addTodo: (text) =>
set((state) => ({
todos: [...state.todos, { id: Date.now().toString(), text, completed: false }],
})),
toggleTodo:
( ({
: state..(
todo. === id ? { ...todo, : !todo. } : todo
),
})),
:
( ({
: state..( todo. !== id),
})),
: ({ filter }),
}))
3. Use Shallow Equality for Multiple Selectors
When selecting multiple values, use shallow from zustand/shallow:
import { create } from 'zustand'
import { shallow } from 'zustand/shallow'
const useStore = create<Store>((set) => ({
nuts: 0,
honey: 0,
increaseNuts: () => set((state) => ({ nuts: state.nuts + 1 })),
increaseHoney: () => set((state) => ({ honey: state.honey + 1 })),
}))
function Component() {
const { nuts, honey } = useStore(
(state) => ({ nuts: state.nuts, honey: state.honey }),
shallow
)
return <div>{nuts} nuts, {honey} honey</div>
}
4. Organize Large Stores with Slices
For complex applications, split stores into logical slices:
interface UserSlice {
user: User | null
login: (credentials: Credentials) => Promise<void>
logout: () => void
}
interface CartSlice {
items: CartItem[]
addItem: (item: Product) => void
removeItem: (id: string) => void
clearCart: () => void
}
const createUserSlice = (set: StateCreator<UserSlice>) => ({
user: null,
login: async (credentials) => {
const user = await api.login(credentials)
set({ user })
},
logout: () => set({ user: null }),
})
const createCartSlice = () => ({
: [],
:
( ({
: [...state., { ...product, : }],
})),
:
( ({
: state..( item. !== id),
})),
: ({ : [] }),
})
useStore = create< & >()( ({
...(...a),
...(...a),
}))
5. Access Store Outside Components
Use getState and setState for non-reactive access:
const useBearStore = create<BearStore>((set, get) => ({
bears: 0,
increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
doSomething: () => {
const currentBears = get().bears
console.log(`Current bears: ${currentBears}`)
},
}))
const currentState = useBearStore.getState()
useBearStore.setState({ bears: 10 })
Examples
Simple Counter Store
import { create } from 'zustand'
interface CounterStore {
count: number
increment: () => void
decrement: () => void
reset: () => void
}
export const useCounterStore = create<CounterStore>((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
reset: () => set({ count: 0 }),
}))
function Counter() {
const { count, increment, decrement, reset } = useCounterStore()
return (
<div>
<h1>Count: {count}
+
-
Reset
)
}
Shopping Cart Store
import { create } from 'zustand'
interface CartItem {
id: string
name: string
price: number
quantity: number
}
interface CartStore {
items: CartItem[]
addItem: (product: Omit<CartItem, 'quantity'>) => void
removeItem: (id: string) => void
updateQuantity: (id: string, quantity: number) => void
clearCart: () => void
total: number
}
export const useCartStore = create<CartStore>((set, get) => ({
items: [],
addItem: (product) =>
set((state) => {
const existingItem = state..( item. === product.)
(existingItem) {
{
: state..(
item. === product.
? { ...item, : item. + }
: item
),
}
}
{
: [...state., { ...product, : }],
}
}),
:
( ({
: state..( item. !== id),
})),
:
( ({
: state..(
item. === id ? { ...item, quantity } : item
),
})),
: ({ : [] }),
() {
()..(
sum + item. * item.,
)
},
}))
Authentication Store
import { create } from 'zustand'
interface User {
id: string
email: string
name: string
}
interface AuthStore {
user: User | null
token: string | null
isLoading: boolean
error: string | null
login: (email: string, password: string) => Promise<void>
logout: () => void
checkAuth: () => Promise<void>
}
export const useAuthStore = create<AuthStore>((set) => ({
user: null,
token: null,
isLoading: false,
error: null,
login: async (email, password) => {
set({ : , : })
{
response = (, {
: ,
: { : },
: .({ email, password }),
})
(!response.) {
()
}
{ user, token } = response.()
({ user, token, : })
.(, token)
} (error) {
({
: error ? error. : ,
: ,
})
}
},
: {
.()
({ : , : })
},
: () => {
token = .()
(!token)
({ : })
{
response = (, {
: { : },
})
(!response.) {
()
}
user = response.()
({ user, token, : })
} (error) {
.()
({ : , : , : })
}
},
}))
Common Patterns
Computed Values
Use getters for derived state:
const useStore = create<Store>((set, get) => ({
items: [],
get itemCount() {
return get().items.length
},
get hasItems() {
return get().items.length > 0
},
}))
Async Actions
Handle async operations within actions:
const useStore = create<Store>((set) => ({
data: null,
isLoading: false,
error: null,
fetchData: async () => {
set({ isLoading: true, error: null })
try {
const data = await api.fetchData()
set({ data, isLoading: false })
} catch (error) {
set({ error: error.message, isLoading: false })
}
},
}))
Reset Store
Implement a reset action:
const initialState = {
count: 0,
name: '',
}
const useStore = create<Store>((set) => ({
...initialState,
increment: () => set((state) => ({ count: state.count + 1 })),
setName: (name: string) => set({ name }),
reset: () => set(initialState),
}))
Anti-Patterns
❌ Don't Mutate State Directly
const useStore = create((set) => ({
items: [],
addItem: (item) => {
items.push(item)
},
}))
const useStore = create((set) => ({
items: [],
addItem: (item) =>
set((state) => ({
items: [...state.items, item],
})),
}))
❌ Don't Select the Entire Store
const store = useStore()
const count = useStore((state) => state.count)
❌ Don't Use External State in Selectors
const [userId, setUserId] = useState('123')
const user = useStore((state) => state.users[userId])
const getUser = (userId: string) => useStore.getState().users[userId]
❌ Don't Create Multiple Stores for Related Data
const useUserStore = create(...)
const useUserSettingsStore = create(...)
const useUserPreferencesStore = create(...)
const useUserStore = create((set) => ({
profile: null,
settings: {},
preferences: {},
}))
Related Skills
- zustand-typescript: TypeScript integration and type safety patterns
- zustand-middleware: Using persist, devtools, and immer middleware
- zustand-advanced-patterns: Subscriptions, transient updates, and advanced techniques