Claude
Skills
Sign in
Back

zustand-advanced-patterns

Included with Lifetime
$97 forever

Use when implementing advanced Zustand patterns including transient updates, subscriptions with selectors, store composition, and performance optimization techniques.

General

What this skill does


# 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:

```typescript
const useStore = create((set) => ({
  count: 0,
  increment: () =>
    set((state) => ({ count: state.count + 1 }), false, 'increment'),
}))

// Usage: Update without re-rendering
useStore.setState({ count: 10 }, true) // replace: true, skip re-render
```

### Subscriptions with Selectors

Subscribe to specific slices of state:

```typescript
const useStore = create<Store>()((set) => ({ /* ... */ }))

// Subscribe only to count changes
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:

```typescript
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,
    }

    // Optimistic update
    set((state) => ({
      todos: [...state.todos, optimisticTodo],
    }))

    try {
      const savedTodo = await api.createTodo({ text })

      // Replace optimistic todo with real one
      set((state) => ({
        todos: state.todos.map((todo) =>
          todo.id === optimisticTodo.id ? savedTodo : todo
        ),
      }))
    } catch (error) {
      // Rollback on error
      set((state) => ({
        todos: state.todos.filter((todo) => todo.id !== optimisticTodo.id),
      }))
      throw error
    }
  },

  updateTodo: async (id, text) => {
    const previousTodos = get().todos

    // Optimistic update
    set((state) => ({
      todos: state.todos.map((todo) =>
        todo.id === id ? { ...todo, text } : todo
      ),
    }))

    try {
      await api.updateTodo(id, { text })
    } catch (error) {
      // Rollback on error
      set({ todos: previousTodos })
      throw error
    }
  },

  deleteTodo: async (id) => {
    const previousTodos = get().todos

    // Optimistic update
    set((state) => ({
      todos: state.todos.filter((todo) => todo.id !== id),
    }))

    try {
      await api.deleteTodo(id)
    } catch (error) {
      // Rollback on error
      set({ todos: previousTodos })
      throw error
    }
  },
}))
```

### 2. Undo/Redo Pattern

Implement time-travel functionality:

```typescript
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 > 0
    },

    set: (newPresent) =>
      set((state) => ({
        history: {
          past: [...state.history.past, state.history.present],
          present: newPresent,
          future: [],
        },
      })),

    undo: () =>
      set((state) => {
        if (state.history.past.length === 0) return state

        const previous = state.history.past[state.history.past.length - 1]
        const newPast = state.history.past.slice(0, -1)

        return {
          history: {
            past: newPast,
            present: previous,
            future: [state.history.present, ...state.history.future],
          },
        }
      }),

    redo: () =>
      set((state) => {
        if (state.history.future.length === 0) return state

        const next = state.history.future[0]
        const newFuture = state.history.future.slice(1)

        return {
          history: {
            past: [...state.history.past, state.history.present],
            present: next,
            future: newFuture,
          },
        }
      }),

    reset: (initialState) =>
      set({
        history: {
          past: [],
          present: initialState,
          future: [],
        },
      }),
  }))
}

// Usage
interface CanvasState {
  shapes: Shape[]
  selectedId: string | null
}

const useCanvasStore = createHistoryStore<CanvasState>({
  shapes: [],
  selectedId: null,
})

function Canvas() {
  const { present } = useCanvasStore((state) => state.history)
  const { canUndo, canRedo, undo, redo } = useCanvasStore()

  return (
    <div>
      <button onClick={undo} disabled={!canUndo}>
        Undo
      </button>
      <button onClick={redo} disabled={!canRedo}>
        Redo
      </button>
      {/* Render canvas */}
    </div>
  )
}
```

### 3. Store Composition

Compose multiple stores together:

```typescript
import { create, StoreApi } from 'zustand'

// Create bound stores that can access each other
function createBoundStore() {
  const useAuthStore = create<AuthStore>()((set, get) => ({
    user: null,
    login: async (credentials) => {
      const user = await api.login(credentials)
      set({ user })

      // Access cart store after login
      const cartStore = stores.cart.getState()
      await cartStore.syncCart()
    },
    logout: () => {
      set({ user: null })
      // Clear cart on logout
      stores.cart.getState().clearCart()
    },
  }))

  const useCartStore = create<CartStore>()((set, get) => ({
    items: [],
    addItem: (item) =>
      set((state) => ({ items: [...state.items, item] })),
    clearCart: () => set({ items: [] }),
    syncCart: async () => {
      const user = stores.auth.getState().user
      if (!user) return

      const items = await api.fetchCart(user.id)
      set({ items })
    },
  }))

  return {
    auth: useAuthStore,
    cart: useCartStore,
  }
}

const stores = createBoundStore()

export const useAuthStore = stores.auth
export const useCartStore = stores.cart
```

### 4. React Context Integration

Use Zustand with React Context for scoped stores:

```typescript
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) =>
      set((state) => ({
        todos: state.todos.map((todo) =>
          todo.id === id ? { ...todo, completed: !todo.completed } : todo
        ),
      })),
  }))
}

const TodoStoreContext = createContext<TodoStoreApi | null>(null)

export function TodoStoreProvider({
  children,
  initialTodos,
}: {
  children: React.ReactNode
  initialTodos?: Todo[]
}) {
  const storeRef = useRef<TodoStoreApi>()

  if (!storeRef.current) {
    storeRef.current = createTodoStore(initialTodos)
  }

  return (
    <TodoStoreContext.Provider value={storeRef.current}>
      {children}
    </TodoStoreContext.Provider>
  )
}

export function useTodoStore<T>(selector: (state: TodoStore) => T): T {
  const store = useContext(TodoStoreContext)

  if (!store) {
    throw new Error('useTodoStore must be used within TodoStoreProvider')
  }

  return useStore(store, selector)
}

// Usage
function 

Related in General