Skip to content

Vault · Construct

Zustand Persist Pattern

Added
2026-01-30
Category
Construct
  • React
  • TypeScript
  • State Management

Two patterns in one file: a basic typed store and one with localStorage persistence via Zustand's middleware. This is the 80/20 of React state management. You get type safety, zero boilerplate, and optional persistence without touching Redux, context providers, or any of that ceremony.

When to Use

  • You need shared state in React but Redux is overkill for the use case
  • Persisting user preferences (theme, sidebar state, settings) across page reloads
  • Any store where you want the API to be "create a hook, use the hook"

The Code

import { create } from 'zustand'
import { persist } from 'zustand/middleware'

// Basic typed store (no persistence)
interface LoadingState {
  isLoading: boolean
  setIsLoading: (isLoading: boolean) => void
}

export const useLoadingStore = create<LoadingState>((set) => ({
  isLoading: true,
  setIsLoading: (isLoading) => set({ isLoading }),
}))

// With persistence middleware (localStorage)
interface UserPreferences {
  theme: 'light' | 'dark'
  sidebarOpen: boolean
  setTheme: (theme: 'light' | 'dark') => void
  toggleSidebar: () => void
}

export const usePreferencesStore = create<UserPreferences>()(
  persist(
    (set) => ({
      theme: 'dark',
      sidebarOpen: true,
      setTheme: (theme) => set({ theme }),
      toggleSidebar: () => set((s) => ({ sidebarOpen: !s.sidebarOpen })),
    }),
    { name: 'user-preferences' }
  )
)

Notes

The persist middleware hydrates from localStorage on mount, which means there's a brief flash where the store has default values. If that matters for your UI (like theme flickering), read the persisted value directly from localStorage in your layout before React hydrates.