UNPKG

@exabytellc/utils

Version:

EB react utils to make everything a little easier!

203 lines (186 loc) 5.97 kB
import { useCallback, useRef, useState } from "react"; import { isArr, isFunc, isObj } from "../types"; const AllRules = { required: (value, name) => { if (!value) return `${name} is required`; }, minLength: (value, name, rule) => { if (value.length <= rule.value) return `${name} must be at least ${rule.value} characters long`; }, maxLength: (value, name, rule) => { if (value.length >= rule.value) return `${name} must be no more than ${rule.value} characters long`; }, pattern: (value, name, rule) => { if (!rule.value.test(value)) return `Invalid format for ${name}`; }, email: (value) => { if (!(/^[^\s@]+@[^\s@]+\.[^\s@]+$/).test(value)) return 'Invalid email format'; }, custom: (value, name, rule) => { if (isFunc(rule.value)) return rule.value(value, name); }, } /** * Custom form hook to manage form state, validation, and input registration. * * @returns {Object} An object containing form methods and state. */ const useForm = () => { const [formValues, setFormValues] = useState({}); const [errors, setErrors] = useState({}); const [isDirty, setIsDirty] = useState(false); const touchedFields = useRef({}); const watchCallbacks = useRef({}); function getKey(field) { return isArr(field) ? `${field[0]}[${field[1]}]` : field; } function checkRule(rule, name, value, validate = () => true,) { if (!rule) return; const obj = isObj(rule) ? rule : { value: rule }; const isValid = validate(value, name, obj); if (isValid) return obj?.message || isValid; return; } /** * Validates a field based on specified validation rules. * * @param {string} field - The name of the field. * @param {*} value - The current value of the field. * @param {Object} rules - An object specifying validation rules for the field. */ const validateField = useCallback(async (name, value, rules) => { for (let key in rules) { const rule = rules[key]; const validate = AllRules?.[key]; const valid = await checkRule(rule, name, value, validate); if (valid) { setErrors((prev) => ({ ...prev, [name]: valid })); return; } } setErrors((prev) => ({ ...prev, [name]: null })); }, []); /** * Registers an input field by its name and optional index. * * @param {string|Array[name,index]} field - The name of the input field (supports array-like names). * @param {Object} [rules] - Validation rules for the input field. * @returns {Object} An object containing the value and onChange handler. */ const register = useCallback( (field, rules = {}) => { const key = getKey(field); return { name: key, value: formValues[key] || '', onChange: (e) => { const newValue = e.target.value; setFormValues((prev) => ({ ...prev, [key]: newValue, })); validateField(key, newValue, rules); touchedFields.current[key] = true; setIsDirty(true); if (watchCallbacks.current[key]) watchCallbacks.current[key](newValue); }, }; }, [formValues, validateField] ); /** * Resets the form values to an initial state. * * @param {Object} values - The initial values for the form. */ const reset = useCallback((values = {}) => { setFormValues(values); setErrors({}); setIsDirty(false); touchedFields.current = {}; }, []); /** * Handles form submission with validation. * * @param {Function} callback - The function to call on successful form submission. * @returns {Function} A submit handler function. */ const handleSubmit = useCallback( (callback) => (e) => { e.preventDefault(); const isValid = Object.values(errors).every((error) => error === ''); if (isValid) { callback(formValues); reset(); // Reset form after submission } }, [errors, formValues, reset] ); /** * Resets a specific field to an empty value. * * @param {string} name - The name of the field to reset. */ const resetField = useCallback((field) => { const key = getKey(field); setFormValues((prev) => ({ ...prev, [key]: '', })); }, []); /** * Gets the value of a specific field. * * @param {string} name - The name of the field. * @param {number} [index] - The index for array-like fields (optional). * @returns {*} The value of the specified field. */ const getValue = useCallback( (field) => { const key = getKey(field); return formValues[key] || ''; }, [formValues] ); /** * Sets the value of a specific field. * * @param {string} name - The name of the field. * @param {*} value - The value to set. * @param {number} [index] - The index for array-like fields (optional). */ const setValue = useCallback((field, value) => { const key = getKey(field); setFormValues((prev) => ({ ...prev, [key]: value, })); }, []); /** * Watches a specific field and calls the callback when its value changes. * * @param {string} name - The name of the field to watch. * @param {Function} callback - The callback function to execute when the field changes. * @returns {Function} Cleanup function to remove the watch on unmount. */ const watch = useCallback((field, callback) => { const key = getKey(field); watchCallbacks.current[key] = callback; return () => { delete watchCallbacks.current[key]; }; }, []); return { register, handleSubmit, reset, resetField, getValue, setValue, watch, values: formValues, errors, isDirty, }; }; export default useForm;