Skip to main content

ADR-0019: Zustand for State Management

Status

Accepted - 2025-01-26


Context

TVL Platform needs client-side state management for UI state (modals, filters, selected items) that persists across components.


Decision

Zustand for lightweight, hook-based state management.

Rationale

  1. Minimal Boilerplate: No providers, actions, or reducers
  2. Small Bundle: ~1KB (vs Redux ~10KB)
  3. TypeScript: Excellent type inference
  4. React Integration: Uses hooks naturally
  5. DevTools: Redux DevTools compatible

Alternatives Considered

Alternative 1: Redux Toolkit

Rejected - Too much boilerplate, overkill for MVP

Alternative 2: Context API

Rejected - Performance issues (re-renders entire tree)

Alternative 3: Jotai

Rejected - Atomic approach less familiar to team


Usage Examples

1. Create Store

// stores/bookingStore.ts
import { create } from 'zustand';

interface BookingStore {
selectedBookingId: string | null;
selectBooking: (id: string | null) => void;
filters: { status: string; dateRange: [Date, Date] | null };
setFilters: (filters: Partial<BookingStore['filters']>) => void;
}

export const useBookingStore = create<BookingStore>((set) => ({
selectedBookingId: null,
selectBooking: (id) => set({ selectedBookingId: id }),
filters: { status: 'all', dateRange: null },
setFilters: (filters) => set((state) => ({
filters: { ...state.filters, ...filters }
}))
}));

2. Use in Components

import { useBookingStore } from '@/stores/bookingStore';

export function BookingsList() {
const { selectBooking, filters } = useBookingStore();

return (
<div>
{bookings.map(booking => (
<div onClick={() => selectBooking(booking.id)}>
{booking.guestName}
</div>
))}
</div>
);
}

export function BookingFilters() {
const { filters, setFilters } = useBookingStore();

return (
<select
value={filters.status}
onChange={(e) => setFilters({ status: e.target.value })}
>
<option value="all">All</option>
<option value="pending">Pending</option>
<option value="confirmed">Confirmed</option>
</select>
);
}

3. Persist to LocalStorage

import { persist } from 'zustand/middleware';

export const useBookingStore = create(
persist<BookingStore>(
(set) => ({
// ... store definition
}),
{ name: 'booking-store' }
)
);

When to Use Zustand vs Server State

Use Zustand for:

  • ✅ UI state (modals, tabs, filters)
  • ✅ Selected items (checkboxes, active row)
  • ✅ Form drafts (local storage)

Use TanStack Query for:

  • ✅ Server data (bookings, properties)
  • ✅ Caching, refetching, optimistic updates

References