FE Master

Build Your Own Form Validation Library

DifficultyMedium
Estimated time: 2-3 hours
0 done

Description

# Build Your Own Form Validation Library Form validation is one of the most common frontend tasks, yet many developers rely heavily on libraries without understanding the underlying mechanics. This c...

Requirements

  • **Rule-Based Validation**: Support for common validation rules (required, email, min/max length, etc.)
  • **Custom Validators**: Allow developers to define custom validation functions
  • **Real-Time Validation**: Validate fields as users type (with debouncing)
  • **Error Message Management**: Display and clear error messages dynamically
  • **Form-Level Validation**: Validate entire forms before submission
  • **Async Validation**: Support for server-side validation (e.g., checking if username exists)

Overview

Form validation is one of the most common frontend tasks, yet many developers rely heavily on libraries without understanding the underlying mechanics. This challenge will guide you through building a robust, reusable form validation library from scratch.

Getting Started

## Step Zero: Set Up Your Environment - **Choose Your Approach**: Vanilla JavaScript or integrate with a framework - **Create Test HTML**: Build a sample form with various input types to test your library

Implementation Guide

Build Your Own Form Validation Library

Form validation is one of the most common frontend tasks, yet many developers rely heavily on libraries without understanding the underlying mechanics. This challenge will guide you through building a robust, reusable form validation library from scratch.

The Challenge - Building a Form Validation Library

The functional requirements for your validation library include:

  • Rule-Based Validation: Support for common validation rules (required, email, min/max length, etc.)
  • Custom Validators: Allow developers to define custom validation functions
  • Real-Time Validation: Validate fields as users type (with debouncing)
  • Error Message Management: Display and clear error messages dynamically
  • Form-Level Validation: Validate entire forms before submission
  • Async Validation: Support for server-side validation (e.g., checking if username exists)

Step Zero: Set Up Your Environment

  • Choose Your Approach: Vanilla JavaScript or integrate with a framework
  • Create Test HTML: Build a sample form with various input types to test your library

Step One: Create the Core Validator Class

Build the foundation of your validation library.

Example:

class FormValidator {
    constructor(formElement) {
        this.form = formElement;
        this.fields = new Map();
        this.errors = new Map();
    }
    
    addField(fieldName, rules) {
        this.fields.set(fieldName, rules);
        return this;
    }
}

Step Two: Implement Built-in Validation Rules

Create common validation rules that developers will need.

Example:

const validationRules = {
    required: (value) => ({
        valid: value.trim().length > 0,
        message: 'This field is required'
    }),
    
    email: (value) => ({
        valid: /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value),
        message: 'Please enter a valid email'
    }),
    
    minLength: (min) => (value) => ({
        valid: value.length >= min,
        message: `Must be at least ${min} characters`
    }),
    
    maxLength: (max) => (value) => ({
        valid: value.length <= max,
        message: `Must be no more than ${max} characters`
    }),
    
    pattern: (regex, message) => (value) => ({
        valid: regex.test(value),
        message: message || 'Invalid format'
    })
};

Step Three: Implement Field-Level Validation

Add the ability to validate individual fields.

Example:

validateField(fieldName, value) {
    const rules = this.fields.get(fieldName);
    const errors = [];
    
    for (const rule of rules) {
        const result = rule(value);
        if (!result.valid) {
            errors.push(result.message);
        }
    }
    
    this.errors.set(fieldName, errors);
    return errors.length === 0;
}

Step Four: Add Real-Time Validation with Debouncing

Validate as users type, but debounce to avoid excessive validation calls.

Example:

attachRealTimeValidation(fieldName, delay = 300) {
    const input = this.form.querySelector(`[name="${fieldName}"]`);
    let timeout;
    
    input.addEventListener('input', (e) => {
        clearTimeout(timeout);
        timeout = setTimeout(() => {
            this.validateField(fieldName, e.target.value);
            this.displayErrors(fieldName);
        }, delay);
    });
}

Step Five: Implement Error Display System

Create a system to show and hide error messages dynamically.

Example:

displayErrors(fieldName) {
    const errors = this.errors.get(fieldName);
    const errorContainer = this.form.querySelector(
        `[data-error-for="${fieldName}"]`
    );
    
    if (errorContainer) {
        if (errors && errors.length > 0) {
            errorContainer.innerHTML = errors.join('<br>');
            errorContainer.style.display = 'block';
        } else {
            errorContainer.style.display = 'none';
        }
    }
}

Step Six: Add Form-Level Validation

Validate all fields when the form is submitted.

Example:

validateForm() {
    let isValid = true;
    
    for (const [fieldName] of this.fields) {
        const input = this.form.querySelector(`[name="${fieldName}"]`);
        const valid = this.validateField(fieldName, input.value);
        this.displayErrors(fieldName);
        
        if (!valid) isValid = false;
    }
    
    return isValid;
}

Step Seven: Support Async Validation

Add support for validation that requires server calls.

Example:

async asyncRule(fieldName, validatorFn) {
    const input = this.form.querySelector(`[name="${fieldName}"]`);
    const value = input.value;
    
    try {
        const result = await validatorFn(value);
        return result;
    } catch (error) {
        return { valid: false, message: 'Validation failed' };
    }
}

Step Eight: Create Custom Validators

Allow developers to add their own validation logic.

Example:

validator.addField('password', [
    validationRules.required,
    validationRules.minLength(8),
    (value) => ({
        valid: /[A-Z]/.test(value) && /[0-9]/.test(value),
        message: 'Password must contain uppercase and number'
    })
]);

Step Nine: Build a Complete Demo

Create a registration form with:

  • Email validation
  • Password strength checking
  • Password confirmation matching
  • Username availability checking (async)
  • Terms acceptance (checkbox validation)

Step Ten: Optional - Add Accessibility Features

  • Ensure error messages are announced to screen readers
  • Add proper ARIA attributes
  • Handle keyboard navigation

The Final Step: Clean Up and Document

  • Write clear API documentation
  • Create usage examples for common scenarios
  • Add TypeScript definitions if applicable

Help Others by Sharing Your Solutions!

Form validation is a universal need. Share your library and help other developers build better, more accessible forms!

CrackIt Dev - Master Frontend Interviews | FE Master | System Design, DSA, JavaScript