react-validator-dev
Version:
A lightweight and customizable React hook for real-time form validation with field dependency support.
174 lines (153 loc) • 5.97 kB
text/typescript
import { useState, useEffect, useMemo } from "react";
import { ValidateProps, ReturnAPIs } from "./types";
import { mandatoryProps, optionalProps } from "./exceptionHandler";
import { validators } from "./validators";
const useValidation = (props:ValidateProps) => {
//Exception handling for props
const { fields, validation } = mandatoryProps(props);
const { isMultiple, debounceDelay, customValidators} = optionalProps(props);
const [returnAPIs, setReturnAPIs] = useState<ReturnAPIs>({
errors: {},
isValid: false,
touchedFields: {},
});
const markTouched = (field: string): void => {
setReturnAPIs((prev) => ({
...prev,
touchedFields: {
...prev.touchedFields,
[field]: true,
},
}));
};
const markUnTouched = (field: string): void => {
setReturnAPIs((prev) => ({
...prev,
touchedFields: {
...prev.touchedFields,
[field]: false,
},
}));
};
const allUntouchedFields = useMemo(() => {
return Object.fromEntries(Object.keys(fields).map((key) => [key, false]));
}, [fields]);
const allTouchedFields = useMemo(() => {
return Object.fromEntries(Object.keys(fields).map((key) => [key, true]));
}, [fields]);
const markAllTouched = (): void => {
setReturnAPIs((prev) => ({
...prev,
touchedFields: allTouchedFields,
}));
};
const markAllUntouched = (): void => {
setReturnAPIs((prev) => ({
...prev,
touchedFields: allUntouchedFields,
}));
};
const mRules = useMemo(() => validation.rules, [validation]);
const mMessages = useMemo(() => validation.messages || {}, [validation]);
const validate = ():void => {
const newErrors:Record<string, string> | any = {};
Object.keys(fields).forEach((field) => {
const value = fields[field];
const rules = mRules[field];
if (!rules) return;
const messages = mMessages[field] || {};
const multipleMessages: string[] = [];
let hasError = false;
let isRequiredError = false;
// isRequired check
if(rules.isRequired){
const error = validators.isRequired(value, messages.isRequired || `Please enter the ${field}`);
if(error){
multipleMessages.push(error);
isRequiredError = true;
hasError = true;
if(!isMultiple){
newErrors[field] = error;
return;
}
}
}
// Custom validator check debounceDelay
if (rules.custom && customValidators?.[field]) {
const error = customValidators[field](value, fields);
if (error) {
if (!isMultiple) {
newErrors[field] = error;
return;
} else {
multipleMessages.push(error);
}
}
}
// Additional checks if not failed by isRequired
if(!hasError || (isMultiple && !isRequiredError)){
const checks: [string, any][] = Object.entries(rules).filter(([key]) => key !== "isRequired");
for (const [rule, ruleValue] of checks) {
let error = "";
switch (rule) {
case "maxLength":
error = validators.maxLength(value, ruleValue, messages.maxLength || `The ${field} length should be at most ${ruleValue}`);
break;
case "minLength":
error = validators.minLength(value, ruleValue, messages.minLength || `The ${field} length should be at least ${ruleValue}`);
break;
case "excludedCharacters":
error = validators.excludedCharacters(value, ruleValue, messages.excludedCharacters || `Please enter valid ${field}`);
break;
case "regex":
error = validators.regex(value, ruleValue, messages.regex || `The ${field} format is invalid`);
break;
case "alpha":
error = validators.alpha(value, messages.alpha || `Please enter valid ${field}`);
break;
case "alphaDash":
error = validators.alphaDash(value, messages.alphaDash || `Please enter valid ${field}`);
break;
case "alphaSpace":
error = validators.alphaSpace(value, messages.alphaSpace || `Please enter valid ${field}`);
break;
case "numeric":
error = validators.numeric(value, messages.numeric || `Please enter valid ${field}`);
break;
case "email":
error = validators.email(value, messages.email || `Please enter a valid ${field}`);
break;
case "date":
error = validators.date(value, messages.date || `Please enter a valid ${field}`);
break;
case "sameAsField":
const otherFieldValue = fields[ruleValue];
error = validators.sameAsField(value, otherFieldValue, messages.sameAsField || `Please ensure ${field} matches ${ruleValue}`);
break;
default:
console.warn(`Unknown validation rule "${rule}" for field "${field}"`);
}
if(error){
if (!isMultiple) {
newErrors[field] = error;
return;
} else {
multipleMessages.push(error);
}
}
}
}
newErrors[field] = isMultiple ? multipleMessages : "";
});
if (JSON.stringify(returnAPIs.errors) !== JSON.stringify(newErrors)) {
const hasErrors = Object.values(newErrors).some((e) => Array.isArray(e) ? e.length > 0 : !!e);
setReturnAPIs((prev)=> ({...prev, errors: newErrors, isValid: !hasErrors }));
}
};
useEffect(() => {
const handler = setTimeout(() => validate() , debounceDelay);
return () => clearTimeout(handler);
}, [fields, validation, debounceDelay]);
return {...returnAPIs,markTouched, markAllTouched , markUnTouched, markAllUntouched};
};
export default useValidation;