Back to Docs
Recipe

Zustand Patterns

Production-grade state recipes for Meridian loaders, auth flows, and real-time dashboards.

Slice Factory

Compose independent slices into a single store. Each slice owns its state, actions, and selectors — no cross-slice imports.

const createAuthSlice = (set) => ({
  session: null,
  login: (s) => set({ session: s }),
})

const createLoaderSlice = (set) => ({
  status: 'idle',
  inject: () => set({ status: 'injecting' }),
})

export const useStore = create((...a) => ({
  ...createAuthSlice(...a),
  ...createLoaderSlice(...a),
}))

Selector Memoization

Use atomic selectors to prevent re-renders. Zustand diffs by reference — return primitives or stable references only.

// Good: primitive selector
const status = useStore((s) => s.status)

// Good: derived with shallow
import { shallow } from 'zustand/shallow'
const [user, token] = useStore(
  (s) => [s.user, s.token],
  shallow
)

Middleware Stack

Layer persist, devtools, and immer in the correct order. Persist wraps the entire store — place it outermost.

import { persist, devtools } from 'zustand/middleware'
import { immer } from 'zustand/middleware/immer'

export const useStore = create(
  devtools(
    persist(
      immer((set) => ({
        counters: {},
        inc: (id) =>
          set((s) => {
            s.counters[id] = (s.counters[id] || 0) + 1
          }),
      })),
      { name: 'meridian-counters' }
    ),
    { name: 'MeridianStore' }
  )
)

External Sync

Subscribe outside React for WebSocket or IPC updates. UseuseStore.getStateanduseStore.setStatewithout triggering component trees.

// In your WebSocket handler (outside React)
ws.onmessage = (ev) => {
  const data = JSON.parse(ev.data)
  useStore.setState({ heartbeat: data.ts })
}

// Read state imperatively
const token = useStore.getState().session?.token

These patterns power the Meridian loader dashboard, license validator, and real-time injection monitor. See the License Flow recipe for end-to-end integration.