npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

react-form-validator-plus

v1.0.0

Published

A powerful, extensible React form validation library with built-in validators and easy customization

Readme

React Form Validator Plus

A powerful, extensible React form validation library with built-in validators, custom validation support, and seamless integration patterns.

npm version License: MIT TypeScript

🚀 Features

  • Extensible Architecture: Easy to extend with custom validators and logic
  • Rich Built-in Validators: 25+ pre-built validators for common use cases
  • React Hooks: Powerful hooks for form and field validation
  • React Components: Ready-to-use validated input components
  • TypeScript Support: Full TypeScript declarations included
  • Async Validation: Built-in support for server-side validation
  • Cross-field Validation: Validate fields based on other field values
  • Real-time Validation: Configurable validation timing (onChange, onBlur, onSubmit)
  • Accessibility: ARIA attributes and screen reader support
  • Performance Optimized: Debounced validation and efficient re-renders

📦 Installation

npm install react-form-validator-plus
yarn add react-form-validator-plus
pnpm add react-form-validator-plus

🏁 Quick Start

Basic Form with Validation

import React from 'react';
import { useForm, V, ValidatedInput, FormActions } from 'react-form-validator-plus';

function ContactForm() {
  const form = useForm({
    initialValues: {
      name: '',
      email: '',
      message: ''
    },
    validationSchema: {
      name: [V.required(), V.minLength(2)],
      email: [V.required(), V.email()],
      message: [V.required(), V.minLength(10, 'Message must be at least 10 characters')]
    },
    onSubmit: async (values) => {
      console.log('Form submitted:', values);
      // Submit to API
    }
  });

  return (
    <form {...form.formProps}>
      <div>
        <label>Name:</label>
        <ValidatedInput 
          {...form.getFieldProps('name')} 
          placeholder="Enter your name"
        />
        {form.hasError('name') && (
          <div className="error">{form.getError('name')}</div>
        )}
      </div>

      <div>
        <label>Email:</label>
        <ValidatedInput 
          {...form.getFieldProps('email')} 
          type="email"
          placeholder="Enter your email"
        />
        {form.hasError('email') && (
          <div className="error">{form.getError('email')}</div>
        )}
      </div>

      <div>
        <label>Message:</label>
        <ValidatedTextarea 
          {...form.getFieldProps('message')} 
          placeholder="Enter your message"
          rows={4}
        />
        {form.hasError('message') && (
          <div className="error">{form.getError('message')}</div>
        )}
      </div>

      <FormActions 
        submitText="Send Message"
        disabled={!form.isValid}
      />
    </form>
  );
}

Using the ValidatedForm Component

import { ValidatedForm, FieldGroup, ValidatedInput, V } from 'react-form-validator-plus';

function UserForm() {
  const schema = {
    username: [V.required(), V.minLength(3), V.regex(/^[a-zA-Z0-9_]+$/)],
    email: [V.requiredEmail()],
    password: [V.strongPassword()],
    confirmPassword: [V.required(), V.confirmPassword('password')]
  };

  return (
    <ValidatedForm
      validationSchema={schema}
      onSubmit={(values) => console.log(values)}
      className="user-form"
    >
      {(form) => (
        <>
          <FieldGroup name="username" label="Username" required>
            <ValidatedInput name="username" />
          </FieldGroup>

          <FieldGroup name="email" label="Email Address" required>
            <ValidatedInput name="email" type="email" />
          </FieldGroup>

          <FieldGroup name="password" label="Password" required>
            <ValidatedInput name="password" type="password" />
          </FieldGroup>

          <FieldGroup name="confirmPassword" label="Confirm Password" required>
            <ValidatedInput name="confirmPassword" type="password" />
          </FieldGroup>

          <FormActions submitText="Create Account" showReset />
        </>
      )}
    </ValidatedForm>
  );
}

📚 Built-in Validators

String Validators

V.required('This field is required')
V.minLength(5, 'Minimum 5 characters')
V.maxLength(100, 'Maximum 100 characters')
V.length(5, 50, 'Must be 5-50 characters')
V.regex(/^[A-Za-z]+$/, 'Letters only')

Numeric Validators

V.numeric('Must be a number')
V.integer('Must be an integer')
V.min(0, 'Must be at least 0')
V.max(100, 'Must be at most 100')
V.range(1, 10, 'Must be between 1 and 10')

Format Validators

V.email('Invalid email address')
V.url('Invalid URL')
V.phone('Invalid phone number')

Password Validators

V.password({
  minLength: 8,
  requireUppercase: true,
  requireLowercase: true,
  requireNumbers: true,
  requireSpecialChars: true
})

V.confirmPassword('password', 'Passwords must match')

Date Validators

V.date('Invalid date')
V.minDate(new Date(), 'Date must be in the future')
V.maxDate('2025-12-31', 'Date must be before 2026')
V.age(18, 65, 'Must be 18-65 years old')

File Validators

V.fileType(['image/jpeg', 'image/png'], 'Only JPEG and PNG allowed')
V.fileSize(5 * 1024 * 1024, 'File must be less than 5MB')
V.fileSizeMB(10, 'File must be less than 10MB')

Selection Validators

V.oneOf(['red', 'green', 'blue'], 'Must select a valid color')
V.arrayLength(1, 3, 'Select 1-3 items')

Cross-field Validators

V.equals('password', 'Must match password')
V.different('username', 'Must be different from username')

🔧 Creating Custom Validators

1. Function Validators (Quick & Easy)

import { V } from 'react-form-validator-plus';

// Simple custom validator
const isEven = V.custom(
  (value) => parseInt(value) % 2 === 0,
  'Number must be even'
);

// Advanced custom validator with context
const uniqueUsername = V.custom(
  (value, context) => {
    const existingUsers = context.existingUsers || [];
    return !existingUsers.includes(value);
  },
  'Username already exists'
);

// Usage
const form = useForm({
  validationSchema: {
    favoriteNumber: [V.required(), V.numeric(), isEven],
    username: [V.required(), uniqueUsername]
  },
  context: {
    existingUsers: ['john', 'jane', 'bob']
  }
});

2. Class-based Validators (Full Control)

import { Validator } from 'react-form-validator-plus';

class CreditCardValidator extends Validator {
  constructor(message = 'Invalid credit card number') {
    super(message);
  }

  validate(value, context = {}) {
    if (!this.shouldValidate(value, context)) {
      return this.createResult(true);
    }

    const cleanValue = String(value).replace(/\s+/g, '');
    
    // Luhn algorithm validation
    const isValid = this.luhnCheck(cleanValue);
    return this.createResult(isValid);
  }

  luhnCheck(cardNumber) {
    let sum = 0;
    let alternate = false;
    
    for (let i = cardNumber.length - 1; i >= 0; i--) {
      let digit = parseInt(cardNumber.charAt(i), 10);
      
      if (alternate) {
        digit *= 2;
        if (digit > 9) digit = (digit % 10) + 1;
      }
      
      sum += digit;
      alternate = !alternate;
    }
    
    return sum % 10 === 0;
  }
}

// Usage
const creditCardValidator = new CreditCardValidator();

3. Async Validators

import { AsyncValidator } from 'react-form-validator-plus';

class UsernameAvailabilityValidator extends AsyncValidator {
  constructor(apiEndpoint) {
    super('Username is not available');
    this.apiEndpoint = apiEndpoint;
    this.debounce(500); // Wait 500ms before validating
  }

  async validateAsync(value, context = {}) {
    try {
      const response = await fetch(
        `${this.apiEndpoint}?username=${encodeURIComponent(value)}`,
        { signal: context.signal } // Support cancellation
      );
      
      const result = await response.json();
      
      return this.createResult(
        result.available,
        result.available ? null : 'Username is already taken'
      );
    } catch (error) {
      if (error.name === 'AbortError') throw error;
      return this.createResult(false, 'Unable to check availability');
    }
  }
}

// Usage
const form = useForm({
  validationSchema: {
    username: [
      V.required(),
      V.minLength(3),
      new UsernameAvailabilityValidator('/api/check-username')
    ]
  }
});

🎯 Advanced Usage Patterns

Form with Dynamic Fields

import { FieldArray, ConditionalField } from 'react-form-validator-plus';

function DynamicForm() {
  const form = useForm({
    initialValues: {
      contacts: [{ name: '', email: '' }],
      hasCompany: false,
      company: ''
    },
    validationSchema: {
      'contacts[].name': [V.required()],
      'contacts[].email': [V.required(), V.email()],
      company: [V.when((value, context) => context.formData.hasCompany, V.required())]
    }
  });

  return (
    <ValidatedForm {...form}>
      <FieldArray
        name="contacts"
        maxLength={5}
        addText="Add Contact"
      >
        {({ index, name, remove, canRemove }) => (
          <div key={index}>
            <ValidatedInput 
              name={`${name}.name`} 
              placeholder="Contact Name" 
            />
            <ValidatedInput 
              name={`${name}.email`} 
              type="email"
              placeholder="Contact Email" 
            />
            {canRemove && (
              <button type="button" onClick={remove}>Remove</button>
            )}
          </div>
        )}
      </FieldArray>

      <label>
        <ValidatedCheckbox 
          name="hasCompany" 
          label="I represent a company"
        />
      </label>

      <ConditionalField condition={(values) => values.hasCompany}>
        <FieldGroup name="company" label="Company Name" required>
          <ValidatedInput name="company" />
        </FieldGroup>
      </ConditionalField>

      <FormActions />
    </ValidatedForm>
  );
}

Custom Composite Validators

import { V, CompositeValidator } from 'react-form-validator-plus';

// Create reusable validator combinations
const strongEmail = () => V.all(
  V.required(),
  V.email(),
  V.custom((email) => {
    const domain = email.split('@')[1];
    const blockedDomains = ['tempmail.com', '10minutemail.com'];
    return !blockedDomains.includes(domain);
  }, 'Temporary email addresses not allowed')
);

const securePassword = () => V.all(
  V.required(),
  V.minLength(12),
  V.password({
    requireUppercase: true,
    requireLowercase: true,
    requireNumbers: true,
    requireSpecialChars: true
  }),
  V.custom((password) => {
    const commonPasswords = ['password123', 'admin123'];
    return !commonPasswords.includes(password.toLowerCase());
  }, 'Password is too common')
);

// Usage
const validationSchema = {
  email: strongEmail(),
  password: securePassword(),
  confirmPassword: [V.required(), V.confirmPassword('password')]
};

Multi-step Form Validation

import { FormProgress } from 'react-form-validator-plus';

function MultiStepForm() {
  const [currentStep, setCurrentStep] = useState(0);
  
  const steps = [
    {
      name: 'Personal Info',
      fields: ['firstName', 'lastName', 'email']
    },
    {
      name: 'Address',
      fields: ['street', 'city', 'state', 'zip']
    },
    {
      name: 'Preferences',
      fields: ['newsletter', 'notifications']
    }
  ];

  const form = useForm({
    validationSchema: {
      firstName: [V.required(), V.minLength(2)],
      lastName: [V.required(), V.minLength(2)],
      email: [V.requiredEmail()],
      street: [V.required()],
      city: [V.required()],
      state: [V.required()],
      zip: [V.required(), V.regex(/^\d{5}$/)],
      newsletter: [],
      notifications: []
    }
  });

  const canProceed = () => {
    const currentFields = steps[currentStep].fields;
    return currentFields.every(field => !form.hasError(field));
  };

  return (
    <ValidatedForm {...form}>
      <FormProgress 
        steps={steps} 
        currentStep={currentStep} 
      />

      {currentStep === 0 && (
        <div>
          <FieldGroup name="firstName" label="First Name" required>
            <ValidatedInput name="firstName" />
          </FieldGroup>
          {/* ... other step 1 fields */}
        </div>
      )}

      {/* ... other steps */}

      <div>
        {currentStep > 0 && (
          <button type="button" onClick={() => setCurrentStep(currentStep - 1)}>
            Previous
          </button>
        )}
        
        {currentStep < steps.length - 1 ? (
          <button 
            type="button" 
            onClick={() => setCurrentStep(currentStep + 1)}
            disabled={!canProceed()}
          >
            Next
          </button>
        ) : (
          <FormActions submitText="Complete Registration" />
        )}
      </div>
    </ValidatedForm>
  );
}

🔌 API Reference

Hooks

useForm(options)

Main form management hook.

Options:

  • initialValues - Initial form values
  • validationSchema - Validation rules for fields
  • validateOnChange - Validate on input change (default: true)
  • validateOnBlur - Validate on field blur (default: true)
  • validateOnSubmit - Validate on form submit (default: true)
  • debounceMs - Debounce validation (default: 300ms)
  • onSubmit - Form submit handler
  • onSubmitSuccess - Success callback
  • onSubmitError - Error callback

Returns:

  • values - Current form values
  • errors - Validation errors
  • touched - Touched field status
  • isValid - Overall form validity
  • isSubmitting - Submit status
  • setFieldValue(name, value) - Set field value
  • getFieldProps(name) - Get input props for field
  • handleSubmit - Form submit handler
  • resetForm - Reset form to initial state

useField(name, validators, options)

Individual field validation hook.

useValidation(validators, options)

Generic validation hook for any value.

Components

<ValidatedForm>

Form wrapper with built-in validation.

<ValidatedInput>

Input component with validation.

<FieldGroup>

Field wrapper with label and error display.

<ErrorMessage>

Displays validation errors.

<FormActions>

Common form buttons (submit, reset).

Validators

All validators support:

  • .withMessage(message) - Custom error message
  • .required() - Mark as required
  • .when(condition) - Conditional validation

🎨 Styling

The library provides minimal styling and CSS classes for customization:

/* Basic validation styles */
.validated-input-wrapper {
  position: relative;
}

.validated-input-wrapper input.error {
  border-color: #dc3545;
  box-shadow: 0 0 0 2px rgba(220, 53, 69, 0.25);
}

.validated-input-wrapper input.valid {
  border-color: #28a745;
  box-shadow: 0 0 0 2px rgba(40, 167, 69, 0.25);
}

.error-message {
  color: #dc3545;
  font-size: 0.875rem;
  margin-top: 0.25rem;
}

.validation-icon {
  position: absolute;
  right: 10px;
  top: 50%;
  transform: translateY(-50%);
}

.valid-icon {
  color: #28a745;
}

.error-icon {
  color: #dc3545;
}

.loading-icon {
  color: #6c757d;
  animation: spin 1s linear infinite;
}

@keyframes spin {
  from { transform: translateY(-50%) rotate(0deg); }
  to { transform: translateY(-50%) rotate(360deg); }
}

/* Form styles */
.field-group {
  margin-bottom: 1.5rem;
}

.field-label {
  display: block;
  margin-bottom: 0.5rem;
  font-weight: 500;
}

.required-indicator {
  color: #dc3545;
  margin-left: 0.25rem;
}

.form-actions {
  margin-top: 2rem;
  text-align: right;
}

.form-actions button {
  margin-left: 0.75rem;
}

🧪 Testing

The library includes comprehensive tests using React Testing Library and Vitest. Run tests with:

npm test              # Run tests in watch mode
npm run test:run      # Run tests once
npm run test:coverage # Run tests with coverage
npm run verify        # Run tests + build (CI)

Testing Forms with Validation

import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { useForm, V } from 'react-form-validator-plus';

// Test component
function TestForm() {
  const form = useForm({
    validationSchema: {
      email: [V.required(), V.email()]
    }
  });

  return (
    <form {...form.formProps}>
      <input {...form.getFieldProps('email')} data-testid="email" />
      {form.hasError('email') && (
        <div data-testid="email-error">{form.getError('email')}</div>
      )}
      <button type="submit">Submit</button>
    </form>
  );
}

// Test cases
test('shows validation error for invalid email', async () => {
  const user = userEvent.setup();
  render(<TestForm />);
  
  const emailInput = screen.getByTestId('email');
  await user.type(emailInput, 'invalid-email');
  await user.tab(); // Trigger blur

  await waitFor(() => {
    expect(screen.getByTestId('email-error')).toBeInTheDocument();
  });
});

test('clears error when valid email is entered', async () => {
  const user = userEvent.setup();
  render(<TestForm />);
  
  const emailInput = screen.getByTestId('email');
  await user.type(emailInput, '[email protected]');
  await user.tab();

  await waitFor(() => {
    expect(screen.queryByTestId('email-error')).not.toBeInTheDocument();
  });
});

Testing Custom Validators

import { CreditCardValidator } from './validators/CreditCardValidator';

describe('CreditCardValidator', () => {
  let validator;

  beforeEach(() => {
    validator = new CreditCardValidator();
  });

  test('validates correct credit card number', () => {
    const result = validator.validate('4532015112830366');
    expect(result.isValid).toBe(true);
  });

  test('rejects invalid credit card number', () => {
    const result = validator.validate('1234567890123456');
    expect(result.isValid).toBe(false);
    expect(result.message).toBe('Invalid credit card number');
  });

  test('handles empty values when not required', () => {
    const result = validator.validate('');
    expect(result.isValid).toBe(true);
  });

  test('rejects empty values when required', () => {
    validator.required();
    const result = validator.validate('');
    expect(result.isValid).toBe(false);
  });
});

📄 License

This project is licensed under the MIT License - see the LICENSE file for details.

🆓 Free to Use

This library is completely free and open source under the MIT License, which means:

Commercial Use: Use in commercial projects without restrictions
Modification: Modify and customize the code for your needs
Distribution: Distribute the original or modified versions
Private Use: Use in private/internal projects
No Attribution Required: While appreciated, not legally required
No Warranty: Provided "as-is" without warranty

You can use this library in any project - personal, commercial, or open source - without any licensing fees or restrictions!

🤝 Contributing

We welcome contributions! Please see our Contributing Guide for details.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

📊 Bundle Size

| Format | Size (gzipped) | |--------|----------------| | ESM | ~12KB | | CommonJS | ~12KB | | UMD | ~15KB |

🔗 Links

🌟 Why Choose React Form Validator Plus?

  • Extensible: Easy to extend with custom validators
  • TypeScript Ready: Full type safety out of the box
  • Performance Focused: Optimized for minimal re-renders
  • Accessible: Built-in ARIA support for screen readers
  • Flexible: Works with any UI library or custom components
  • Well Tested: Comprehensive test suite
  • Production Ready: Used in production applications
  • Active Maintenance: Regular updates and bug fixes
  • Great DX: Excellent developer experience with IntelliSense

Made with ❤️ for the React community