Design A Form Validation Library
Basic Requirements:
Design A Form Validation Library

Basic Requirements:
Supported Validation Rules:
- Required Field
- Minimum/Maximum Length
- Regular Expression Pattern Matching
- Email Format
- Date Format
- Number Range
- Custom Validation Functions
Error Handling:
- Clear and user-friendly error messages (inline or separate)
- Customization of error display styles (inline elements, popovers, tooltip)
- Ability to suppress validation on specific fields or when the form is disabled
User Experience:
- Real-time feedback as user types (optional highlighting or subtle hints)
- Debouncing or throttling for performance and UX optimization
- Accessibility considerations (screen readers, keyboard navigation)
Flexibility:
- Support for various input types (text, email, password, number, select, textarea)
- Dynamic rule configuration during form initialization or runtime
- Seamless integration with different frontend frameworks (if applicable)
Additional Requirements:
Advanced Validation Rules:
- Asynchronous validation (e.g., checking username uniqueness)
- Conditional validation (rules depend on other field values)
- Cross-field validation (checking the consistency between values)
Form Submission Control:
- Prevent submission if validation fails (disable the submit button or show an error message)
- Optional data handling before submission (sanitization, transformations)
Internationalization:
- Customizable error messages and formatting based on locale
- Support for right-to-left languages
The architecture of Components:
- Validator:
- Responsible for:
- Accepting data and rules
- Performing validation checks
- Generating error messages
- Providing additional data (e.g., warnings)
Should be:
- Modular and reusable for different field types
- Able to handle both basic and advanced rules
- Extendable for custom validation logic
2. Rule Manager:
- Responsible for:
- Storing and managing validation rules
- Assigning rules to fields
- Providing rule configuration API
Should be:
- Dynamic and flexible
- Efficient for large forms or many rules
- Able to handle conditional and cross-field rules
3. Error Handler:
- Responsible for:
- Displaying error messages to the user
- Managing error states (e.g., highlighting fields)
- Customizing error presentation
Should be:
- User-friendly and clear
- Customizable in terms of styling and placement
- Accessible and keyboard-compatible
4. Form Manager:
- Responsible for:
- Coordinating validation across the entire form
- Handling form submission behavior based on validation results
- Handling disabled state and dynamic rule updates
Should be:
- Well-organized and maintainable
- Efficient for complex forms
- Flexible for different use cases
Data Models:
- Field:
- Contains metadata like field name, type, label, placeholder, initial value
- May have a reference to associated Validator(s)
- Rule:
- Defines the validation criteria (type, parameters, error message)
- Error:
- Stores error message, severity level (optional), related field(s)
- Form:
- Includes a collection of Fields, rules attached to those fields, and a reference to the Error Handler
Optimization:
- Debouncing/Throttling: Apply debouncing or throttling to validation checks as users type to improve performance and user experience.
- Lazy Validation: Don’t perform expensive validation immediately, but delay it until necessary (e.g., on blur or form submission).
- Memoization: Cache validation results for fields with unchanged values to avoid redundant computations.
- Efficient Rule Management: Use efficient data structures and algorithms for rule lookups and updates.
- Lightweight Error Display: Display errors efficiently, considering potential UI impact.
Additional Considerations:
- Documentation: Provide clear and comprehensive documentation for developers using the library.
- Testing: Write unit and integration tests to ensure the library’s correctness and robustness.
- Maintainability: Use a clean and modular code structure for easy maintenance and future development.
- Community: Consider open-sourcing the library to benefit from community contributions and support.
Version 1
// Validator class
class Validator {
constructor(field, rules) {
this.field = field;
this.rules = rules;
this.errors = [];
}
validate() {
this.errors = []; // Clear previous errors
for (const rule of this.rules) {
const isValid = rule.check(this.field.value);
if (!isValid) {
this.errors.push(rule.errorMessage);
}
}
return this.errors.length === 0;
}
get isValid() {
return this.validate();
}
}
// Rule class
class Rule {
constructor(type, params, errorMessage) {
this.type = type;
this.params = params;
this.errorMessage = errorMessage;
}
check(value) {
switch (this.type) {
case "required":
return value.trim() !== "";
case "minLength":
return value.length >= this.params.minLength;
case "maxLength":
return value.length <= this.params.maxLength;
case "pattern":
return new RegExp(this.params.pattern).test(value);
case "email":
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
case "number":
return !isNaN(value);
case "range":
return value >= this.params.min && value <= this.params.max;
// Add more rule types as needed
default:
throw new Error(`Invalid rule type: ${this.type}`);
}
}
}
// ErrorHandler class
class ErrorHandler {
constructor(form) {
this.form = form;
this.errorEl = document.createElement("div"); // Container for displaying errors
this.errorEl.classList.add("form-errors");
this.form.appendChild(this.errorEl);
}
displayErrors(errors) {
this.errorEl.textContent = ""; // Clear previous errors
if (errors.length > 0) {
const ul = document.createElement("ul");
for (const error of errors) {
const li = document.createElement("li");
li.textContent = error;
ul.appendChild(li);
}
this.errorEl.appendChild(ul);
this.form.classList.add("has-errors"); // Add a CSS class for styling
} else {
this.form.classList.remove("has-errors");
}
}
}
// FormManager class
class FormManager {
constructor(form) {
this.form = form;
this.validators = {}; // Map field names to validators
this.errorHandler = new ErrorHandler(form);
this.initValidators();
this.bindEvents();
}
initValidators() {
// Get validation rules from data attributes or another mechanism
for (const field of this.form.elements) {
const rules = field.dataset.validation;
if (rules) {
const parsedRules = rules.split(",").map(rule => {
const [type, ...params] = rule.trim().split(":");
return new Rule(type, params.length > 0 ? JSON.parse(params[0]) : {}, field.dataset.errorMessage || "");
});
this.validators[field.name] = new Validator(field, parsedRules);
}
}
}
bindEvents() {
this.form.addEventListener("submit", (event) => {
event.preventDefault(); // Prevent default form submission
for (const fieldName in this.validators) {
const validator = this.validators[fieldName];
if (!validator.isValid) {
const errors = validator.errors;
this.errorHandler.displayErrors(errors);
return; // Stop further processing if there are errors
}
}
// Submit the form if all validations pass
// TODO: Add logic to handle form submission (e.g., send data to server)
console.log("Form submitted successfully!");
});
// Add event listeners for individual fields as needed
// (e.g., real-time validation, debouncing)
}
}
Version 2
import React, { useState, useRef } from 'react';
import TextField from '@mui/material/TextField';
import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline';
// Validator class (similar to previous implementation)
class Validator {
constructor(field, rules) {
this.field = field;
this.rules = rules;
this.errors = [];
}
validate() {
this.errors = []; // Clear previous errors
for (const rule of this.rules) {
const isValid = rule.check(this.field.value);
if (!isValid) {
this.errors.push(rule.errorMessage);
}
}
return this.errors.length === 0;
}
get isValid() {
return this.validate();
}
}
// Rule class (similar to previous implementation)
class Rule {
constructor(type, params, errorMessage) {
this.type = type;
this.params = params;
this.errorMessage = errorMessage;
}
check(value) {
// ... (same logic as before)
}
}
const FormManager = () => {
const [formData, setFormData] = useState({
name: '',
email: '',
age: '',
});
const [errors, setErrors] = useState({});
const handleChange = (event) => {
setFormData({ ...formData, [event.target.name]: event.target.value });
const validator = validators[event.target.name];
if (validator) {
const fieldErrors = validator.errors;
setErrors({ ...errors, [event.target.name]: fieldErrors });
}
};
const handleSubmit = (event) => {
event.preventDefault();
const allErrors = {};
for (const fieldName in validators) {
const validator = validators[fieldName];
if (!validator.isValid) {
allErrors[fieldName] = validator.errors;
}
}
if (Object.keys(allErrors).length === 0) {
// Submit the form
console.log('Form submitted successfully:', formData);
} else {
setErrors(allErrors);
}
};
const validators = {
name: new Validator(formData.name, [
{ type: 'required', errorMessage: 'Name is required' },
{ type: 'minLength', params: { minLength: 3 }, errorMessage: 'Name must be at least 3 characters long' },
]),
email: new Validator(formData.email, [
{ type: 'required', errorMessage: 'Email is required' },
{ type: 'email', errorMessage: 'Invalid email format' },
]),
age: new Validator(formData.age, [
{ type: 'required', errorMessage: 'Age is required' },
{ type: 'number', errorMessage: 'Age must be a number' },
{ type: 'range', params: { min: 18, max: 120 }, errorMessage: 'Age must be between 18 and 120' },
]),
};
return (
<form onSubmit={handleSubmit}>
<TextField
label="Name"
name="name"
value={formData.name}
onChange={handleChange}
error={errors.name?.length > 0}
helperText={errors.name?.join(', ')}
// Additional MUI error props as needed
/>
<TextField
label="Email"
name="email"
value={formData.email}
onChange={handleChange}
error={errors.email?.length > 0}
helperText={errors.email?.join(', ')}
// Additional MUI error props as needed
/>
<TextField
label="Age"
name="age"
value={formData.age}
onChange={handleChange}
error={errors.age?.length > 0}
helperText={errors.age?.join(', ')}
// Additional MUI error props as needed
/>
<button type="submit">Submit</button>
</form>
);
};
export default FormManager;
By carefully considering these requirements, components, data models, optimization techniques, and additional considerations, we can create a robust, extensible, user-friendly form validation library for client-side development.
메타데이터
- post_id
- 3cf9d2b6bb85
- slug
- design-a-form-validation-library-3cf9d2b6bb85
- url
- https://medium.com/@amanrags/design-a-form-validation-library-3cf9d2b6bb85
- canonical_url
- https://medium.com/@amanrags/design-a-form-validation-library-3cf9d2b6bb85
- author_url
- https://medium.com/@amanrags
- status
- ok
- fetched_at
- 2026-07-24 09:50:29