auto-form-validator
Version:
A smart, zero-setup form validation library for React with automatic rule detection, i18n support, async validation, and UI integration helpers.
172 lines (166 loc) • 6.14 kB
JavaScript
// src/hooks/useAutoValidator.ts
import React, { useRef, useState, useEffect, useCallback } from "react";
// src/utils/validators.ts
var validateValue = async (value, rules, messages) => {
if (rules.required && !value.trim()) {
return typeof rules.required === "object" && rules.required.message ? rules.required.message : messages.required;
}
if (rules.minLength && value.length < (typeof rules.minLength === "object" ? rules.minLength.value : rules.minLength)) {
const min = typeof rules.minLength === "object" ? rules.minLength.value : rules.minLength;
return typeof rules.minLength === "object" && rules.minLength.message ? rules.minLength.message : messages.minLength(min);
}
if (rules.maxLength && value.length > (typeof rules.maxLength === "object" ? rules.maxLength.value : rules.maxLength)) {
const max = typeof rules.maxLength === "object" ? rules.maxLength.value : rules.maxLength;
return typeof rules.maxLength === "object" && rules.maxLength.message ? rules.maxLength.message : messages.maxLength(max);
}
if (rules.pattern && !(typeof rules.pattern === "object" && "test" in rules.pattern ? rules.pattern.test(value) : rules.pattern.test(value))) {
if (typeof rules.pattern === "object" && "message" in rules.pattern && rules.pattern.message) {
return rules.pattern.message;
}
return messages.pattern;
}
if (rules.custom) {
const result = rules.custom(value);
if (result) return result;
}
if (rules.async) {
const asyncError = await rules.async(value);
if (asyncError) return asyncError || messages.async;
}
return null;
};
// src/utils/messages.ts
var messagesMap = {
en: {
required: "This field is required",
minLength: (n) => `Minimum length is ${n}`,
maxLength: (n) => `Maximum length is ${n}`,
pattern: "Invalid format",
async: "Validation failed"
},
hi: {
required: "\u092F\u0939 \u092B\u093C\u0940\u0932\u094D\u0921 \u0906\u0935\u0936\u094D\u092F\u0915 \u0939\u0948",
minLength: (n) => `\u0928\u094D\u092F\u0942\u0928\u0924\u092E \u0932\u0902\u092C\u093E\u0908 ${n} \u0939\u094B\u0928\u0940 \u091A\u093E\u0939\u093F\u090F`,
maxLength: (n) => `\u0905\u0927\u093F\u0915\u0924\u092E \u0932\u0902\u092C\u093E\u0908 ${n} \u0939\u094B\u0928\u0940 \u091A\u093E\u0939\u093F\u090F`,
pattern: "\u0905\u092E\u093E\u0928\u094D\u092F \u092A\u094D\u0930\u093E\u0930\u0942\u092A",
async: "\u092E\u093E\u0928\u094D\u092F\u0915\u0930\u0923 \u0935\u093F\u092B\u0932 \u0939\u0941\u0906"
}
};
// src/components/validationPatterns.ts
var defaultValidationPatterns = {
email: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
number: /^-?\d+(\.\d+)?$/,
tel: /^\+?[1-9]\d{1,14}$/,
url: /^(https?:\/\/)?([\da-z.-]+)\.([a-z.]{2,6})([\/\w .-]*)*\/?$/,
date: /^\d{4}-\d{2}-\d{2}$/,
time: /^([01]\d|2[0-3]):([0-5]\d)$/,
color: /^#([0-9A-F]{3}){1,2}$/i
};
// src/hooks/useAutoValidator.ts
function debounce(func, wait = 300) {
let timeout;
return (...args) => {
clearTimeout(timeout);
timeout = setTimeout(() => func(...args), wait);
};
}
var useAutoValidator = (manualSchema = {}, language = "en") => {
const fields = useRef({});
const [errors, setErrors] = useState({});
const messages = messagesMap[language];
const extractRules = useCallback(
(el) => {
const rules = {};
if (el.required) rules.required = true;
if ("minLength" in el && el.minLength > 0) rules.minLength = el.minLength;
if ("maxLength" in el && el.maxLength > 0) rules.maxLength = el.maxLength;
if ("pattern" in el && el.pattern) rules.pattern = new RegExp(el.pattern);
const typePattern = defaultValidationPatterns[el.type];
if (typePattern) {
rules.pattern = typePattern;
}
return rules;
},
[]
);
const getField = useCallback((name) => {
if (!fields.current[name]) {
const ref = React.createRef();
fields.current[name] = {
ref,
rules: {}
};
}
return fields.current[name];
}, []);
useEffect(() => {
Object.entries(fields.current).forEach(([name, { ref }]) => {
const el = ref.current;
if (el) {
const extractedRules = extractRules(el);
fields.current[name].rules = extractedRules;
}
});
}, [extractRules]);
const validateAndSetError = async (name) => {
const field = fields.current[name];
if (!field || !field.ref.current) return;
const rules = manualSchema[name] || field.rules;
const value = field.ref.current.value;
const error = await validateValue(value, rules, messages);
setErrors((prev) => ({ ...prev, [name]: error }));
};
const debouncedValidateAndSetError = useCallback(
debounce(validateAndSetError, 300),
[manualSchema, messages]
);
const register = (name) => {
const field = getField(name);
const handleBlur = async () => {
await validateAndSetError(name);
};
const handleChange = () => {
debouncedValidateAndSetError(name);
};
return {
name,
ref: (el) => {
field.ref.current = el;
},
onBlur: handleBlur,
onChange: handleChange
};
};
const validateForm = async (values) => {
let valid = true;
const newErrors = {};
for (const name in values) {
const field = fields.current[name];
const rules = manualSchema[name] || (field == null ? void 0 : field.rules) || {};
const value = values[name];
const error = await validateValue(value, rules, messages);
newErrors[name] = error;
if (error) valid = false;
}
setErrors(newErrors);
return valid;
};
const resetErrors = () => setErrors({});
return {
register,
errors,
validateForm,
resetErrors
};
};
// src/components/ErrorMessage.tsx
import React2 from "react";
var ErrorMessage = ({ name, errors }) => {
if (!errors[name]) return null;
return /* @__PURE__ */ React2.createElement("p", { style: { color: "red", fontSize: "0.875rem", marginTop: "4px" } }, errors[name]);
};
var ErrorMessage_default = ErrorMessage;
export {
ErrorMessage_default as ErrorMessage,
useAutoValidator
};