A lightweight, production-ready form validation library for React. Zero dependencies, full TypeScript support, and comprehensive validation rules out of the box.
✅ 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
npm install react-smart-form-errorsimport { 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>
);
}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
}
});{
// 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
}Supports both React events and manual usage:
// React event handler
<input onChange={form.handleChange} />
// Manual usage
form.handleChange('email', 'test@example.com');Supports both React events and manual usage:
// React event handler
<input onBlur={form.handleBlur} />
// Manual usage
form.handleBlur('email');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',
}
Validates full names (must have at least first and last name).
rules: {
fullName: 'fullName',
}Validates first names.
rules: {
firstName: 'firstName',
}Validates last names.
rules: {
lastName: 'lastName',
}Validates usernames (alphanumeric and underscore only).
rules: {
username: [
'required',
{ rule: 'username', minLength: 3, maxLength: 20 },
],
}Validates password strength (requires uppercase, lowercase, number, and special character).
rules: {
password: [
'required',
{ rule: 'password', minLength: 8 },
],
}Validates that a password matches another field.
rules: {
password: ['required', { rule: 'password', minLength: 8 }],
confirmPassword: [
'required',
{ rule: 'confirmPassword', password: values.password },
],
}Validates date of birth with optional minimum age check.
rules: {
dob: [
'required',
{ rule: 'dob', minAge: 18 },
],
}Supports YYYY-MM-DD format or Date objects.
Validates HTTP/HTTPS URLs.
rules: {
website: 'url',
}Validates numeric values with optional min/max constraints.
rules: {
age: [
'required',
{ rule: 'number', min: 0, max: 150 },
],
}Validates minimum string length.
rules: {
bio: ['required', { rule: 'minLength', length: 10 }],
}Validates maximum string length.
rules: {
title: { rule: 'maxLength', length: 100 },
}Validates against a regular expression.
rules: {
zipCode: {
rule: 'pattern',
regex: /^\d{5}$/,
},
}{
initialValues?: Record<string, any>,
rules?: ValidationRulesMap,
messages?: ErrorMessages,
}{
// 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,
}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 invalidThe 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
}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()}`,
},
});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;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>
);
}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>
);
}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>
);
}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>
);
}- Chrome (latest)
- Firefox (latest)
- Safari (latest)
- Edge (latest)
- Bundle Size: ~5KB gzipped
- Zero Dependencies: No external libraries
- Tree-shakeable: Import only what you need
MIT
Contributions are welcome! Please feel free to submit a Pull Request.
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>
);
}- ✅ 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
See docs for complete API reference and examples.
MIT Shaeel Khan