# ADR-013: Form Handling & Validation Strategy **Status:** ✅ Accepted **Date:** 2026-02-24 **Decision Makers:** Frontend Team **Related Documents:** [Frontend Guidelines](../05-Engineering-Guidelines/05-03-frontend-guidelines.md) **Version Applicability:** v1.8.0+ **Next Review:** 2026-08-01 (6-month cycle) --- ## Gap Analysis & Requirement Linking ### ปิด Gap จาก Requirements: | Gap/Requirement | แหล่งที่มา | วิธีการแก้ไขใน ADR นี้ | |----------------|-------------|-------------------| | **Form Validation** | [Product Vision](../00-overview/00-03-product-vision.md) - Data Integrity | React Hook Form + Zod validation | | **Performance Optimization** | [Acceptance Criteria](../01-Requirements/01-05-acceptance-criteria.md) - AC-UI-002 | Uncontrolled components for minimal re-renders | | **Type Safety** | [Engineering Guidelines](../05-Engineering-Guidelines/05-01-fullstack-js-guidelines.md) - TypeScript | Zod schema to TypeScript types | | **Developer Experience** | [Frontend Guidelines](../05-Engineering-Guidelines/05-03-frontend-guidelines.md) - DX | Clean API with minimal boilerplate | | **Bundle Size Constraints** | [Architecture](../02-architecture/02-02-software-architecture.md) - Performance | Lightweight solution (8.5kb) | ### แก้ไขความขัดแย้ง: - **Conflict:** Performance vs. Developer Experience (Formik vs. RHF) - **Resolution:** Chose React Hook Form for performance and type safety - **Trade-off:** Learning curve for uncontrolled components vs. Better performance --- ## Impact Analysis ### Affected Components (ส่วนประกอบที่ได้รับผลกระทบ): | Component | ผลกระทบ | ความสำคัญ | |-----------|----------|-----------| | **Form Components** | All forms use RHF + Zod | 🔴 Critical | | **Validation Schemas** | Zod schemas for all forms | 🔴 Critical | | **API Routes** | Server-side validation | 🔴 Critical | | **UI Components** | FormField wrapper components | 🟡 Important | | **File Upload** | RHF integration for file handling | 🟡 Important | | **Dynamic Forms** | useFieldArray for complex forms | 🟡 Important | | **Type Definitions** | Form types from Zod schemas | 🟡 Important | | **Testing Setup** | Form testing utilities | 🟢 Guidelines | | **Documentation** | Form patterns and examples | 🟢 Guidelines | ### Required Changes (การเปลี่ยนแปลงที่ต้องดำเนินการ): #### Frontend (Next.js) - [x] Install React Hook Form + Zod - [x] Create validation schemas for all forms - [x] Update all form components to use RHF - [x] Create reusable FormField components - [x] Implement file upload with RHF - [x] Add dynamic form patterns (useFieldArray) - [x] Setup form testing utilities #### Backend (NestJS) - [x] Sync validation logic with frontend schemas - [x] Update DTOs to match Zod schemas - [x] Add proper error responses for validation failures #### Architecture - [x] Document form patterns - [x] Create form component templates - [x] Define validation strategy across stack --- ## 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 */}