ATlast — you'll never need to find your favorites on another platform again. Find your favs in the ATmosphere.
atproto
1import { useState, useCallback } from "react"; 2import { ValidationResult } from "../lib/validation"; 3 4interface FieldState { 5 value: string; 6 error: string | null; 7 touched: boolean; 8} 9 10type ValidationFunction = (value: string) => ValidationResult; 11 12export function useFormValidation(initialValues: Record<string, string>) { 13 const [fields, setFields] = useState<Record<string, FieldState>>(() => { 14 const initial: Record<string, FieldState> = {}; 15 Object.keys(initialValues).forEach((key) => { 16 initial[key] = { 17 value: initialValues[key], 18 error: null, 19 touched: false, 20 }; 21 }); 22 return initial; 23 }); 24 25 const setValue = useCallback((fieldName: string, value: string) => { 26 setFields((prev) => ({ 27 ...prev, 28 [fieldName]: { 29 ...prev[fieldName], 30 value, 31 }, 32 })); 33 }, []); 34 35 const setError = useCallback((fieldName: string, error: string | null) => { 36 setFields((prev) => ({ 37 ...prev, 38 [fieldName]: { 39 ...prev[fieldName], 40 error, 41 }, 42 })); 43 }, []); 44 45 const setTouched = useCallback((fieldName: string) => { 46 setFields((prev) => ({ 47 ...prev, 48 [fieldName]: { 49 ...prev[fieldName], 50 touched: true, 51 }, 52 })); 53 }, []); 54 55 const validate = useCallback( 56 (fieldName: string, validationFn: ValidationFunction): boolean => { 57 const result = validationFn(fields[fieldName].value); 58 setFields((prev) => ({ 59 ...prev, 60 [fieldName]: { 61 ...prev[fieldName], 62 error: result.error || null, 63 touched: true, 64 }, 65 })); 66 return result.isValid; 67 }, 68 [fields], 69 ); 70 71 const validateAll = useCallback( 72 (validations: Record<string, ValidationFunction>): boolean => { 73 let isValid = true; 74 const newFields = { ...fields }; 75 76 Object.keys(validations).forEach((fieldName) => { 77 const result = validations[fieldName](fields[fieldName].value); 78 newFields[fieldName] = { 79 ...newFields[fieldName], 80 error: result.error || null, 81 touched: true, 82 }; 83 if (!result.isValid) { 84 isValid = false; 85 } 86 }); 87 88 setFields(newFields); 89 return isValid; 90 }, 91 [fields], 92 ); 93 94 const reset = useCallback(() => { 95 const resetFields: Record<string, FieldState> = {}; 96 Object.keys(fields).forEach((key) => { 97 resetFields[key] = { 98 value: "", 99 error: null, 100 touched: false, 101 }; 102 }); 103 setFields(resetFields); 104 }, [fields]); 105 106 const getFieldProps = useCallback( 107 (fieldName: string) => ({ 108 value: fields[fieldName]?.value || "", 109 onChange: (e: React.ChangeEvent<HTMLInputElement>) => 110 setValue(fieldName, e.target.value), 111 onBlur: () => setTouched(fieldName), 112 }), 113 [fields, setValue, setTouched], 114 ); 115 116 return { 117 fields, 118 setValue, 119 setError, 120 setTouched, 121 validate, 122 validateAll, 123 reset, 124 getFieldProps, 125 }; 126}