# ADR-014: State Management Strategy **Status:** ✅ Accepted **Date:** 2025-12-01 **Decision Makers:** Frontend Team **Related Documents:** [Frontend Guidelines](../03-implementation/03-03-frontend-guidelines.md), [ADR-011: App Router](./ADR-011-nextjs-app-router.md) --- ## Context and Problem Statement ระบบ LCBP3-DMS ต้องการจัดการ Global State เช่น User session, Notifications, UI preferences ต้องเลือก State Management solution ที่เหมาะสม ### ปัญหาที่ต้องแก้: 1. **Global State:** จัดการ State ที่ใช้ร่วมกันทั้งแอปอย่างไร 2. **Server State:** จัดการข้อมูลจาก API อย่างไร 3. **Performance:** หลีกเลี่ยง Unnecessary re-renders 4. **Type Safety:** Type-safe state management 5. **Bundle Size:** ไม่ทำให้ Bundle ใหญ่เกินไป --- ## Decision Drivers - ⚡ **Performance:** Minimal re-renders - 📦 **Bundle Size:** เล็กที่สุด - 🎯 **Simplicity:** เรียนรู้และใช้งานง่าย - ✅ **Type Safety:** TypeScript support - 🔄 **Server State:** จัดการ API data ได้ดี --- ## Considered Options ### Option 1: Redux Toolkit **Pros:** - ✅ Industry standard - ✅ DevTools ดี - ✅ Middleware support **Cons:** - ❌ Boilerplate มาก - ❌ Bundle size ใหญ่ (~40kb) - ❌ Complexity สูง - ❌ Overkill สำหรับ App ส่วนใหญ่ ### Option 2: React Context API **Pros:** - ✅ Built-in (no dependencies) - ✅ Simple **Cons:** - ❌ Performance issues (re-render ทั้ง tree) - ❌ ไม่เหมาะสำหรับ Complex state - ❌ ต้องจัดการ Optimization เอง ### Option 3: Zustand **Props:** - ✅ **Lightweight:** ~1.2kb only - ✅ **Simple API:** เรียนรู้ง่าย - ✅ **Performance:** Selective re-renders - ✅ **TypeScript:** Full support - ✅ **No boilerplate** - ✅ **DevTools support** **Cons:** - ❌ Smaller community กว่า Redux ### Option 4: React Query (TanStack Query) for Server State **Pros:** - ✅ **Specialized:** จัดการ Server state ได้ดีที่สุด - ✅ **Caching:** Auto cache management - ✅ **Refetching:** Auto refetch on focus - ✅ **TypeScript:** Excellent support **Cons:** - ❌ เฉพาะ Server state (ต้องใช้คู่กับ Client state solution) --- ## Decision Outcome **Chosen Option:** **Zustand (Client State) + Native Fetch with Server Components (Server State)** ### Rationale **For Client State (UI state, Preferences):** - Use **Zustand** - lightweight และเรียนรู้ง่าย **For Server State (API data):** - Use **Server Components** + **SWR** (เฉพาะที่จำเป็น) - Server Components ดึงข้อมูลฝั่ง Server ไม่ต้องจัดการ state --- ## Implementation Details ### 1. Install Zustand ```bash npm install zustand ``` ### 2. Create Global Store (User Session) ```typescript // File: lib/stores/auth-store.ts import { create } from 'zustand'; import { persist } from 'zustand/middleware'; interface User { user_id: number; username: string; email: string; first_name: string; last_name: string; } interface AuthState { user: User | null; token: string | null; isAuthenticated: boolean; // Actions setAuth: (user: User, token: string) => void; logout: () => void; } export const useAuthStore = create()( persist( (set) => ({ user: null, token: null, isAuthenticated: false, setAuth: (user, token) => set({ user, token, isAuthenticated: true, }), logout: () => set({ user: null, token: null, isAuthenticated: false, }), }), { name: 'auth-storage', // LocalStorage key } ) ); ``` ### 3. Use Store in Components ```typescript // File: components/header.tsx 'use client'; import { useAuthStore } from '@/lib/stores/auth-store'; import { Button } from '@/components/ui/button'; export function Header() { const { user, logout } = useAuthStore(); return (
Welcome, {user?.first_name}
); } ``` ### 4. Notifications Store ```typescript // File: lib/stores/notification-store.ts import { create } from 'zustand'; interface Notification { id: string; type: 'success' | 'error' | 'info'; message: string; } interface NotificationState { notifications: Notification[]; addNotification: (notification: Omit) => void; removeNotification: (id: string) => void; clearAll: () => void; } export const useNotificationStore = create((set) => ({ notifications: [], addNotification: (notification) => set((state) => ({ notifications: [ ...state.notifications, { ...notification, id: Math.random().toString() }, ], })), removeNotification: (id) => set((state) => ({ notifications: state.notifications.filter((n) => n.id !== id), })), clearAll: () => set({ notifications: [] }), })); ``` ### 5. Server State with Server Components ```typescript // File: app/(dashboard)/correspondences/page.tsx // Server Component - No state management needed! import { getCorrespondences } from '@/lib/api/correspondences'; export default async function CorrespondencesPage() { // Fetch directly on server const correspondences = await getCorrespondences(); return (

Correspondences

{correspondences.map((item) => (
{item.subject}
))}
); } ``` ### 6. Client-Side Fetching (with SWR for real-time data) ```bash npm install swr ``` ```typescript // File: components/correspondences/realtime-list.tsx 'use client'; import useSWR from 'swr'; const fetcher = (url: string) => fetch(url).then((r) => r.json()); export function RealtimeCorrespondenceList() { const { data, error, isLoading, mutate } = useSWR( '/api/correspondences', fetcher, { refreshInterval: 30000, // Auto refresh every 30s } ); if (isLoading) return
Loading...
; if (error) return
Error loading data
; return (
{data.map((item) => (
{item.subject}
))}
); } ``` ### 7. UI Preferences Store ```typescript // File: lib/stores/ui-store.ts import { create } from 'zustand'; import { persist } from 'zustand/middleware'; interface UIState { sidebarCollapsed: boolean; theme: 'light' | 'dark'; toggleSidebar: () => void; setTheme: (theme: 'light' | 'dark') => void; } export const useUIStore = create()( persist( (set) => ({ sidebarCollapsed: false, theme: 'light', toggleSidebar: () => set((state) => ({ sidebarCollapsed: !state.sidebarCollapsed })), setTheme: (theme) => set({ theme }), }), { name: 'ui-preferences', } ) ); ``` --- ## State Management Patterns ### When to Use Zustand (Client State) ✅ Use Zustand for: - User authentication state - UI preferences (theme, sidebar state) - Notifications/Toasts - Shopping cart (if applicable) - Form wizard state - Modal state (global) ### When to Use Server Components (Server State) ✅ Use Server Components for: - Initial data loading - Static/semi-static data - SEO-important content - Data that doesn't need real-time updates ### When to Use SWR (Client-Side Server State) ✅ Use SWR for: - Real-time data (notifications count) - Polling/Auto-refresh data - User-specific data that changes often - Optimistic UI updates --- ## Consequences ### Positive Consequences 1. ✅ **Lightweight:** Zustand ~1.2kb 2. ✅ **Simple:** Easy to learn and use 3. ✅ **Performance:** Selective re-renders 4. ✅ **No Boilerplate:** Clean API 5. ✅ **Type Safe:** Full TypeScript support 6. ✅ **Persistent:** Easy LocalStorage persist ### Negative Consequences 1. ❌ **Smaller Ecosystem:** กว่า Redux 2. ❌ **Less Tooling:** DevTools ไม่ครบเท่า Redux ### Mitigation Strategies - **Documentation:** Document common patterns - **Code Examples:** Provide store templates - **Testing:** Unit test stores thoroughly --- ## Related ADRs - [ADR-011: Next.js App Router](./ADR-011-nextjs-app-router.md) - Server Components - [ADR-007: API Design](./ADR-007-api-design-error-handling.md) --- ## References - [Zustand Documentation](https://github.com/pmndrs/zustand) - [SWR Documentation](https://swr.vercel.app/) --- **Last Updated:** 2025-12-01 **Next Review:** 2026-06-01