import { useState, useCallback, FormEvent } from 'react'; import { useFormValidation, ValidationRules, FormErrors } from './useFormValidation'; /** * Hook for managing complete form state and submission */ export interface FormState> { values: T; errors: FormErrors; touched: Record; isValid: boolean; isSubmitting: boolean; isSubmitted: boolean; submitCount: number; } export interface FormOptions> { initialValues: T; validationRules: Record; onSubmit: (values: T) => Promise | void; validateOnMount?: boolean; } export interface FormReturn> extends FormState { setFieldValue: (field: keyof T, value: any) => void; setFieldError: (field: keyof T, error: string) => void; clearFieldError: (field: keyof T) => void; handleChange: (field: keyof T, value: any) => void; handleSubmit: (e: FormEvent) => Promise; reset: () => void; setAllTouched: () => void; setValues: (values: T) => void; setErrors: (errors: FormErrors) => void; setSubmitting: (isSubmitting: boolean) => void; getFormProps: () => { onSubmit: (e: FormEvent) => Promise; noValidate: boolean }; } /** * Hook for managing complete form state with validation and submission */ export function useForm>( options: FormOptions ): FormReturn { const { initialValues, validationRules, onSubmit, validateOnMount = false, } = options; const { values, errors, touched, isValid, setFieldValue: validationSetFieldValue, setFieldError: validationSetFieldError, clearFieldError: validationClearFieldError, validate, reset: validationReset, setAllTouched: validationSetAllTouched, setValues: validationSetValues, } = useFormValidation(initialValues, validationRules); const [isSubmitting, setIsSubmitting] = useState(false); const [isSubmitted, setIsSubmitted] = useState(false); const [submitCount, setSubmitCount] = useState(0); // Validate on mount if requested // Note: This is handled by useFormValidation's useEffect const setFieldValue = useCallback((field: keyof T, value: any) => { validationSetFieldValue(field, value); }, [validationSetFieldValue]); const setFieldError = useCallback((field: keyof T, error: string) => { validationSetFieldError(field, error); }, [validationSetFieldError]); const clearFieldError = useCallback((field: keyof T) => { validationClearFieldError(field); }, [validationClearFieldError]); const handleChange = useCallback((field: keyof T, value: any) => { setFieldValue(field, value); }, [setFieldValue]); const setErrors = useCallback((newErrors: FormErrors) => { Object.entries(newErrors).forEach(([field, fieldErrors]) => { if (Array.isArray(fieldErrors) && fieldErrors.length > 0) { fieldErrors.forEach((error) => { setFieldError(field as keyof T, error); }); } }); }, [setFieldError]); const setSubmitting = useCallback((state: boolean) => { setIsSubmitting(state); }, []); const reset = useCallback(() => { validationReset(); setIsSubmitting(false); setIsSubmitted(false); setSubmitCount(0); }, [validationReset]); const setAllTouched = useCallback(() => { validationSetAllTouched(); }, [validationSetAllTouched]); const setValues = useCallback((newValues: T) => { validationSetValues(newValues); }, [validationSetValues]); const handleSubmit = useCallback(async (e: FormEvent) => { e.preventDefault(); e.stopPropagation(); // Increment submit count setSubmitCount((prev) => prev + 1); // Set all fields as touched to show validation errors setAllTouched(); // Validate form const validation = validate(); if (!validation.isValid) { return; } // Start submission setIsSubmitting(true); try { // Call submit handler await onSubmit(values); setIsSubmitted(true); } catch (error) { // Handle submission error console.error('Form submission error:', error); // You can set a general error or handle specific error cases if (error instanceof Error) { setFieldError('submit' as keyof T, error.message); } else { setFieldError('submit' as keyof T, 'An error occurred during submission'); } } finally { setIsSubmitting(false); } }, [values, onSubmit, validate, setAllTouched, setFieldError]); const getFormProps = useCallback(() => ({ onSubmit: handleSubmit, noValidate: true, }), [handleSubmit]); return { values, errors, touched, isValid, isSubmitting, isSubmitted, submitCount, setFieldValue, setFieldError, clearFieldError, handleChange, handleSubmit, reset, setAllTouched, setValues, setErrors, setSubmitting, getFormProps, }; } /** * Hook for managing form state with additional utilities */ export function useFormWithHelpers>( options: FormOptions ) { const form = useForm(options); const getFormProps = () => ({ onSubmit: form.handleSubmit, noValidate: true, // We handle validation manually }); const getSubmitButtonProps = () => ({ type: 'submit', disabled: form.isSubmitting || !form.isValid, loading: form.isSubmitting, }); const getResetButtonProps = () => ({ type: 'button', onClick: form.reset, disabled: form.isSubmitting, }); const getFieldProps = (field: keyof T) => ({ value: form.values[field] as any, onChange: (e: any) => { const target = e.target; let value: any = target.value; if (target.type === 'checkbox') { value = target.checked; } else if (target.type === 'number') { value = target.value === '' ? '' : Number(target.value); } form.setFieldValue(field, value); }, error: form.errors[field as string]?.[0], touched: form.touched[field], onBlur: () => { // Mark as touched on blur if not already if (!form.touched[field]) { form.setAllTouched(); } }, }); const hasFieldError = (field: keyof T): boolean => { return !!form.errors[field as string]?.length && !!form.touched[field]; }; const getFieldError = (field: keyof T): string | null => { const errors = form.errors[field as string]; return errors && errors.length > 0 ? errors[0] : null; }; const clearFieldError = (field: keyof T) => { form.clearFieldError(field); }; const setFieldError = (field: keyof T, error: string) => { form.setFieldError(field, error); }; const isDirty = (): boolean => { return Object.keys(form.values).some((key) => { const currentValue = form.values[key as keyof T]; const initialValue = options.initialValues[key as keyof T]; return currentValue !== initialValue; }); }; const canSubmit = (): boolean => { return !form.isSubmitting && form.isValid && isDirty(); }; return { ...form, getFormProps, getSubmitButtonProps, getResetButtonProps, getFieldProps, hasFieldError, getFieldError, clearFieldError, setFieldError, isDirty, canSubmit, }; }