State Management

State Management

Overview

Haddock uses a multi-layered approach to state management:

  • Zustand for global application state
  • React Query for server state
  • React Context for theme and auth
  • Local state for component-specific data

Global State (Zustand)

Store Setup

import { create } from 'zustand'

interface AppState {
  theme: 'light' | 'dark'
  setTheme: (theme: 'light' | 'dark') => void
  sidebarOpen: boolean
  setSidebarOpen: (open: boolean) => void
}

export const useAppStore = create<AppState>((set) => ({
  theme: 'light',
  setTheme: (theme) => set({ theme }),
  sidebarOpen: false,
  setSidebarOpen: (open) => set({ sidebarOpen: open })
}))

Using Global State

import { useAppStore } from '@/stores/app'

function ThemeToggle() {
  const { theme, setTheme } = useAppStore()
  
  return (
    <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
      Toggle Theme
    </button>
  )
}

Server State (React Query)

Query Setup

import { useQuery, useMutation } from '@tanstack/react-query'

// Query hook
export function useProjects() {
  return useQuery({
    queryKey: ['projects'],
    queryFn: fetchProjects
  })
}

// Mutation hook
export function useCreateProject() {
  return useMutation({
    mutationFn: createProject,
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['projects'] })
    }
  })
}

Using Server State

function ProjectList() {
  const { data: projects, isLoading } = useProjects()
  
  if (isLoading) return <LoadingSpinner />
  
  return (
    <div>
      {projects?.map(project => (
        <ProjectCard key={project.id} project={project} />
      ))}
    </div>
  )
}

Context State

Context Setup

import { createContext, useContext } from 'react'

const AuthContext = createContext<AuthContextType | null>(null)

export function AuthProvider({ children }: { children: React.ReactNode }) {
  const [user, setUser] = useState<User | null>(null)
  
  return (
    <AuthContext.Provider value={{ user, setUser }}>
      {children}
    </AuthContext.Provider>
  )
}

Using Context

function UserProfile() {
  const { user } = useAuth()
  
  if (!user) return <LoginPrompt />
  
  return <div>Welcome, {user.name}</div>
}

WebSocket State

Socket Store

interface SocketStore {
  connected: boolean
  messages: Message[]
  connect: () => void
  disconnect: () => void
  send: (message: Message) => void
}

export const useSocketStore = create<SocketStore>((set) => ({
  connected: false,
  messages: [],
  connect: () => {
    // WebSocket connection logic
  },
  disconnect: () => {
    // Disconnect logic
  },
  send: (message) => {
    // Send message logic
  }
}))

State Management Best Practices

When to Use Each Type

  1. Global State (Zustand)

    • Theme preferences
    • Authentication state
    • UI state (sidebar, modals)
    • Shared application state
  2. Server State (React Query)

    • API data
    • CRUD operations
    • Data that needs caching
    • Real-time updates
  3. Context

    • Theme provider
    • Auth provider
    • Feature flags
    • Localization
  4. Local State

    • Form inputs
    • Toggle states
    • Component-specific UI state

Performance Optimization

  1. State Splitting

    // Split into smaller stores
    const useUIStore = create<UIState>()
    const useAuthStore = create<AuthState>()
  2. Selective Updates

    // Use selectors
    const theme = useAppStore(state => state.theme)
  3. Middleware

    const log = (config) => (set, get, api) => config(
      (...args) => {
        console.log('Before', get())
        set(...args)
        console.log('After', get())
      },
      get,
      api
    )

Error Handling

const { data, error } = useQuery({
  queryKey: ['projects'],
  queryFn: fetchProjects,
  retry: 3,
  onError: (error) => {
    toast({
      title: 'Error',
      description: error.message
    })
  }
})