Skip to content

shaeel45/react-smart-form-errors

Repository files navigation

React Smart Form Errors

A lightweight, production-ready form validation library for React. Zero dependencies, full TypeScript support, and comprehensive validation rules out of the box.

Features

15+ Built-in Validators - email, phone, password, DOB, URLs, and more ✅ Lightweight - No external dependencies, tiny bundle size ✅ TypeScript Support - Complete type definitions included ✅ Flexible - String rules, object rules with options, or custom functions ✅ Framework Agnostic - Works with React useState, React Hook Form, or Formik ✅ Accessible - Full form state management with touched/untouched tracking ✅ Customizable Messages - Override default error messages globally or per-field ✅ 90%+ Test Coverage - Comprehensive test suite with Vitest

Installation

npm install react-smart-form-errors

Quick Start

import { useSmartForm } from 'react-smart-form-errors';

function RegistrationForm() {
  const form = useSmartForm({
    initialValues: {
      email: '',
      password: '',
      fullName: '',
      phone: '',
      dob: ''
    },
    rules: {
      email: ['required', 'email'],
      password: ['required', { rule: 'password', minLength: 8 }],
      fullName: ['required', 'fullname'],
      phone: ['required', 'phone'],
      dob: ['required', { rule: 'dob', minAge: 18 }]
    }
  });

  const handleSubmit = (e) => {
    e.preventDefault();
    if (form.validateForm()) {
      console.log('Form is valid!', form.values);
      // Submit form
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <input
          name="email"
          type="email"
          placeholder="Email"
          value={form.values.email}
          onChange={form.handleChange}
          onBlur={form.handleBlur}
        />
        {form.getFieldError('email') && (
          <span className="error">{form.getFieldError('email')}</span>
        )}
      </div>

      <div>
        <input
          name="password"
          type="password"
          placeholder="Password"
          value={form.values.password}
          onChange={form.handleChange}
          onBlur={form.handleBlur}
        />
        {form.getFieldError('password') && (
          <span className="error">{form.getFieldError('password')}</span>
        )}
      </div>

      <div>
        <input
          name="fullName"
          placeholder="Full Name"
          value={form.values.fullName}
          onChange={form.handleChange}
          onBlur={form.handleBlur}
        />
        {form.getFieldError('fullName') && (
          <span className="error">{form.getFieldError('fullName')}</span>
        )}
      </div>

      <div>
        <input
          name="phone"
          type="tel"
          placeholder="Phone"
          value={form.values.phone}
          onChange={form.handleChange}
          onBlur={form.handleBlur}
        />
        {form.getFieldError('phone') && (
          <span className="error">{form.getFieldError('phone')}</span>
        )}
      </div>

      <div>
        <input
          name="dob"
          type="date"
          value={form.values.dob}
          onChange={form.handleChange}
          onBlur={form.handleBlur}
        />
        {form.getFieldError('dob') && (
          <span className="error">{form.getFieldError('dob')}</span>
        )}
      </div>

      <button type="submit" disabled={!form.isValid}>
        Register
      </button>
    </form>
  );
}

Hook API

useSmartForm(config)

Configuration

const form = useSmartForm({
  // Initial form values
  initialValues: {
    email: '',
    password: '',
    fullName: '',
    phone: '',
    dob: ''
  },
  
  // Validation rules - string, array, object, or function
  rules: {
    email: ['required', 'email'],
    password: ['required', { rule: 'password', minLength: 8 }],
    fullName: ['required', 'fullname'],
    phone: ['required', 'phone'],
    dob: ['required', { rule: 'dob', minAge: 18 }]
  },
  
  // Custom error messages (optional)
  messages: {
    required: (field) => `${field} is required`,
    email: (field) => `${field} is not a valid email`,
    // ... override other messages
  }
});

Return Object

{
  // Form values
  values,           // Current form values
  errors,           // Object with field errors
  touched,          // Fields that have been interacted with
  
  // Validation methods
  validateField,    // Validate a single field
  validateForm,     // Validate all fields
  getFieldError,    // Get error message for a field (returns string)
  
  // Value setters
  setValue,         // Set single field value
  setValues,        // Set multiple field values
  setError,         // Set error manually
  setTouched,       // Set touched state
  resetForm,        // Reset to initial state
  
  // Event handlers
  handleChange,     // Handle input changes
  handleBlur,       // Handle field blur
  
  // Computed state
  isValid,          // True if no errors
  isDirty           // True if values differ from initialValues
}

Event Handlers

handleChange

Supports both React events and manual usage:

// React event handler
<input onChange={form.handleChange} />

// Manual usage
form.handleChange('email', 'test@example.com');

handleBlur

Supports both React events and manual usage:

// React event handler
<input onBlur={form.handleBlur} />

// Manual usage
form.handleBlur('email');

Import Validators

You can import and use validators directly:

import {
  useSmartForm,
  validateEmail,
  validatePhone,
  validatePassword,
  validateDOB,
  validateFullName,
  validateRequired
} from 'react-smart-form-errors';

// Use in custom validation
const customEmail = validateEmail('test@example.com');
const customPhone = validatePhone('03001234567');

#### `phone`
Validates phone numbers (supports Pakistani format by default: 03001234567, +923001234567, 923001234567).

```javascript
rules: {
  phone: 'phone',
}

Text Validators

fullName

Validates full names (must have at least first and last name).

rules: {
  fullName: 'fullName',
}

firstName

Validates first names.

rules: {
  firstName: 'firstName',
}

lastName

Validates last names.

rules: {
  lastName: 'lastName',
}

username

Validates usernames (alphanumeric and underscore only).

rules: {
  username: [
    'required',
    { rule: 'username', minLength: 3, maxLength: 20 },
  ],
}

Password Validators

password

Validates password strength (requires uppercase, lowercase, number, and special character).

rules: {
  password: [
    'required',
    { rule: 'password', minLength: 8 },
  ],
}

confirmPassword

Validates that a password matches another field.

rules: {
  password: ['required', { rule: 'password', minLength: 8 }],
  confirmPassword: [
    'required',
    { rule: 'confirmPassword', password: values.password },
  ],
}

Specialized Validators

dob (Date of Birth)

Validates date of birth with optional minimum age check.

rules: {
  dob: [
    'required',
    { rule: 'dob', minAge: 18 },
  ],
}

Supports YYYY-MM-DD format or Date objects.

url

Validates HTTP/HTTPS URLs.

rules: {
  website: 'url',
}

number

Validates numeric values with optional min/max constraints.

rules: {
  age: [
    'required',
    { rule: 'number', min: 0, max: 150 },
  ],
}

Length & Pattern Validators

minLength

Validates minimum string length.

rules: {
  bio: ['required', { rule: 'minLength', length: 10 }],
}

maxLength

Validates maximum string length.

rules: {
  title: { rule: 'maxLength', length: 100 },
}

pattern

Validates against a regular expression.

rules: {
  zipCode: {
    rule: 'pattern',
    regex: /^\d{5}$/,
  },
}

Hook API

useSmartForm(config)

Parameters

{
  initialValues?: Record<string, any>,
  rules?: ValidationRulesMap,
  messages?: ErrorMessages,
}

Return Value

{
  // State
  values: Record<string, any>,
  errors: Record<string, ValidationError>,
  touched: Record<string, boolean>,

  // Validation methods
  validateField: (fieldName, value) => ValidationError | null,
  validateForm: () => boolean,
  getFieldError: (fieldName) => string | null,

  // State setters
  setValue: (fieldName, value) => void,
  setError: (fieldName, error) => void,
  setValues: (values) => void,
  setTouched: (touched) => void,
  resetForm: () => void,

  // Event handlers
  handleChange: (e: ChangeEvent) => void,
  handleBlur: (e: FocusEvent) => void,

  // Computed state
  isValid: boolean,
  isDirty: boolean,
}

Direct Validator Imports

You can also import validators directly without the hook:

import {
  validateEmail,
  validatePhone,
  validatePassword,
  validateDOB,
  validateFullName,
  validateUsername,
  validateURL,
  validateNumber,
} from 'react-smart-form-errors';

const error = validateEmail('test@example.com');
// error will be null if valid, or an error object if invalid

Error Messages

Default Messages

The library comes with sensible default error messages:

{
  required: (field) => `${field} is required`,
  email: (field) => `${field} must be a valid email address`,
  phone: (field) => `${field} must be a valid phone number`,
  password: {
    minLength: (field, value) => `${field} must be at least ${value} characters`,
    uppercase: (field) => `${field} must contain at least one uppercase letter`,
    lowercase: (field) => `${field} must contain at least one lowercase letter`,
    number: (field) => `${field} must contain at least one number`,
    special: (field) => `${field} must contain at least one special character`,
  },
  // ... more messages
}

Custom Messages

Override messages globally or per-validation:

import { useSmartForm, messages } from 'react-smart-form-errors';

const { values, errors, ... } = useSmartForm({
  initialValues: { email: '' },
  rules: { email: ['required', 'email'] },
  messages: {
    ...messages,
    required: (field) => `Please fill in ${field}`,
    email: (field) => `Please provide a valid ${field.toLowerCase()}`,
  },
});

Examples

React with useState

import { useSmartForm } from 'react-smart-form-errors';

function SignupForm() {
  const form = useSmartForm({
    initialValues: {
      email: '',
      password: '',
      fullName: '',
      phone: '',
      dob: '',
    },
    rules: {
      email: ['required', 'email'],
      password: ['required', { rule: 'password', minLength: 8 }],
      fullName: ['required', 'fullName'],
      phone: ['required', 'phone'],
      dob: ['required', { rule: 'dob', minAge: 18 }],
    },
  });

  const handleSubmit = (e) => {
    e.preventDefault();
    if (form.validateForm()) {
      console.log('Signup:', form.values);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <input
          name="email"
          type="email"
          value={form.values.email}
          onChange={form.handleChange}
          onBlur={form.handleBlur}
          placeholder="Email"
        />
        {form.touched.email && form.errors.email && (
          <span>{form.getFieldError('email')}</span>
        )}
      </div>

      <div>
        <input
          name="password"
          type="password"
          value={form.values.password}
          onChange={form.handleChange}
          onBlur={form.handleBlur}
          placeholder="Password"
        />
        {form.touched.password && form.errors.password && (
          <span>{form.getFieldError('password')}</span>
        )}
      </div>

      <div>
        <input
          name="fullName"
          value={form.values.fullName}
          onChange={form.handleChange}
          onBlur={form.handleBlur}
          placeholder="Full Name"
        />
        {form.touched.fullName && form.errors.fullName && (
          <span>{form.getFieldError('fullName')}</span>
        )}
      </div>

      <div>
        <input
          name="phone"
          value={form.values.phone}
          onChange={form.handleChange}
          onBlur={form.handleBlur}
          placeholder="Phone"
        />
        {form.touched.phone && form.errors.phone && (
          <span>{form.getFieldError('phone')}</span>
        )}
      </div>

      <div>
        <input
          name="dob"
          type="date"
          value={form.values.dob}
          onChange={form.handleChange}
          onBlur={form.handleBlur}
        />
        {form.touched.dob && form.errors.dob && (
          <span>{form.getFieldError('dob')}</span>
        )}
      </div>

      <button type="submit" disabled={!form.isValid && form.isDirty}>
        Sign Up
      </button>
    </form>
  );
}

export default SignupForm;

React Hook Form Integration

import { useForm } from 'react-hook-form';
import { validators } from 'react-smart-form-errors';

function MyForm() {
  const { register, formState: { errors }, handleSubmit } = useForm();

  const onSubmit = (data) => console.log(data);

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input
        {...register('email', {
          validate: (value) => {
            const error = validators.email(value);
            return error ? 'Invalid email' : true;
          },
        })}
      />
      {errors.email && <span>{errors.email.message}</span>}

      <input
        {...register('password', {
          validate: (value) => {
            const error = validators.password(value, { minLength: 8 });
            return error ? 'Password too weak' : true;
          },
        })}
      />

      <button type="submit">Submit</button>
    </form>
  );
}

Formik Integration

import { Formik, Form, Field, ErrorMessage } from 'formik';
import { validateEmail, validatePhone, validatePassword } from 'react-smart-form-errors';

const validationSchema = {
  email: (value) => validateEmail(value),
  phone: (value) => validatePhone(value),
  password: (value) => validatePassword(value, { minLength: 8 }),
};

function MyForm() {
  return (
    <Formik
      initialValues={{ email: '', phone: '', password: '' }}
      validate={(values) => {
        const errors = {};
        Object.keys(values).forEach((key) => {
          const error = validationSchema[key]?.(values[key]);
          if (error) errors[key] = error.type;
        });
        return errors;
      }}
      onSubmit={(values) => console.log(values)}
    >
      <Form>
        <Field name="email" type="email" />
        <ErrorMessage name="email" component="div" />

        <Field name="phone" />
        <ErrorMessage name="phone" component="div" />

        <Field name="password" type="password" />
        <ErrorMessage name="password" component="div" />

        <button type="submit">Submit</button>
      </Form>
    </Formik>
  );
}

Custom Validators

import { useSmartForm } from 'react-smart-form-errors';

function MyForm() {
  const customEvenNumber = (value) => {
    const num = Number(value);
    if (isNaN(num) || num % 2 !== 0) {
      return { type: 'evenNumber' };
    }
    return null;
  };

  const form = useSmartForm({
    initialValues: { number: '' },
    rules: {
      number: ['required', customEvenNumber],
    },
    messages: {
      evenNumber: (field) => `${field} must be an even number`,
    },
  });

  return (
    <div>
      <input
        name="number"
        value={form.values.number}
        onChange={form.handleChange}
      />
      {form.errors.number && <div>{form.getFieldError('number')}</div>}
    </div>
  );
}

TypeScript Usage

import { useSmartForm, ValidationRulesMap, UseSmartFormReturn } from 'react-smart-form-errors';

interface FormValues {
  email: string;
  password: string;
  fullName: string;
}

const rules: ValidationRulesMap = {
  email: ['required', 'email'],
  password: ['required', { rule: 'password', minLength: 8 }],
  fullName: ['required', 'fullName'],
};

function MyForm(): JSX.Element {
  const form: UseSmartFormReturn = useSmartForm({
    initialValues: {
      email: '',
      password: '',
      fullName: '',
    },
    rules,
  });

  return (
    <form onSubmit={(e) => {
      e.preventDefault();
      if (form.validateForm()) {
        const data: FormValues = form.values as FormValues;
        console.log(data);
      }
    }}>
      {/* Form fields */}
    </form>
  );
}

Browser Support

  • Chrome (latest)
  • Firefox (latest)
  • Safari (latest)
  • Edge (latest)

Performance

  • Bundle Size: ~5KB gzipped
  • Zero Dependencies: No external libraries
  • Tree-shakeable: Import only what you need

License

MIT

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Support

For issues and questions, please open an issue on GitHub.

import { useFormErrors } from 'react-smart-form-errors';

function MyForm() {
    const { errors, validate, clearErrors } = useFormErrors();

    const handleSubmit = async (formData) => {
        if (validate(formData)) {
            // Submit form
        }
    };

    return (
        <form onSubmit={handleSubmit}>
            <input name="email" />
            {errors.email && <span>{errors.email}</span>}
        </form>
    );
}

Benefits

  • ✅ Reduce boilerplate code for error handling
  • ✅ Improve user experience with instant validation feedback
  • ✅ Maintain clean, readable component code
  • ✅ Reusable validation logic across your application
  • ✅ Better accessibility and error messaging

Documentation

See docs for complete API reference and examples.

License

MIT Shaeel Khan

About

Lightweight React form validation and smart error handling library

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors