# ADR-013: Form Handling & Validation Strategy **Status:** ✅ Accepted **Date:** 2025-12-01 **Decision Makers:** Frontend Team **Related Documents:** [Frontend Guidelines](../03-implementation/03-03-frontend-guidelines.md) --- ## Context and Problem Statement ระบบ LCBP3-DMS มี Forms จำนวนมาก (Create/Edit Correspondence, RFA, Drawings) ต้องการวิธีจัดการ Forms ที่มี Performance ดี Validation ชัดเจน และ Developer Experience สูง ### ปัญหาที่ต้องแก้: 1. **Form State Management:** จัดการ Form state อย่างไร 2. **Validation:** Validate client-side และ server-side อย่างไร 3. **Error Handling:** แสดง Error messages อย่างไร 4. **Performance:** Forms ขนาดใหญ่ไม่ช้า 5. **Type Safety:** Type-safe forms with TypeScript --- ## Decision Drivers - ✅ **Type Safety:** TypeScript support เต็มรูปแบบ - ⚡ **Performance:** Re-render minimal - 🎯 **DX:** Developer Experience ดี - 📝 **Validation:** Schema-based validation - 🔄 **Reusability:** Reuse validation schema - 🎨 **Flexibility:** ปรับแต่งได้ง่าย --- ## Considered Options ### Option 1: Formik **Pros:** - ✅ Popular และ Mature - ✅ Documentation ดี - ✅ Yup validation **Cons:** - ❌ Performance issues (re-renders) - ❌ Bundle size ใหญ่ - ❌ TypeScript support ไม่ดีมาก - ❌ Not actively maintained ### Option 2: Plain React State ```typescript const [formData, setFormData] = useState({}); ``` **Pros:** - ✅ Simple - ✅ No dependencies **Cons:** - ❌ Boilerplate code มาก - ❌ ต้องจัดการ Validation เอง - ❌ Error handling ซับซ้อน - ❌ Performance issues ### Option 3: React Hook Form + Zod **Pros:** - ✅ **Performance:** Uncontrolled components (minimal re-renders) - ✅ **TypeScript First:** Full type safety - ✅ **Small Bundle:** ~8.5kb - ✅ **Schema Validation:** Zod integration - ✅ **DX:** Clean API - ✅ **Actively Maintained** **Cons:** - ❌ Learning curve (uncontrolled approach) - ❌ Complex forms ต้องใช้ Controller --- ## Decision Outcome **Chosen Option:** **Option 3 - React Hook Form + Zod** ### Rationale 1. **Performance:** Uncontrolled components = minimal re-renders 2. **Type Safety:** Zod schemas → TypeScript types → Runtime validation 3. **Bundle Size:** เล็กมาก (8.5kb) 4. **Developer Experience:** API สะอาด ใช้งานง่าย 5. **Validation Reuse:** Validation schema ใช้ร่วมกับ Backend ได้ --- ## Implementation Details ### 1. Install Dependencies ```bash npm install react-hook-form zod @hookform/resolvers ``` ### 2. Define Zod Schema ```typescript // File: lib/validations/correspondence.ts import { z } from 'zod'; export const correspondenceSchema = z.object({ subject: z .string() .min(5, 'Subject must be at least 5 characters') .max(255, 'Subject must not exceed 255 characters'), description: z .string() .min(10, 'Description must be at least 10 characters') .optional(), document_type_id: z.number({ required_error: 'Document type is required', }), from_organization_id: z.number({ required_error: 'From organization is required', }), to_organization_id: z.number({ required_error: 'To organization is required', }), importance: z.enum(['NORMAL', 'HIGH', 'URGENT']).default('NORMAL'), attachments: z.array(z.instanceof(File)).optional(), }); // Export TypeScript type export type CorrespondenceFormData = z.infer; ``` ### 3. Create Form Component ```typescript // File: components/correspondences/create-form.tsx 'use client'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { correspondenceSchema, type CorrespondenceFormData, } from '@/lib/validations/correspondence'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Textarea } from '@/components/ui/textarea'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; export function CreateCorrespondenceForm() { const { register, handleSubmit, formState: { errors, isSubmitting }, setValue, } = useForm({ resolver: zodResolver(correspondenceSchema), defaultValues: { importance: 'NORMAL', }, }); const onSubmit = async (data: CorrespondenceFormData) => { try { const response = await fetch('/api/correspondences', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data), }); if (!response.ok) throw new Error('Failed to create'); // Success - redirect window.location.href = '/correspondences'; } catch (error) { console.error(error); } }; return (
{/* Subject */}
{errors.subject && (

{errors.subject.message}

)}
{/* Description */}