| name | zustand-advanced-patterns |
| user-invocable | false |
| description | Use when implementing advanced Zustand patterns including transient updates, subscriptions with selectors, store composition, and performance optimization techniques. |
| allowed-tools | ["Read","Write","Edit","Bash","Grep","Glob"] |
Zustand - Advanced Patterns
Advanced techniques and patterns for building complex applications with Zustand, including transient updates, optimistic updates, and sophisticated state management strategies.
Key Concepts
Transient Updates
Update state without triggering re-renders:
const useStore = create((set) => ({
count: 0,
increment: () =>
set((state) => ({ count: state.count + 1 }), false, 'increment'),
}))
useStore.setState({ count: 10 }, true)
Subscriptions with Selectors
Subscribe to specific slices of state:
const useStore = create<Store>()((set) => ({ }))
const unsubscribe = useStore.subscribe(
(state) => state.count,
(count, prevCount) => {
console.log(`Count changed from ${prevCount} to ${count}`)
},
{
equalityFn: (a, b) => a === b,
fireImmediately: false,
}
)
Best Practices
1. Optimistic Updates
Update UI immediately, then sync with server:
interface TodoStore {
todos: Todo[]
addTodo: (text: string) => Promise<void>
updateTodo: (id: string, text: string) => Promise<void>
deleteTodo: (id: string) => Promise<void>
}
const useTodoStore = create<TodoStore>()((set, get) => ({
todos: [],
addTodo: async (text) => {
const optimisticTodo = {
id: `temp-${Date.now()}`,
text,
completed: false,
}
set((state) => ({
todos: [...state.todos, optimisticTodo],
}))
try {
const savedTodo = await api.createTodo({ text })
set( ({
: state..(
todo. === optimisticTodo. ? savedTodo : todo
),
}))
} (error) {
( ({
: state..( todo. !== optimisticTodo.),
}))
error
}
},
: (id, text) => {
previousTodos = ().
( ({
: state..(
todo. === id ? { ...todo, text } : todo
),
}))
{
api.(id, { text })
} (error) {
({ : previousTodos })
error
}
},
: (id) => {
previousTodos = ().
( ({
: state..( todo. !== id),
}))
{
api.(id)
} (error) {
({ : previousTodos })
error
}
},
}))
2. Undo/Redo Pattern
Implement time-travel functionality:
interface HistoryState<T> {
past: T[]
present: T
future: T[]
}
interface HistoryStore<T> {
history: HistoryState<T>
canUndo: boolean
canRedo: boolean
set: (newPresent: T) => void
undo: () => void
redo: () => void
reset: (initialState: T) => void
}
function createHistoryStore<T>(initialState: T) {
return create<HistoryStore<T>>()((set, get) => ({
history: {
past: [],
present: initialState,
future: [],
},
get canUndo() {
return get().history.past.length > 0
},
get canRedo() {
return get().history.future.length >
},
:
( ({
: {
: [...state.., state..],
: newPresent,
: [],
},
})),
:
( {
(state... === ) state
previous = state..[state... - ]
newPast = state...(, -)
{
: {
: newPast,
: previous,
: [state.., ...state..],
},
}
}),
:
( {
(state... === ) state
next = state..[]
newFuture = state...()
{
: {
: [...state.., state..],
: next,
: newFuture,
},
}
}),
:
({
: {
: [],
: initialState,
: [],
},
}),
}))
}
{
: []
: |
}
useCanvasStore = createHistoryStore<>({
: [],
: ,
})
() {
{ present } = ( state.)
{ canUndo, canRedo, undo, redo } = ()
(
)
}
3. Store Composition
Compose multiple stores together:
import { create, StoreApi } from 'zustand'
function createBoundStore() {
const useAuthStore = create<AuthStore>()((set, get) => ({
user: null,
login: async (credentials) => {
const user = await api.login(credentials)
set({ user })
const cartStore = stores.cart.getState()
await cartStore.syncCart()
},
logout: () => {
set({ user: null })
stores.cart.getState().clearCart()
},
}))
const useCartStore = create<CartStore>()((set, get) => ({
items: [],
addItem: (item) =>
set((state) => ({ items: [...state.items, item] })),
clearCart: () => ({ : [] }),
: () => {
user = stores..().
(!user)
items = api.(user.)
({ items })
},
}))
{
: useAuthStore,
: useCartStore,
}
}
stores = ()
useAuthStore = stores.
useCartStore = stores.
4. React Context Integration
Use Zustand with React Context for scoped stores:
import { createContext, useContext, useRef } from 'react'
import { createStore, useStore } from 'zustand'
interface TodoStore {
todos: Todo[]
addTodo: (text: string) => void
toggleTodo: (id: string) => void
}
type TodoStoreApi = ReturnType<typeof createTodoStore>
const createTodoStore = (initialTodos: Todo[] = []) => {
return createStore<TodoStore>()((set) => ({
todos: initialTodos,
addTodo: (text) =>
set((state) => ({
todos: [
...state.todos,
{ id: Date.now().toString(), text, completed: false },
],
})),
toggleTodo: (id) =>
( ({
: state..(
todo. === id ? { ...todo, : !todo. } : todo
),
})),
}))
}
= createContext< | >()
() {
storeRef = useRef<>()
(!storeRef.) {
storeRef. = (initialTodos)
}
(
)
}
useTodoStore<T>(: T): T {
store = ()
(!store) {
()
}
(store, selector)
}
() {
(
)
}
() {
todos = ( state.)
addTodo = ( state.)
(
)
}
5. Derived State with Selectors
Create memoized derived state:
import { create } from 'zustand'
import { shallow } from 'zustand/shallow'
interface Store {
items: Item[]
filter: 'all' | 'active' | 'completed'
sortBy: 'name' | 'date'
}
const useStore = create<Store>()((set) => ({ }))
const selectFilteredAndSortedItems = (state: Store) => {
let items = state.items
if (state.filter === 'active') {
items = items.filter((item) => !item.completed)
} else if (state.filter === 'completed') {
items = items.filter((item) => item.completed)
}
if (state.sortBy === 'name') {
items = [...items].sort((a, b) => a..(b.))
} {
items = [...items].( b..() - a..())
}
items
}
() {
items = (selectFilteredAndSortedItems)
}
Examples
WebSocket Integration
interface ChatStore {
messages: Message[]
isConnected: boolean
connect: () => void
disconnect: () => void
sendMessage: (text: string) => void
}
const useChatStore = create<ChatStore>()((set, get) => {
let ws: WebSocket | null = null
return {
messages: [],
isConnected: false,
connect: () => {
ws = new WebSocket('wss://chat.example.com')
ws.onopen = () => {
set({ isConnected: true })
}
ws.onmessage = (event) => {
const message = JSON.parse(event.data)
set((state) => ({
messages: [...state.messages, message],
}))
}
ws. = {
({ : })
}
ws. = {
.(, error)
({ : })
}
},
: {
ws?.()
ws =
({ : })
},
: {
(!ws || ws. !== .)
message = {
: .().(),
text,
: (),
: ,
}
ws.(.(message))
( ({
: [...state., message],
}))
},
}
})
Pagination Pattern
interface PaginatedStore<T> {
items: T[]
page: number
pageSize: number
total: number
isLoading: boolean
hasMore: boolean
fetchPage: (page: number) => Promise<void>
nextPage: () => Promise<void>
prevPage: () => Promise<void>
reset: () => void
}
function createPaginatedStore<T>(
fetcher: (page: number, pageSize: number) => Promise<{ items: T[]; total: number }>,
pageSize: number = 20
) {
return create<PaginatedStore<T>>()((set, get) => ({
items: [],
page: 1,
pageSize,
total: 0,
isLoading: ,
() {
{ page, pageSize, total } = ()
page * pageSize < total
},
: (page) => {
({ : })
{
{ items, total } = (page, ().)
({ items, page, total, : })
} (error) {
({ : })
error
}
},
: () => {
{ page, hasMore } = ()
(!hasMore)
().(page + )
},
: () => {
{ page } = ()
(page <= )
().(page - )
},
:
({
: [],
: ,
: ,
: ,
}),
}))
}
useProductStore = createPaginatedStore<>(
(page, pageSize) => {
response = (
)
response.()
}
)
Computed Properties with Getters
interface Store {
items: Item[]
filter: string
sortBy: string
filteredItems: Item[]
sortedItems: Item[]
stats: {
total: number
completed: number
active: number
}
}
const useStore = create<Store>()((set, get) => ({
items: [],
filter: 'all',
sortBy: 'date',
get filteredItems() {
const { items, filter } = get()
if (filter === 'all') return items
if (filter === 'completed') return items.filter((i) => i.completed)
return items.filter((i) => !i.completed)
},
get sortedItems() {
const { filteredItems, sortBy } = get()
const items = [...filteredItems]
(sortBy === ) {
items.( a..(b.))
}
items.( b..() - a..())
},
() {
{ items } = ()
{
: items.,
: items.( i.).,
: items.( !i.).,
}
},
}))
Common Patterns
Batched Updates
Update multiple stores atomically:
function batchUpdates(updates: Array<() => void>) {
updates.forEach((update) => update())
}
batchUpdates([
() => useAuthStore.setState({ user: newUser }),
() => useCartStore.setState({ items: [] }),
() => useNotificationStore.setState({ unread: 0 }),
])
Error Boundary Integration
interface ErrorStore {
errors: Error[]
addError: (error: Error) => void
clearErrors: () => void
}
const useErrorStore = create<ErrorStore>()((set) => ({
errors: [],
addError: (error) =>
set((state) => ({ errors: [...state.errors, error] })),
clearErrors: () => set({ errors: [] }),
}))
function ErrorBoundary({ children }: { children: React.ReactNode }) {
const errors = useErrorStore((state) => state.errors)
if (errors.length > 0) {
return <div>Error: {errors[0].message}</div>
}
return <>{children}</>
}
Anti-Patterns
❌ Don't Store Derived State
const useStore = create((set) => ({
items: [],
itemCount: 0,
addItem: (item) =>
set((state) => ({
items: [...state.items, item],
itemCount: state.items.length + 1,
})),
}))
const useStore = create((set, get) => ({
items: [],
get itemCount() {
return get().items.length
},
addItem: (item) =>
set((state) => ({ items: [...state.items, item] })),
}))
❌ Don't Create Circular Dependencies
const useStoreA = create((set) => ({
value: 0,
update: () => {
useStoreB.getState().sync()
},
}))
const useStoreB = create((set) => ({
value: 0,
sync: () => {
useStoreA.getState().update()
},
}))
❌ Don't Overuse Subscriptions
function Component() {
useEffect(() => {
const unsubscribe = useStore.subscribe((state) => {
console.log(state)
})
}, [])
}
function Component() {
const value = useStore((state) => state.value)
return <div>{value}</div>
}
Related Skills
- zustand-store-patterns: Basic store creation and usage
- zustand-typescript: TypeScript integration
- zustand-middleware: Using middleware for enhanced functionality