Claude Code for Zustand Advanced: Slices, Middleware, and Persist — Claude Skills 360 Blog
Blog / Frontend / Claude Code for Zustand Advanced: Slices, Middleware, and Persist
Frontend

Claude Code for Zustand Advanced: Slices, Middleware, and Persist

Published: February 14, 2027
Read time: 8 min read
By: Claude Skills 360

Zustand manages React state with minimal boilerplate — a create() call defines a store with state and actions in one object. The slice pattern composes multiple state chunks into a single store. immer middleware enables mutation-style state updates for complex nested objects. persist serializes state to localStorage, sessionStorage, or custom storage adapters for cross-session persistence. subscribeWithSelector subscribes to specific state slices without re-rendering on unrelated changes. devtools exposes the store to Redux DevTools for time-travel debugging. TypeScript generics infer the full store type from the initial state. Zustand stores can be used outside React — plain getState() and subscribe() calls work in any context. Claude Code generates Zustand store definitions, slice patterns, middleware compositions, TypeScript-safe selectors, and the persistence configurations for production React applications.

CLAUDE.md for Zustand

## Zustand Stack
- Version: zustand >= 5.0
- Create: create<StoreType>()(immer(devtools(persist(...)))) — compose middleware
- Slices: combine slices with combineSlices() or spread pattern for modular stores
- Selectors: useStore(s => s.value) — select deeply, shallow() for objects
- Persist: persist(stateCreator, { name: "store", storage }) — localStorage/IndexedDB
- Immer: immer middleware — mutate draft directly, Immer creates immutable update
- Devtools: devtools(creator, { name: "MyStore" }) — Redux DevTools integration
- Testing: const store = createStore(stateCreator) — test outside React

Slice Pattern

// stores/slices/orders-slice.ts — orders slice
import type { StateCreator } from "zustand"

export interface Order {
  id: string
  status: "pending" | "processing" | "shipped" | "delivered" | "cancelled"
  totalCents: number
  customerId: string
  createdAt: string
}

export interface OrdersSlice {
  orders: Order[]
  ordersLoading: boolean
  ordersError: string | null
  // Actions
  fetchOrders: (customerId: string) => Promise<void>
  cancelOrder: (orderId: string) => Promise<void>
  updateOrderStatus: (orderId: string, status: Order["status"]) => void
}

export const createOrdersSlice: StateCreator<
  OrdersSlice & CartSlice,  // Access full store type for cross-slice actions
  [["zustand/immer", never], ["zustand/devtools", never]],
  [],
  OrdersSlice
> = (set, get) => ({
  orders: [],
  ordersLoading: false,
  ordersError: null,

  fetchOrders: async (customerId) => {
    set(state => { state.ordersLoading = true; state.ordersError = null })

    try {
      const response = await fetch(`/api/orders?customerId=${customerId}`)
      if (!response.ok) throw new Error(`HTTP ${response.status}`)
      const orders = await response.json()

      set(state => {
        state.orders = orders
        state.ordersLoading = false
      })
    } catch (error) {
      set(state => {
        state.ordersError = (error as Error).message
        state.ordersLoading = false
      })
    }
  },

  cancelOrder: async (orderId) => {
    await fetch(`/api/orders/${orderId}/cancel`, { method: "POST" })

    // Immer: direct mutation — no spread needed
    set(state => {
      const order = state.orders.find(o => o.id === orderId)
      if (order) order.status = "cancelled"
    })

    // Cross-slice: clear cart items for this order
    get().clearOrderItems(orderId)
  },

  updateOrderStatus: (orderId, status) => {
    set(state => {
      const order = state.orders.find(o => o.id === orderId)
      if (order) order.status = status
    })
  },
})
// stores/slices/cart-slice.ts — cart slice
import type { StateCreator } from "zustand"

export interface CartItem {
  productId: string
  name: string
  priceCents: number
  quantity: number
}

export interface CartSlice {
  cartItems: CartItem[]
  couponCode: string | null
  discountPercent: number
  // Computed (in selector, not slice)
  addToCart: (item: Omit<CartItem, "quantity">) => void
  removeFromCart: (productId: string) => void
  updateQuantity: (productId: string, quantity: number) => void
  applyCoupon: (code: string) => Promise<void>
  clearCart: () => void
  clearOrderItems: (orderId: string) => void
}

export const createCartSlice: StateCreator<
  CartSlice & OrdersSlice,
  [["zustand/immer", never], ["zustand/devtools", never], ["zustand/persist", unknown]],
  [],
  CartSlice
> = (set) => ({
  cartItems: [],
  couponCode: null,
  discountPercent: 0,

  addToCart: (item) => {
    set(state => {
      const existing = state.cartItems.find(i => i.productId === item.productId)
      if (existing) {
        existing.quantity += 1
      } else {
        state.cartItems.push({ ...item, quantity: 1 })
      }
    })
  },

  removeFromCart: (productId) => {
    set(state => {
      state.cartItems = state.cartItems.filter(i => i.productId !== productId)
    })
  },

  updateQuantity: (productId, quantity) => {
    set(state => {
      if (quantity <= 0) {
        state.cartItems = state.cartItems.filter(i => i.productId !== productId)
      } else {
        const item = state.cartItems.find(i => i.productId === productId)
        if (item) item.quantity = quantity
      }
    })
  },

  applyCoupon: async (code) => {
    const response = await fetch(`/api/coupons/${code}`)
    if (!response.ok) throw new Error("Invalid coupon")
    const { discount } = await response.json()

    set(state => {
      state.couponCode = code
      state.discountPercent = discount
    })
  },

  clearCart: () => {
    set(state => {
      state.cartItems = []
      state.couponCode = null
      state.discountPercent = 0
    })
  },

  clearOrderItems: (_orderId) => {
    // Called from orders slice after cancel
    set(state => { state.cartItems = [] })
  },
})

Combined Store with Middleware

// stores/index.ts — combine slices with middleware
import { create } from "zustand"
import { immer } from "zustand/middleware/immer"
import { devtools, persist } from "zustand/middleware"
import { subscribeWithSelector } from "zustand/middleware"
import type { OrdersSlice } from "./slices/orders-slice"
import type { CartSlice } from "./slices/cart-slice"
import { createOrdersSlice } from "./slices/orders-slice"
import { createCartSlice } from "./slices/cart-slice"

export type AppStore = OrdersSlice & CartSlice

export const useAppStore = create<AppStore>()(
  devtools(
    persist(
      subscribeWithSelector(
        immer((...args) => ({
          ...createOrdersSlice(...args),
          ...createCartSlice(...args),
        }))
      ),
      {
        name: "app-store",
        // Only persist cart — not fetched orders
        partialize: (state) => ({
          cartItems: state.cartItems,
          couponCode: state.couponCode,
          discountPercent: state.discountPercent,
        }),
        version: 1,
        // Migrate old persisted state if schema changes
        migrate: (persistedState: any, version) => {
          if (version === 0) {
            return { ...persistedState, discountPercent: 0 }
          }
          return persistedState
        },
      }
    ),
    { name: "AppStore" }
  )
)

// Computed selectors — outside store for memoization
export const selectCartTotal = (state: AppStore) =>
  state.cartItems.reduce(
    (sum, item) => sum + item.priceCents * item.quantity,
    0
  )

export const selectCartDiscount = (state: AppStore) =>
  Math.round(selectCartTotal(state) * state.discountPercent / 100)

export const selectCartFinalTotal = (state: AppStore) =>
  selectCartTotal(state) - selectCartDiscount(state)

// Subscribe to cart changes outside React
useAppStore.subscribe(
  state => state.cartItems.length,
  (count) => {
    document.title = count > 0 ? `Cart (${count}) — MyStore` : "MyStore"
  }
)

React Components

// components/CartSummary.tsx — shallow selector for objects
import { useAppStore, selectCartTotal, selectCartFinalTotal } from "@/stores"
import { shallow } from "zustand/shallow"

export function CartSummary() {
  // shallow: re-renders only when these specific values change
  const { cartItems, couponCode, discountPercent } = useAppStore(
    state => ({
      cartItems: state.cartItems,
      couponCode: state.couponCode,
      discountPercent: state.discountPercent,
    }),
    shallow
  )

  // Individual selector — primitives use reference equality
  const total = useAppStore(selectCartTotal)
  const finalTotal = useAppStore(selectCartFinalTotal)
  const clearCart = useAppStore(state => state.clearCart)

  return (
    <div>
      <p>{cartItems.length} items</p>
      {couponCode && <p>Coupon: {couponCode} (-{discountPercent}%)</p>}
      <p>Subtotal: ${(total / 100).toFixed(2)}</p>
      <p>Total: ${(finalTotal / 100).toFixed(2)}</p>
      <button onClick={clearCart}>Clear cart</button>
    </div>
  )
}

Store Testing

// tests/orders-store.test.ts — test store without React
import { createStore } from "zustand/vanilla"
import { immer } from "zustand/middleware/immer"
import { createOrdersSlice } from "../stores/slices/orders-slice"

describe("OrdersSlice", () => {
  it("updates order status optimistically", () => {
    const store = createStore(immer(createOrdersSlice))

    store.setState(state => {
      state.orders = [
        { id: "ord-1", status: "pending", totalCents: 1000, customerId: "cust-1", createdAt: "" }
      ]
    })

    store.getState().updateOrderStatus("ord-1", "shipped")

    const { orders } = store.getState()
    expect(orders[0].status).toBe("shipped")
  })
})

For the Jotai atomic state alternative that decomposes state into atoms — better for highly granular subscriptions where components read independent state slices, see the React state management guide for atom patterns. For the Redux Toolkit alternative when a structured actions/reducers model with better DevTools time-travel and Redux ecosystem (redux-saga, RTK Query) is required, the Redux guide covers RTK patterns. The Claude Skills 360 bundle includes Zustand skill sets covering slices, middleware, and persist configuration. Start with the free tier to try Zustand store generation.

Keep Reading

Frontend

Claude Code for Chart.js Advanced: Custom Plugins and Mixed Charts

Advanced Chart.js patterns with Claude Code — chart.register() for tree-shaking, mixed chart types combining bar and line, custom plugin API with beforeDraw and afterDatasetsDraw hooks, ScriptableContext for computed colors, ChartDataLabels plugin for value labels, chartjs-plugin-zoom for pan and zoom, custom gradient fills via ctx.createLinearGradient, ChartJS annotation plugin for threshold lines, streaming data with chartjs-plugin-streaming, and react-chartjs-2 with useRef and chart instance.

6 min read Jun 27, 2027
Frontend

Claude Code for Nivo: Rich SVG and Canvas Charts

Build rich data visualizations with Nivo and Claude Code — ResponsiveLine and ResponsiveBar for adaptive charts, ResponsiveHeatMap for matrix data, ResponsiveTreeMap for hierarchal data, ResponsiveSunburst for nested proportions, ResponsiveChord for relationship diagrams, ResponsiveCalendar for activity heat maps, ResponsiveNetwork for force graphs, NivoTheme for consistent styling, tooltip customization with sliceTooltip, and motion config for spring animations.

6 min read Jun 26, 2027
Frontend

Claude Code for Victory Charts: React Native and Web Charts

Build cross-platform charts with Victory and Claude Code — VictoryChart, VictoryLine, VictoryBar, and VictoryScatter for web and React Native, VictoryPie for donut charts, VictoryArea for stacked areas, VictoryAxis for custom axes, VictoryTooltip and VictoryVoronoiContainer for hover tooltips, VictoryBrushContainer for range selection, VictoryZoomContainer for pan and zoom, VictoryLegend for series labels, custom theme with VictoryTheme, and VictoryStack for grouped bars.

6 min read Jun 25, 2027

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free