import { useState, useCallback, useMemo } from "react"; /** * Hook for managing form state with validation and dirty checking. * Handles common form patterns like tracking changes, validation, and submission. * * @param initialData - Initial form data * @param validator - Validation function that returns field errors * @param onSubmit - Submit handler function * @returns Form state and handlers */ export function useForm< TData extends Record, TErrors extends Record, >( initialData: TData, validator: (data: TData) => TErrors, onSubmit: (data: TData) => Promise ): { formData: TData; setFormData: React.Dispatch>; errors: TErrors; setErrors: React.Dispatch>; isDirty: boolean; isValid: boolean; isSubmitting: boolean; handleChange: (field: keyof TData) => (e: React.ChangeEvent) => void; handleSubmit: (e: React.FormEvent) => Promise; reset: () => void; } { const [formData, setFormData] = useState(initialData); const [originalData] = useState(initialData); const [errors, setErrors] = useState({} as TErrors); const [isSubmitting, setIsSubmitting] = useState(false); const isDirty = useMemo(() => { return JSON.stringify(formData) !== JSON.stringify(originalData); }, [formData, originalData]); const isValid = useMemo(() => { return Object.keys(errors).length === 0; }, [errors]); const handleChange = useCallback( (field: keyof TData) => (e: React.ChangeEvent) => { setFormData((prev) => ({ ...prev, [field]: e.target.value })); // Clear error for this field when user starts typing setErrors((prev) => { const newErrors = { ...prev }; delete newErrors[field as keyof TErrors]; return newErrors; }); }, [] ); const handleSubmit = useCallback( async (e: React.FormEvent) => { e.preventDefault(); // Validate all fields const validationErrors = validator(formData); setErrors(validationErrors); if (Object.keys(validationErrors).length > 0) { return; } setIsSubmitting(true); try { await onSubmit(formData); } finally { setIsSubmitting(false); } }, [formData, validator, onSubmit] ); const reset = useCallback(() => { setFormData(originalData); setErrors({} as TErrors); }, [originalData]); return { formData, setFormData, errors, setErrors, isDirty, isValid, isSubmitting, handleChange, handleSubmit, reset, }; }