@exabytellc/utils
Version:
EB react utils to make everything a little easier!
203 lines (186 loc) • 5.97 kB
JavaScript
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;