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}