UNPKG

react-simple-form-hook

Version:

A lightweight, type-safe React form management hook with built-in Zod validation, field-level validation, touched state tracking, async submissions, and advanced form manipulation features

181 lines (178 loc) 5.48 kB
import { useState, useMemo, useCallback } from 'react'; import { ZodError } from 'zod'; // src/useForm.ts function useForm({ initialValues, schema, validateOnChange = false, validateOnBlur = true, onSubmit, }) { const [values, setValuesState] = useState(initialValues); const [errors, setErrors] = useState({}); const [touched, setTouched] = useState({}); const [isSubmitting, setIsSubmitting] = useState(false); // Check if form is dirty (has been modified) const isDirty = useMemo(() => { return JSON.stringify(values) !== JSON.stringify(initialValues); }, [values, initialValues]); // Check if form is valid const isValid = useMemo(() => { try { schema.parse(values); return true; } catch { return false; } }, [values, schema]); const handleChange = useCallback((event) => { const { name, value, type } = event.target; const inputValue = type === 'checkbox' ? event.target.checked : value; setValuesState((prev) => ({ ...prev, [name]: inputValue, })); // Clear error for the field being edited setErrors((prev) => ({ ...prev, [name]: undefined, })); // Validate on change if enabled if (validateOnChange) { setTimeout(() => validateField(name), 0); } }, [validateOnChange]); const handleBlur = useCallback((event) => { const { name } = event.target; setTouched((prev) => ({ ...prev, [name]: true, })); // Validate on blur if enabled if (validateOnBlur) { validateField(name); } }, [validateOnBlur, values]); const validate = useCallback((data) => { try { schema.parse(data); setErrors({}); return true; } catch (err) { if (err instanceof ZodError) { const fieldErrors = {}; err.issues.forEach((issue) => { const key = issue.path[0]; fieldErrors[key] = issue.message; }); setErrors(fieldErrors); return false; } return false; } }, [schema]); const validateField = useCallback((field) => { try { // Validate the entire object but only set error for this field schema.parse(values); setErrors((prev) => ({ ...prev, [field]: undefined, })); return true; } catch (err) { if (err instanceof ZodError) { const fieldError = err.issues.find(issue => issue.path[0] === field); setErrors((prev) => ({ ...prev, [field]: fieldError?.message, })); return !fieldError; } return false; } }, [values, schema]); const handleSubmit = useCallback((callback) => async (event) => { event.preventDefault(); // Mark all fields as touched const allTouched = Object.keys(values).reduce((acc, key) => { acc[key] = true; return acc; }, {}); setTouched(allTouched); if (validate(values)) { setIsSubmitting(true); try { const submitCallback = callback || onSubmit; if (submitCallback) { await submitCallback(values); } } catch (error) { console.error('Form submission error:', error); } finally { setIsSubmitting(false); } } }, [values, validate, onSubmit]); const reset = useCallback((newValues) => { setValuesState(newValues || initialValues); setErrors({}); setTouched({}); setIsSubmitting(false); }, [initialValues]); const setFieldValue = useCallback((field, value) => { setValuesState((prev) => ({ ...prev, [field]: value, })); }, []); const setFieldError = useCallback((field, error) => { setErrors((prev) => ({ ...prev, [field]: error, })); }, []); const setFieldTouched = useCallback((field, isTouched) => { setTouched((prev) => ({ ...prev, [field]: isTouched, })); }, []); const clearErrors = useCallback(() => { setErrors({}); }, []); const setValues = useCallback((newValues) => { setValuesState((prev) => ({ ...prev, ...newValues, })); }, []); const getFieldProps = useCallback((field) => { return { name: field, value: values[field], onChange: handleChange, onBlur: handleBlur, }; }, [values, handleChange, handleBlur]); return { values, errors, touched, isDirty, isSubmitting, isValid, handleChange, handleBlur, handleSubmit, setFieldValue, setFieldError, setFieldTouched, validateField, reset, clearErrors, setValues, getFieldProps, }; } export { useForm }; //# sourceMappingURL=index.js.map