2025-09-19 16:34:10 +09:00
|
|
|
/**
|
|
|
|
|
* Simple Zod Form Hook for React
|
|
|
|
|
* Just uses Zod as-is with React state management
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import { useState, useCallback } from 'react';
|
|
|
|
|
import { ZodSchema, ZodError } from 'zod';
|
|
|
|
|
|
|
|
|
|
export interface ZodFormOptions<T> {
|
|
|
|
|
schema: ZodSchema<T>;
|
|
|
|
|
initialValues: T;
|
|
|
|
|
onSubmit?: (data: T) => Promise<void> | void;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function useZodForm<T extends Record<string, any>>({
|
|
|
|
|
schema,
|
|
|
|
|
initialValues,
|
|
|
|
|
onSubmit
|
|
|
|
|
}: ZodFormOptions<T>) {
|
|
|
|
|
const [values, setValues] = useState<T>(initialValues);
|
|
|
|
|
const [errors, setErrors] = useState<Partial<Record<keyof T, string>>>({});
|
|
|
|
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
|
|
|
|
|
|
|
|
const validate = useCallback(() => {
|
|
|
|
|
try {
|
|
|
|
|
schema.parse(values);
|
|
|
|
|
setErrors({});
|
|
|
|
|
return true;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
if (error instanceof ZodError) {
|
|
|
|
|
const fieldErrors: Partial<Record<keyof T, string>> = {};
|
|
|
|
|
error.issues.forEach(issue => {
|
|
|
|
|
const field = issue.path[0] as keyof T;
|
|
|
|
|
if (field) {
|
|
|
|
|
fieldErrors[field] = issue.message;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
setErrors(fieldErrors);
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}, [schema, values]);
|
|
|
|
|
|
|
|
|
|
const setValue = useCallback(<K extends keyof T>(field: K, value: T[K]) => {
|
|
|
|
|
setValues(prev => ({ ...prev, [field]: value }));
|
|
|
|
|
// Clear error when user starts typing
|
|
|
|
|
if (errors[field]) {
|
|
|
|
|
setErrors(prev => ({ ...prev, [field]: undefined }));
|
|
|
|
|
}
|
|
|
|
|
}, [errors]);
|
|
|
|
|
|
|
|
|
|
const handleSubmit = useCallback(async (e?: React.FormEvent) => {
|
|
|
|
|
if (e) e.preventDefault();
|
|
|
|
|
|
|
|
|
|
if (!validate()) return;
|
|
|
|
|
if (!onSubmit) return;
|
|
|
|
|
|
|
|
|
|
setIsSubmitting(true);
|
|
|
|
|
try {
|
|
|
|
|
await onSubmit(values);
|
|
|
|
|
} catch (error) {
|
2025-09-20 11:35:40 +09:00
|
|
|
// Form submission error - logging handled by consuming application
|
2025-09-19 16:34:10 +09:00
|
|
|
} finally {
|
|
|
|
|
setIsSubmitting(false);
|
|
|
|
|
}
|
|
|
|
|
}, [validate, onSubmit, values]);
|
|
|
|
|
|
|
|
|
|
const reset = useCallback(() => {
|
|
|
|
|
setValues(initialValues);
|
|
|
|
|
setErrors({});
|
|
|
|
|
setIsSubmitting(false);
|
|
|
|
|
}, [initialValues]);
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
values,
|
|
|
|
|
errors,
|
|
|
|
|
isSubmitting,
|
|
|
|
|
setValue,
|
|
|
|
|
handleSubmit,
|
|
|
|
|
reset,
|
|
|
|
|
validate
|
|
|
|
|
};
|
|
|
|
|
}
|