Generated by Spark: Too risky making changes without refactoring now. Create hook library, All components <150LOC. Consider orchestrating pages using json. JSON can describe actions, hooks, component tree, seed data you name it.

This commit is contained in:
2026-01-16 19:04:10 +00:00
committed by GitHub
parent e29e8b7361
commit c5e486859f
33 changed files with 4612 additions and 42 deletions

2
src/hooks/forms/index.ts Normal file
View File

@@ -0,0 +1,2 @@
export * from './use-form'
export * from './use-form-field'

View File

@@ -0,0 +1,54 @@
import { useState, useCallback } from 'react'
export type ValidationRule<T> = {
validate: (value: T) => boolean
message: string
}
export function useFormField<T>(
initialValue: T,
rules: ValidationRule<T>[] = []
) {
const [value, setValue] = useState<T>(initialValue)
const [error, setError] = useState<string | null>(null)
const [touched, setTouched] = useState(false)
const validate = useCallback(() => {
for (const rule of rules) {
if (!rule.validate(value)) {
setError(rule.message)
return false
}
}
setError(null)
return true
}, [value, rules])
const onChange = useCallback((newValue: T) => {
setValue(newValue)
setTouched(true)
}, [])
const onBlur = useCallback(() => {
setTouched(true)
validate()
}, [validate])
const reset = useCallback(() => {
setValue(initialValue)
setError(null)
setTouched(false)
}, [initialValue])
return {
value,
setValue,
onChange,
onBlur,
error,
touched,
isValid: error === null && touched,
validate,
reset,
}
}

View File

@@ -0,0 +1,73 @@
import { useState, useCallback } from 'react'
export interface FormConfig<T> {
initialValues: T
onSubmit: (values: T) => void | Promise<void>
validate?: (values: T) => Record<string, string>
}
export function useForm<T extends Record<string, any>>({
initialValues,
onSubmit,
validate,
}: FormConfig<T>) {
const [values, setValues] = useState<T>(initialValues)
const [errors, setErrors] = useState<Record<string, string>>({})
const [touched, setTouched] = useState<Record<string, boolean>>({})
const [isSubmitting, setIsSubmitting] = useState(false)
const setValue = useCallback((field: keyof T, value: any) => {
setValues((prev) => ({ ...prev, [field]: value }))
}, [])
const setFieldTouched = useCallback((field: keyof T) => {
setTouched((prev) => ({ ...prev, [field]: true }))
}, [])
const validateForm = useCallback(() => {
if (!validate) return true
const validationErrors = validate(values)
setErrors(validationErrors)
return Object.keys(validationErrors).length === 0
}, [values, validate])
const handleSubmit = useCallback(
async (e?: React.FormEvent) => {
if (e) e.preventDefault()
setIsSubmitting(true)
if (!validateForm()) {
setIsSubmitting(false)
return
}
try {
await onSubmit(values)
} finally {
setIsSubmitting(false)
}
},
[values, validateForm, onSubmit]
)
const reset = useCallback(() => {
setValues(initialValues)
setErrors({})
setTouched({})
setIsSubmitting(false)
}, [initialValues])
return {
values,
errors,
touched,
isSubmitting,
setValue,
setFieldTouched,
handleSubmit,
reset,
isValid: Object.keys(errors).length === 0,
}
}