More Forms and Validation Libraries

Explore additional form handling and validation libraries that complement React Hook Form, Zod, and Yup for specialized use cases.

Introduction

While React Hook Form, Zod, and Yup cover most form and validation needs, the JavaScript ecosystem offers several other specialized libraries that excel in specific scenarios. This lesson explores additional tools that can enhance your form development workflow.

Additional Validation Libraries

Joi

Joi

Joi is a powerful schema description language and data validator for JavaScript. Originally designed for Node.js applications, it's now widely used for both server-side and client-side validation.

Key Features:

  • Object-oriented schema definition
  • Rich validation API
  • Conditional validation
  • Custom error messages
  • Works in Node.js and browsers

Best For:

  • API validation on the backend
  • Complex business logic validation
  • Projects already using Joi on the server
  • Teams familiar with Joi's API

Installation:

yarn add joi

Example:

import Joi from 'joi';

const schema = Joi.object({
  username: Joi.string().alphanum().min(3).max(30).required(),
  password: Joi.string().pattern(new RegExp('^[a-zA-Z0-9]{3,30}$')),
  email: Joi.string().email({ tlds: { allow: false } }),
  birth_year: Joi.number().integer().min(1900).max(2025)
});

const { error, value } = schema.validate(data);

With React Hook Form:

import { useForm } from 'react-hook-form';
import { joiResolver } from '@hookform/resolvers/joi';
import Joi from 'joi';

const schema = Joi.object({
  email: Joi.string().email().required(),
  password: Joi.string().min(8).required()
});

function LoginForm() {
  const { register, handleSubmit, formState: { errors } } = useForm({
    resolver: joiResolver(schema)
  });

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register('email')} />
      {errors.email && <span>{errors.email.message}</span>}
      
      <input {...register('password')} type="password" />
      {errors.password && <span>{errors.password.message}</span>}
      
      <button type="submit">Login</button>
    </form>
  );
}

Pros:

  • Comprehensive validation rules
  • Strong type definitions
  • Works across Node.js and browsers
  • Extensive documentation

Cons:

  • Larger bundle size (~69kB)
  • Verbose API compared to Zod
  • Less TypeScript-first than Zod

Superstruct

Superstruct is a simple and composable way to validate data in JavaScript and TypeScript. It provides a minimal, elegant API for defining and validating data structures.

Key Features:

  • Simple, composable API
  • TypeScript support
  • Small bundle size (~6kB)
  • Runtime type checking
  • Custom error messages

Best For:

  • Projects prioritizing simplicity
  • TypeScript projects needing runtime validation
  • API response validation
  • Data transformation pipelines

Installation:

yarn add superstruct

Example:

import { object, string, number, min, size } from 'superstruct';

const User = object({
  name: size(string(), 1, 50),
  email: string(),
  age: min(number(), 18)
});

const data = { name: 'John', email: 'john@example.com', age: 25 };
const [error, user] = User.validate(data);

With React Hook Form:

import { useForm } from 'react-hook-form';
import { superstructResolver } from '@hookform/resolvers/superstruct';
import { object, string, min, size } from 'superstruct';

const schema = object({
  username: size(string(), 3, 20),
  email: string(),
  password: min(string(), 8)
});

function RegistrationForm() {
  const { register, handleSubmit } = useForm({
    resolver: superstructResolver(schema)
  });
  
  // Form implementation
}

Pros:

  • Very small bundle size
  • Clean, minimal API
  • Good TypeScript support
  • Easy to learn

Cons:

  • Less feature-rich than Joi or Yup
  • Smaller community
  • Fewer built-in validators

Vest

Vest is a validation framework inspired by unit testing libraries. It provides a declarative way to write validations that feel like writing tests.

Key Features:

  • Test-like syntax
  • Async validation support
  • Field-level validation
  • Conditional validation
  • Framework agnostic

Best For:

  • Teams familiar with testing frameworks
  • Complex conditional validations
  • Forms with dependent fields
  • Progressive validation

Installation:

yarn add vest

Example:

import { create, test, enforce } from 'vest';

const suite = create((data = {}) => {
  test('username', 'Username is required', () => {
    enforce(data.username).isNotEmpty();
  });

  test('username', 'Username must be at least 3 characters', () => {
    enforce(data.username).longerThanOrEquals(3);
  });

  test('email', 'Email is invalid', () => {
    enforce(data.email).matches(/^[^@]+@[^@]+\.[^@]+$/);
  });
});

const result = suite(formData);
console.log(result.hasErrors()); // boolean
console.log(result.getErrors('username')); // array of errors

Pros:

  • Familiar syntax for developers who write tests
  • Excellent for complex validation logic
  • Framework agnostic
  • Good async support

Cons:

  • Different paradigm from traditional validators
  • Smaller ecosystem
  • Steeper learning curve

Alternative Form Libraries

Formik

Formik

Formik was once the most popular React form library. While React Hook Form has become more popular due to better performance, Formik remains a solid choice for many projects.

Key Features:

  • Component-based and hook-based API
  • Built-in validation support
  • Field-level and form-level validation
  • Works with Yup out of the box
  • Large ecosystem

Best For:

  • Existing projects using Formik
  • Teams experienced with Formik
  • Projects requiring component-based forms

Installation:

yarn add formik

Example:

import { useFormik } from 'formik';
import * as Yup from 'yup';

const validationSchema = Yup.object({
  email: Yup.string().email().required(),
  password: Yup.string().min(8).required()
});

function LoginForm() {
  const formik = useFormik({
    initialValues: { email: '', password: '' },
    validationSchema,
    onSubmit: (values) => {
      console.log(values);
    }
  });

  return (
    <form onSubmit={formik.handleSubmit}>
      <input
        name="email"
        value={formik.values.email}
        onChange={formik.handleChange}
        onBlur={formik.handleBlur}
      />
      {formik.touched.email && formik.errors.email && (
        <span>{formik.errors.email}</span>
      )}

      <input
        name="password"
        type="password"
        value={formik.values.password}
        onChange={formik.handleChange}
        onBlur={formik.handleBlur}
      />
      {formik.touched.password && formik.errors.password && (
        <span>{formik.errors.password}</span>
      )}

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

Pros:

  • Mature and stable
  • Extensive documentation
  • Large community
  • Works well with Yup

Cons:

  • More re-renders than React Hook Form
  • Larger bundle size
  • More boilerplate code

React Final Form

React Final Form is a high-performance form library that uses a subscription-based state management approach to minimize re-renders.

Key Features:

  • Subscription-based rendering
  • Framework-agnostic core
  • Array and async validation support
  • Small bundle size
  • Zero dependencies

Best For:

  • Performance-critical applications
  • Complex forms with many fields
  • Projects needing fine-grained control

Installation:

yarn add react-final-form final-form

Example:

import { Form, Field } from 'react-final-form';

function MyForm() {
  const onSubmit = (values) => {
    console.log(values);
  };

  const validate = (values) => {
    const errors = {};
    if (!values.email) {
      errors.email = 'Required';
    }
    if (!values.password || values.password.length < 8) {
      errors.password = 'Must be at least 8 characters';
    }
    return errors;
  };

  return (
    <Form
      onSubmit={onSubmit}
      validate={validate}
      render={({ handleSubmit, submitting }) => (
        <form onSubmit={handleSubmit}>
          <Field name="email">
            {({ input, meta }) => (
              <div>
                <input {...input} type="email" placeholder="Email" />
                {meta.error && meta.touched && <span>{meta.error}</span>}
              </div>
            )}
          </Field>

          <Field name="password">
            {({ input, meta }) => (
              <div>
                <input {...input} type="password" placeholder="Password" />
                {meta.error && meta.touched && <span>{meta.error}</span>}
              </div>
            )}
          </Field>

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

Pros:

  • Excellent performance
  • Minimal re-renders
  • Flexible API
  • Good for complex forms

Cons:

  • Render props can be verbose
  • Smaller community than Formik
  • Less modern than React Hook Form

Specialized Tools

@hookform/resolvers

@hookform/resolvers provides validation resolvers for React Hook Form that work with various schema validation libraries.

Supported Libraries:

  • Zod
  • Yup
  • Joi
  • Superstruct
  • Vest
  • Ajv
  • And more

Installation:

yarn add @hookform/resolvers

Usage:

import { zodResolver } from '@hookform/resolvers/zod';
import { yupResolver } from '@hookform/resolvers/yup';
import { joiResolver } from '@hookform/resolvers/joi';

// Use with React Hook Form
const { register } = useForm({
  resolver: zodResolver(schema)  // or yupResolver, joiResolver, etc.
});

Validator.js

Validator.js is a library of string validators and sanitizers. While not a complete form solution, it's useful for custom validation logic.

Installation:

yarn add validator

Example:

import validator from 'validator';

const isValid = validator.isEmail('test@example.com'); // true
const isCreditCard = validator.isCreditCard('4111111111111111'); // true
const isURL = validator.isURL('https://example.com'); // true

Common Validators:

  • isEmail()
  • isURL()
  • isCreditCard()
  • isIP()
  • isMobilePhone()
  • isPostalCode()
  • isJSON()
  • isJWT()

Comparison Table

LibraryTypeBundle SizeTypeScriptLearning CurveBest For
React Hook FormForm Management~8.6kBExcellentMediumMost projects
ZodValidation~8kBNativeLowTypeScript projects
YupValidation~15kBGoodLowMature validation
JoiValidation~69kBGoodMediumBackend + Frontend
SuperstructValidation~6kBGoodLowMinimal validation
VestValidation~5kBGoodMediumTest-like validation
FormikForm Management~15kBGoodLowLegacy projects
React Final FormForm Management~7kBGoodMediumPerformance critical

When to Use Each

Use React Hook Form + Zod When:

  • Starting a new TypeScript project
  • Need excellent performance and type safety
  • Want modern, minimal API
  • Prioritize developer experience

Use React Hook Form + Yup When:

  • Need proven, stable validation
  • Working with existing Yup schemas
  • Require internationalization
  • Team familiar with Yup

Use React Hook Form + Joi When:

  • Sharing validation between frontend and backend
  • Need comprehensive validation rules
  • Already using Joi on server
  • Complex business logic validation

Use Formik When:

  • Maintaining existing Formik projects
  • Team highly experienced with Formik
  • Component-based approach preferred
  • Migration cost is too high

Use React Final Form When:

  • Performance is absolutely critical
  • Need fine-grained subscription control
  • Working with very large forms
  • Framework-agnostic core needed

Use Vest When:

  • Validation logic is very complex
  • Team familiar with testing frameworks
  • Need progressive validation
  • Conditional validation is primary concern

Combining Libraries

You can often combine these libraries for optimal results:

Best Modern Stack

// React Hook Form + Zod + Validator.js
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import validator from 'validator';

const schema = z.object({
  email: z.string().refine((val) => validator.isEmail(val)),
  phone: z.string().refine((val) => validator.isMobilePhone(val)),
  url: z.string().refine((val) => validator.isURL(val))
});

Enterprise Stack

// React Hook Form + Yup + Custom Validators
import { useForm } from 'react-hook-form';
import { yupResolver } from '@hookform/resolvers/yup';
import * as yup from 'yup';

const schema = yup.object({
  email: yup.string().email().required(),
  // Add custom business logic validators
});

Best Practices

1. Choose Based on Project Needs

  • New projects: React Hook Form + Zod
  • Existing projects: Keep current libraries unless there's a strong reason to migrate
  • Backend sharing: Consider Joi

2. Maintain Consistency

  • Use the same validation library across your project
  • Create reusable schema patterns
  • Document validation rules

3. Performance Considerations

  • React Hook Form offers best performance for forms
  • Use schema validation for complex logic
  • Leverage lazy validation when appropriate

4. Type Safety

  • Prefer TypeScript-first libraries (Zod)
  • Use type inference where available
  • Keep types and validation in sync

5. Developer Experience

  • Choose libraries with good documentation
  • Consider team familiarity
  • Evaluate community support and ecosystem

Migration Guide

From Formik to React Hook Form

Formik:

const formik = useFormik({
  initialValues: { email: '' },
  onSubmit: (values) => console.log(values)
});

<input
  value={formik.values.email}
  onChange={formik.handleChange}
/>

React Hook Form:

const { register, handleSubmit } = useForm({
  defaultValues: { email: '' }
});

<input {...register('email')} />

From Yup to Zod

Yup:

const schema = yup.object({
  email: yup.string().email().required(),
  age: yup.number().min(18).required()
});

Zod:

const schema = z.object({
  email: z.string().email(),
  age: z.number().min(18)
});