@shezy26/validajs
Version:
Universal form validation library with Laravel-style syntax for vanilla JS, React, and Vue
1,102 lines (940 loc) • 29.8 kB
JavaScript
import { useState, useCallback } from 'react';
/**
* ValidaJS - Laravel-style Validation Rules
* Client-side implementation of Laravel validation rules
*/
function required(value) {
if (value === null || value === undefined) return false;
if (typeof value === "string") return value.trim().length > 0;
if (Array.isArray(value)) return value.length > 0;
return true;
}
function email(value) {
if (!value) return true;
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(String(value));
}
function min(value, [minValue]) {
if (!value) return true;
minValue = Number(minValue);
if (typeof value === "string") return value.length >= minValue;
if (typeof value === "number") return value >= minValue;
if (Array.isArray(value)) return value.length >= minValue;
return true;
}
function max(value, [maxValue]) {
if (!value) return true;
maxValue = Number(maxValue);
if (typeof value === "string") return value.length <= maxValue;
if (typeof value === "number") return value <= maxValue;
if (Array.isArray(value)) return value.length <= maxValue;
return true;
}
function between(value, [min, max]) {
if (!value) return true;
min = Number(min);
max = Number(max);
// Try to convert to number first
const numValue = Number(value);
if (!isNaN(numValue) && isFinite(numValue)) {
return numValue >= min && numValue <= max;
}
// For non-numeric strings, check length
if (typeof value === "string") {
const length = value.length;
return length >= min && length <= max;
}
if (Array.isArray(value)) {
const length = value.length;
return length >= min && length <= max;
}
return true;
}
function numeric(value) {
if (!value) return true;
return !isNaN(parseFloat(value)) && isFinite(value);
}
function integer(value) {
if (!value) return true;
return Number.isInteger(Number(value));
}
function alpha(value) {
if (!value) return true;
return /^[a-zA-Z]+$/.test(String(value));
}
function alpha_dash(value) {
if (!value) return true;
return /^[a-zA-Z0-9_-]+$/.test(String(value));
}
function alpha_num(value) {
if (!value) return true;
return /^[a-zA-Z0-9]+$/.test(String(value));
}
function url(value) {
if (!value) return true;
try {
new URL(value);
return true;
} catch {
return false;
}
}
function same(value, [otherField], allValues) {
if (!value) return true;
return value === allValues[otherField];
}
function different(value, [otherField], allValues) {
if (!value) return true;
return value !== allValues[otherField];
}
function confirmed(value, params, allValues, fieldName) {
if (!value) return true;
const confirmField = `${fieldName}_confirmation`;
return value === allValues[confirmField];
}
function in_rule(value, params) {
if (!value) return true;
return params.includes(String(value));
}
function not_in(value, params) {
if (!value) return true;
return !params.includes(String(value));
}
function boolean(value) {
if (value === null || value === undefined) return true;
const validValues = [true, false, 1, 0, "1", "0", "true", "false"];
return validValues.includes(value);
}
function accepted(value) {
const acceptedValues = ["yes", "on", "1", 1, true, "true"];
return acceptedValues.includes(value);
}
function declined(value) {
const declinedValues = ["no", "off", "0", 0, false, "false"];
return declinedValues.includes(value);
}
function size(value, [sizeValue]) {
if (!value) return true;
sizeValue = Number(sizeValue);
if (typeof value === "string") return value.length === sizeValue;
if (typeof value === "number") return value === sizeValue;
if (Array.isArray(value)) return value.length === sizeValue;
return true;
}
function digits(value, [length]) {
if (!value) return true;
const str = String(value);
return /^\d+$/.test(str) && str.length === Number(length);
}
function digits_between(value, [min, max]) {
if (!value) return true;
const str = String(value);
const length = str.length;
return /^\d+$/.test(str) && length >= Number(min) && length <= Number(max);
}
function date(value) {
if (!value) return true;
const dateObj = new Date(value);
return !isNaN(dateObj.getTime());
}
function before(value, [compareDate]) {
if (!value) return true;
const dateObj = new Date(value);
const compare = new Date(compareDate);
return (
!isNaN(dateObj.getTime()) && !isNaN(compare.getTime()) && dateObj < compare
);
}
function after(value, [compareDate]) {
if (!value) return true;
const dateObj = new Date(value);
const compare = new Date(compareDate);
return (
!isNaN(dateObj.getTime()) && !isNaN(compare.getTime()) && dateObj > compare
);
}
function before_or_equal(value, [compareDate]) {
if (!value) return true;
const dateObj = new Date(value);
const compare = new Date(compareDate);
return (
!isNaN(dateObj.getTime()) && !isNaN(compare.getTime()) && dateObj <= compare
);
}
function after_or_equal(value, [compareDate]) {
if (!value) return true;
const dateObj = new Date(value);
const compare = new Date(compareDate);
return (
!isNaN(dateObj.getTime()) && !isNaN(compare.getTime()) && dateObj >= compare
);
}
function regex(value, [pattern]) {
if (!value) return true;
try {
const regexObj = new RegExp(pattern);
return regexObj.test(String(value));
} catch {
console.warn(`Invalid regex pattern: ${pattern}`);
return false;
}
}
function string(value) {
if (value === null || value === undefined) return true;
return typeof value === "string";
}
function nullable(value) {
return true;
}
function array(value) {
if (value === null || value === undefined) return true;
return Array.isArray(value);
}
function starts_with(value, params) {
if (!value) return true;
const str = String(value);
return params.some((prefix) => str.startsWith(prefix));
}
function ends_with(value, params) {
if (!value) return true;
const str = String(value);
return params.some((suffix) => str.endsWith(suffix));
}
function lowercase(value) {
if (!value) return true;
return String(value) === String(value).toLowerCase();
}
function uppercase(value) {
if (!value) return true;
return String(value) === String(value).toUpperCase();
}
function ip(value) {
if (!value) return true;
const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/;
if (ipv4Regex.test(value)) {
const parts = value.split(".");
return parts.every((part) => Number(part) >= 0 && Number(part) <= 255);
}
const ipv6Regex = /^([0-9a-fA-F]{0,4}:){7}[0-9a-fA-F]{0,4}$/;
return ipv6Regex.test(value);
}
function ipv4(value) {
if (!value) return true;
const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/;
if (!ipv4Regex.test(value)) return false;
const parts = value.split(".");
return parts.every((part) => Number(part) >= 0 && Number(part) <= 255);
}
function ipv6(value) {
if (!value) return true;
const ipv6Regex = /^([0-9a-fA-F]{0,4}:){7}[0-9a-fA-F]{0,4}$/;
return ipv6Regex.test(value);
}
function json(value) {
if (!value) return true;
try {
JSON.parse(value);
return true;
} catch {
return false;
}
}
function uuid(value) {
if (!value) return true;
const uuidRegex =
/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
return uuidRegex.test(String(value));
}
function gt(value, [otherField], allValues) {
if (!value) return true;
const compareValue = allValues[otherField];
if (compareValue === undefined) return true;
return Number(value) > Number(compareValue);
}
function gte(value, [otherField], allValues) {
if (!value) return true;
const compareValue = allValues[otherField];
if (compareValue === undefined) return true;
return Number(value) >= Number(compareValue);
}
function lt(value, [otherField], allValues) {
if (!value) return true;
const compareValue = allValues[otherField];
if (compareValue === undefined) return true;
return Number(value) < Number(compareValue);
}
function lte(value, [otherField], allValues) {
if (!value) return true;
const compareValue = allValues[otherField];
if (compareValue === undefined) return true;
return Number(value) <= Number(compareValue);
}
function required_if(value, [otherField, compareValue], allValues) {
const otherValue = allValues[otherField];
if (String(otherValue) === String(compareValue)) {
return required(value);
}
return true;
}
function required_with(value, params, allValues) {
const hasAnyField = params.some((field) => {
const fieldValue = allValues[field];
return fieldValue !== null && fieldValue !== undefined && fieldValue !== "";
});
if (hasAnyField) {
return required(value);
}
return true;
}
function required_without(value, params, allValues) {
const hasAnyFieldMissing = params.some((field) => {
const fieldValue = allValues[field];
return fieldValue === null || fieldValue === undefined || fieldValue === "";
});
if (hasAnyFieldMissing) {
return required(value);
}
return true;
}
function ulid(value) {
if (!value) return true;
const ulidRegex = /^[0-7][0-9A-HJKMNP-TV-Z]{25}$/i;
return ulidRegex.test(String(value));
}
function mac_address(value) {
if (!value) return true;
const macRegex = /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/;
return macRegex.test(String(value));
}
function ascii(value) {
if (!value) return true;
return /^[\x00-\x7F]*$/.test(String(value));
}
function hex_color(value) {
if (!value) return true;
return /^#?([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/.test(String(value));
}
function password(value, [minLength = 8]) {
if (!value) return true;
const min = Number(minLength);
if (value.length < min) return false;
const hasLowercase = /[a-z]/.test(value);
const hasUppercase = /[A-Z]/.test(value);
const hasNumber = /[0-9]/.test(value);
const hasSymbol = /[!@#$%^&*(),.?":{}|<>]/.test(value);
return hasLowercase && hasUppercase && hasNumber && hasSymbol;
}
function max_digits(value, [maxDigits]) {
if (!value) return true;
const str = String(value).replace(/[^0-9]/g, "");
return str.length <= Number(maxDigits);
}
function min_digits(value, [minDigits]) {
if (!value) return true;
const str = String(value).replace(/[^0-9]/g, "");
return str.length >= Number(minDigits);
}
function decimal(value, [min, max]) {
if (!value) return true;
if (!numeric(value)) return false;
const str = String(value);
const parts = str.split(".");
if (parts.length === 1) return min === undefined || Number(min) === 0;
const decimalPlaces = parts[1].length;
if (max !== undefined) {
return decimalPlaces >= (Number(min) || 0) && decimalPlaces <= Number(max);
}
return decimalPlaces === Number(min);
}
function multiple_of(value, [multipleOf]) {
if (!value) return true;
const num = Number(value);
const multiple = Number(multipleOf);
if (isNaN(num) || isNaN(multiple)) return false;
return num % multiple === 0;
}
function active_url(value) {
if (!value) return true;
try {
const urlObj = new URL(value);
return urlObj.protocol === "http:" || urlObj.protocol === "https:";
} catch {
return false;
}
}
function timezone(value) {
if (!value) return true;
try {
Intl.DateTimeFormat(undefined, { timeZone: value });
return true;
} catch {
return false;
}
}
function date_equals(value, [compareDate]) {
if (!value) return true;
const date1 = new Date(value);
const date2 = new Date(compareDate);
if (isNaN(date1.getTime()) || isNaN(date2.getTime())) return false;
return date1.toDateString() === date2.toDateString();
}
function date_format(value, [format]) {
if (!value) return true;
if (format === "Y-m-d") {
return /^\d{4}-\d{2}-\d{2}$/.test(value) && date(value);
}
if (format === "d/m/Y" || format === "m/d/Y") {
return /^\d{2}\/\d{2}\/\d{4}$/.test(value);
}
return date(value);
}
function not_regex(value, [pattern]) {
if (!value) return true;
try {
const regexObj = new RegExp(pattern);
return !regexObj.test(String(value));
} catch {
console.warn(`Invalid regex pattern: ${pattern}`);
return false;
}
}
function doesnt_start_with(value, params) {
if (!value) return true;
const str = String(value);
return !params.some((prefix) => str.startsWith(prefix));
}
function doesnt_end_with(value, params) {
if (!value) return true;
const str = String(value);
return !params.some((suffix) => str.endsWith(suffix));
}
function present(value) {
return value !== undefined;
}
function filled(value) {
if (value === null || value === undefined) return true;
if (typeof value === "string") return value.trim().length > 0;
if (Array.isArray(value)) return value.length > 0;
return true;
}
function prohibited(value) {
if (value === null || value === undefined) return true;
if (typeof value === "string") return value.trim().length === 0;
if (Array.isArray(value)) return value.length === 0;
return false;
}
function distinct(value) {
if (value === null || value === undefined) return true;
if (!Array.isArray(value)) return true;
const unique = [...new Set(value)];
return unique.length === value.length;
}
function required_unless(
value,
[otherField, ...compareValues],
allValues
) {
const otherValue = allValues[otherField];
const shouldBeRequired = !compareValues.some(
(v) => String(otherValue) === String(v)
);
if (shouldBeRequired) {
return required(value);
}
return true;
}
function required_with_all(value, params, allValues) {
const hasAllFields = params.every((field) => {
const fieldValue = allValues[field];
return fieldValue !== null && fieldValue !== undefined && fieldValue !== "";
});
if (hasAllFields) {
return required(value);
}
return true;
}
function required_without_all(value, params, allValues) {
const allFieldsMissing = params.every((field) => {
const fieldValue = allValues[field];
return fieldValue === null || fieldValue === undefined || fieldValue === "";
});
if (allFieldsMissing) {
return required(value);
}
return true;
}
function accepted_if(value, [otherField, compareValue], allValues) {
const otherValue = allValues[otherField];
if (String(otherValue) === String(compareValue)) {
return accepted(value);
}
return true;
}
function declined_if(value, [otherField, compareValue], allValues) {
const otherValue = allValues[otherField];
if (String(otherValue) === String(compareValue)) {
return declined(value);
}
return true;
}
var rules = /*#__PURE__*/Object.freeze({
__proto__: null,
accepted: accepted,
accepted_if: accepted_if,
active_url: active_url,
after: after,
after_or_equal: after_or_equal,
alpha: alpha,
alpha_dash: alpha_dash,
alpha_num: alpha_num,
array: array,
ascii: ascii,
before: before,
before_or_equal: before_or_equal,
between: between,
boolean: boolean,
confirmed: confirmed,
date: date,
date_equals: date_equals,
date_format: date_format,
decimal: decimal,
declined: declined,
declined_if: declined_if,
different: different,
digits: digits,
digits_between: digits_between,
distinct: distinct,
doesnt_end_with: doesnt_end_with,
doesnt_start_with: doesnt_start_with,
email: email,
ends_with: ends_with,
filled: filled,
gt: gt,
gte: gte,
hex_color: hex_color,
in: in_rule,
in_rule: in_rule,
integer: integer,
ip: ip,
ipv4: ipv4,
ipv6: ipv6,
json: json,
lowercase: lowercase,
lt: lt,
lte: lte,
mac_address: mac_address,
max: max,
max_digits: max_digits,
min: min,
min_digits: min_digits,
multiple_of: multiple_of,
not_in: not_in,
not_regex: not_regex,
nullable: nullable,
numeric: numeric,
password: password,
present: present,
prohibited: prohibited,
regex: regex,
required: required,
required_if: required_if,
required_unless: required_unless,
required_with: required_with,
required_with_all: required_with_all,
required_without: required_without,
required_without_all: required_without_all,
same: same,
size: size,
starts_with: starts_with,
string: string,
timezone: timezone,
ulid: ulid,
uppercase: uppercase,
url: url,
uuid: uuid
});
/**
* Rule Engine
* Manages and executes validation rules
*/
class RuleEngine {
constructor() {
this.rules = new Map();
this.registerDefaultRules();
}
registerDefaultRules() {
for (const [name, ruleFn] of Object.entries(rules)) {
this.register(name, ruleFn);
}
}
register(name, validator) {
if (typeof validator !== "function") {
throw new Error(`Validator for rule "${name}" must be a function`);
}
this.rules.set(name, validator);
}
validate(ruleName, value, params = [], allValues = {}, fieldName = "") {
const validator = this.rules.get(ruleName);
if (!validator) {
console.warn(`Validation rule "${ruleName}" not found`);
return true;
}
try {
return validator(value, params, allValues, fieldName);
} catch (error) {
console.error(`Error executing validation rule "${ruleName}":`, error);
return false;
}
}
has(ruleName) {
return this.rules.has(ruleName);
}
getAllRules() {
return Array.from(this.rules.keys());
}
unregister(ruleName) {
return this.rules.delete(ruleName);
}
}
/**
* Parser Utility
* Parse Laravel-style validation rule strings
*/
function parseRule(rule) {
if (typeof rule !== "string") {
return { name: rule, params: [] };
}
const parts = rule.split(":");
const name = parts[0];
const params = parts[1] ? parts[1].split(",") : [];
return { name, params };
}
function normalizeSchema(schema) {
const normalized = {};
for (const [field, rules] of Object.entries(schema)) {
if (Array.isArray(rules)) {
normalized[field] = rules;
} else if (typeof rules === "string") {
normalized[field] = rules.split("|");
} else {
normalized[field] = [rules];
}
}
return normalized;
}
/**
* Default Error Messages
* Laravel-style error messages for validation rules
*/
const defaultMessages = {
required: "The :attribute field is required.",
email: "The :attribute must be a valid email address.",
min: "The :attribute must be at least :min characters.",
max: "The :attribute may not be greater than :max characters.",
between: "The :attribute must be between :min and :max.",
numeric: "The :attribute must be a number.",
integer: "The :attribute must be an integer.",
alpha: "The :attribute may only contain letters.",
alpha_dash:
"The :attribute may only contain letters, numbers, dashes and underscores.",
alpha_num: "The :attribute may only contain letters and numbers.",
url: "The :attribute must be a valid URL.",
same: "The :attribute and :other must match.",
different: "The :attribute and :other must be different.",
confirmed: "The :attribute confirmation does not match.",
in: "The selected :attribute is invalid.",
not_in: "The selected :attribute is invalid.",
boolean: "The :attribute field must be true or false.",
accepted: "The :attribute must be accepted.",
declined: "The :attribute must be declined.",
size: "The :attribute must be :size.",
digits: "The :attribute must be :digits digits.",
digits_between: "The :attribute must be between :min and :max digits.",
date: "The :attribute is not a valid date.",
before: "The :attribute must be a date before :date.",
after: "The :attribute must be a date after :date.",
before_or_equal: "The :attribute must be a date before or equal to :date.",
after_or_equal: "The :attribute must be a date after or equal to :date.",
regex: "The :attribute format is invalid.",
string: "The :attribute must be a string.",
nullable: "The :attribute field is optional.",
array: "The :attribute must be an array.",
starts_with: "The :attribute must start with one of the following: :values.",
ends_with: "The :attribute must end with one of the following: :values.",
lowercase: "The :attribute must be lowercase.",
uppercase: "The :attribute must be uppercase.",
ip: "The :attribute must be a valid IP address.",
ipv4: "The :attribute must be a valid IPv4 address.",
ipv6: "The :attribute must be a valid IPv6 address.",
json: "The :attribute must be a valid JSON string.",
uuid: "The :attribute must be a valid UUID.",
gt: "The :attribute must be greater than :other.",
gte: "The :attribute must be greater than or equal to :other.",
lt: "The :attribute must be less than :other.",
lte: "The :attribute must be less than or equal to :other.",
required_if: "The :attribute field is required when :other is :value.",
required_with: "The :attribute field is required when :values is present.",
required_without:
"The :attribute field is required when :values is not present.",
ulid: "The :attribute must be a valid ULID.",
mac_address: "The :attribute must be a valid MAC address.",
ascii:
"The :attribute must only contain single-byte alphanumeric characters and symbols.",
hex_color: "The :attribute must be a valid hexadecimal color.",
password:
"The :attribute must be at least :min characters and contain uppercase, lowercase, numbers, and symbols.",
max_digits: "The :attribute must not have more than :max digits.",
min_digits: "The :attribute must have at least :min digits.",
decimal: "The :attribute must have :decimal decimal places.",
multiple_of: "The :attribute must be a multiple of :value.",
active_url: "The :attribute is not a valid URL.",
timezone: "The :attribute must be a valid timezone.",
date_equals: "The :attribute must be a date equal to :date.",
date_format: "The :attribute does not match the format :format.",
not_regex: "The :attribute format must not match the given pattern.",
doesnt_start_with:
"The :attribute may not start with one of the following: :values.",
doesnt_end_with:
"The :attribute may not end with one of the following: :values.",
present: "The :attribute field must be present.",
filled: "The :attribute field must have a value.",
prohibited: "The :attribute field is prohibited.",
distinct: "The :attribute field has a duplicate value.",
required_unless:
"The :attribute field is required unless :other is in :values.",
required_with_all:
"The :attribute field is required when :values are present.",
required_without_all:
"The :attribute field is required when none of :values are present.",
accepted_if: "The :attribute must be accepted when :other is :value.",
declined_if: "The :attribute must be declined when :other is :value.",
};
/**
* ValidaJS Core Validator
* Framework-agnostic validation engine
*/
class Validator {
constructor(schema, options = {}) {
this.schema = schema;
this.options = {
realtime: true,
validateOnBlur: true,
validateOnInput: true,
validateOnSubmit: true,
messages: {},
...options,
};
this.ruleEngine = new RuleEngine();
this.errors = {};
this.touched = {};
this.values = {};
this.customMessages = { ...defaultMessages, ...this.options.messages };
}
validateField(fieldName, value, allValues = {}) {
const rules = this.schema[fieldName];
if (!rules || !Array.isArray(rules)) {
return null;
}
delete this.errors[fieldName];
for (const rule of rules) {
const { name, params } = parseRule(rule);
const isValid = this.ruleEngine.validate(
name,
value,
params,
allValues,
fieldName
);
if (!isValid) {
this.errors[fieldName] = this.getErrorMessage(fieldName, name, params);
break;
}
}
return this.errors[fieldName] || null;
}
validateAll(values) {
this.errors = {};
this.values = values;
for (const fieldName in this.schema) {
const value = values[fieldName];
this.validateField(fieldName, value, values);
}
return {
isValid: Object.keys(this.errors).length === 0,
errors: this.errors,
};
}
getErrorMessage(fieldName, ruleName, params) {
const customKey = `${fieldName}.${ruleName}`;
if (this.customMessages[customKey]) {
return this.formatMessage(
this.customMessages[customKey],
fieldName,
params
);
}
if (this.customMessages[ruleName]) {
return this.formatMessage(
this.customMessages[ruleName],
fieldName,
params
);
}
const defaultMessage =
defaultMessages[ruleName] || "The :attribute field is invalid.";
return this.formatMessage(defaultMessage, fieldName, params);
}
formatMessage(message, fieldName, params = []) {
let formatted = message.replace(
":attribute",
this.formatFieldName(fieldName)
);
// Replace specific parameter placeholders first
if (params.length > 0) {
formatted = formatted.replace(":min", params[0]);
formatted = formatted.replace(":size", params[0]);
formatted = formatted.replace(":other", params[0]);
formatted = formatted.replace(":date", params[0]);
formatted = formatted.replace(":digits", params[0]);
formatted = formatted.replace(":value", params[0]);
}
if (params.length > 1) {
formatted = formatted.replace(":max", params[1]);
}
// Replace generic parameter placeholders
params.forEach((param, index) => {
formatted = formatted.replace(`:param${index}`, param);
});
// Replace :values with comma-separated list
if (params.length > 0) {
formatted = formatted.replace(":values", params.join(", "));
}
return formatted;
}
formatFieldName(fieldName) {
return fieldName
.replace(/_/g, " ")
.replace(/\b\w/g, (char) => char.toUpperCase());
}
touch(fieldName) {
this.touched[fieldName] = true;
}
isTouched(fieldName) {
return !!this.touched[fieldName];
}
getAllValues() {
return this.values;
}
clearErrors() {
this.errors = {};
}
clearFieldError(fieldName) {
delete this.errors[fieldName];
}
getErrors() {
return this.errors;
}
hasErrors() {
return Object.keys(this.errors).length > 0;
}
getFieldError(fieldName) {
return this.errors[fieldName] || null;
}
reset() {
this.errors = {};
this.touched = {};
this.values = {};
}
}
/**
* ValidaJS - React Adapter
* React hook for form validation
*/
function useValidaJS(schema, options = {}) {
const [values, setValues] = useState({});
const [errors, setErrors] = useState({});
const [touched, setTouched] = useState({});
const validator = useState(
() => new Validator(normalizeSchema(schema), options)
)[0];
const validateField = useCallback(
(fieldName, value) => {
const error = validator.validateField(fieldName, value, values);
setErrors((prev) => ({
...prev,
[fieldName]: error,
}));
return error;
},
[validator, values]
);
const validateAll = useCallback(() => {
const result = validator.validateAll(values);
setErrors(result.errors);
return result;
}, [validator, values]);
const register = useCallback(
(fieldName) => {
return {
name: fieldName,
value: values[fieldName] || "",
onChange: (e) => {
const value =
e.target.type === "checkbox" ? e.target.checked : e.target.value;
setValues((prev) => ({ ...prev, [fieldName]: value }));
if (touched[fieldName]) {
validateField(fieldName, value);
}
},
onBlur: () => {
setTouched((prev) => ({ ...prev, [fieldName]: true }));
validateField(fieldName, values[fieldName]);
},
};
},
[values, touched, validateField]
);
const handleSubmit = useCallback(
(onSuccess, onError) => {
return (e) => {
e.preventDefault();
const result = validateAll();
if (result.isValid) {
onSuccess?.(values);
} else {
onError?.(result.errors);
}
};
},
[values, validateAll]
);
const reset = useCallback(() => {
setValues({});
setErrors({});
setTouched({});
validator.reset();
}, [validator]);
const setValue = useCallback((fieldName, value) => {
setValues((prev) => ({ ...prev, [fieldName]: value }));
}, []);
const getError = useCallback(
(fieldName) => {
return errors[fieldName] || null;
},
[errors]
);
return {
values,
errors,
touched,
isValid: Object.keys(errors).length === 0,
register,
handleSubmit,
validateField,
validateAll,
reset,
setValue,
getError,
};
}
export { useValidaJS };