84 lines
2.1 KiB
TypeScript
Raw Normal View History

/**
* 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) {
// Form submission error - logging handled by consuming application
} finally {
setIsSubmitting(false);
}
}, [validate, onSubmit, values]);
const reset = useCallback(() => {
setValues(initialValues);
setErrors({});
setIsSubmitting(false);
}, [initialValues]);
return {
values,
errors,
isSubmitting,
setValue,
handleSubmit,
reset,
validate
};
}