UNPKG

@hookform/resolvers

Version:

React Hook Form validation resolvers: Yup, Joi, Superstruct, Zod, Vest, Class Validator, io-ts, Nope, computed-types, TypeBox, arktype, Typanion, Effect-TS and VineJS

927 lines (732 loc) 26 kB
<div align="center"> <p align="center"> <a href="https://react-hook-form.com" title="React Hook Form - Simple React forms validation"> <img src="https://raw.githubusercontent.com/bluebill1049/react-hook-form/master/docs/logo.png" alt="React Hook Form Logo - React hook custom hook for form validation" /> </a> </p> </div> <p align="center">Performant, flexible and extensible forms with easy to use validation.</p> <div align="center"> [![npm downloads](https://img.shields.io/npm/dm/@hookform/resolvers.svg?style=for-the-badge)](https://www.npmjs.com/package/@hookform/resolvers) [![npm](https://img.shields.io/npm/dt/@hookform/resolvers.svg?style=for-the-badge)](https://www.npmjs.com/package/@hookform/resolvers) [![npm](https://img.shields.io/bundlephobia/minzip/@hookform/resolvers?style=for-the-badge)](https://bundlephobia.com/result?p=@hookform/resolvers) </div> ## React Hook Form Resolvers This function allows you to use any external validation library such as Yup, Zod, Joi, Vest, Ajv and many others. The goal is to make sure you can seamlessly integrate whichever validation library you prefer. If you're not using a library, you can always write your own logic to validate your forms. ## Install Install your preferred validation library alongside `@hookform/resolvers`. npm install @hookform/resolvers # npm yarn add @hookform/resolvers # yarn pnpm install @hookform/resolvers # pnpm bun install @hookform/resolvers # bun <details> <summary>Resolver Comparison</summary> | resolver | Infer values <br /> from schema | [criteriaMode](https://react-hook-form.com/docs/useform#criteriaMode) | |---|---|---| | AJV | ❌ | `firstError | all` | | Arktype | ✅ | `firstError` | | class-validator | ✅ | `firstError | all` | | computed-types | ✅ | `firstError` | | Effect | ✅ | `firstError | all` | | fluentvalidation-ts | ❌ | `firstError` | | io-ts | ✅ | `firstError` | | joi | ❌ | `firstError | all` | | Nope | ❌ | `firstError` | | Standard Schema | ✅ | `firstError | all` | | Superstruct | ✅ | `firstError` | | typanion | ✅ | `firstError` | | typebox | ✅ | `firstError | all` | | typeschema | ❌ | `firstError | all` | | valibot | ✅ | `firstError | all` | | vest | ❌ | `firstError | all` | | vine | ✅ | `firstError | all` | | yup | ✅ | `firstError | all` | | zod | ✅ | `firstError | all` | </details> ## TypeScript Most of the resolvers can infer the output type from the schema. See comparison table for more details. ```tsx useForm<Input, Context, Output>() ``` Example: ```tsx import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; const schema = z.object({ id: z.number(), }); // Automatically infers the output type from the schema useForm({ resolver: zodResolver(schema), }); // Force the output type useForm<z.input<typeof schema>, any, z.output<typeof schema>>({ resolver: zodResolver(schema), }); ``` ## Links - [React-hook-form validation resolver documentation ](https://react-hook-form.com/docs/useform#resolver) ### Supported resolvers - [Install](#install) - [Links](#links) - [Supported resolvers](#supported-resolvers) - [API](#api) - [Quickstart](#quickstart) - [Yup](#yup) - [Zod](#zod) - [Superstruct](#superstruct) - [Joi](#joi) - [Vest](#vest) - [Class Validator](#class-validator) - [io-ts](#io-ts) - [Nope](#nope) - [computed-types](#computed-types) - [typanion](#typanion) - [Ajv](#ajv) - [TypeBox](#typebox) - [With `ValueCheck`](#with-valuecheck) - [With `TypeCompiler`](#with-typecompiler) - [ArkType](#arktype) - [Valibot](#valibot) - [TypeSchema](#typeschema) - [effect-ts](#effect-ts) - [VineJS](#vinejs) - [fluentvalidation-ts](#fluentvalidation-ts) - [standard-schema](#standard-schema) - [Backers](#backers) - [Sponsors](#sponsors) - [Contributors](#contributors) ## API ``` type Options = { mode: 'async' | 'sync', raw?: boolean } resolver(schema: object, schemaOptions?: object, resolverOptions: Options) ``` | | type | Required | Description | | --------------- | -------- | -------- | --------------------------------------------- | | schema | `object` | ✓ | validation schema | | schemaOptions | `object` | | validation library schema options | | resolverOptions | `object` | | resolver options, `async` is the default mode | ## Quickstart ### [Yup](https://github.com/jquense/yup) Dead simple Object schema validation. [![npm](https://img.shields.io/bundlephobia/minzip/yup?style=for-the-badge)](https://bundlephobia.com/result?p=yup) ```typescript jsx import { useForm } from 'react-hook-form'; import { yupResolver } from '@hookform/resolvers/yup'; import * as yup from 'yup'; const schema = yup .object() .shape({ name: yup.string().required(), age: yup.number().required(), }) .required(); const App = () => { const { register, handleSubmit } = useForm({ resolver: yupResolver(schema), }); return ( <form onSubmit={handleSubmit((d) => console.log(d))}> <input {...register('name')} /> <input type="number" {...register('age')} /> <input type="submit" /> </form> ); }; ``` ### [Zod](https://github.com/vriad/zod) TypeScript-first schema validation with static type inference [![npm](https://img.shields.io/bundlephobia/minzip/zod?style=for-the-badge)](https://bundlephobia.com/result?p=zod) > ⚠️ Example below uses the `valueAsNumber`, which requires `react-hook-form` v6.12.0 (released Nov 28, 2020) or later. ```tsx import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; const schema = z.object({ name: z.string().min(1, { message: 'Required' }), age: z.number().min(10), }); const App = () => { const { register, handleSubmit, formState: { errors }, } = useForm({ resolver: zodResolver(schema), }); return ( <form onSubmit={handleSubmit((d) => console.log(d))}> <input {...register('name')} /> {errors.name?.message && <p>{errors.name?.message}</p>} <input type="number" {...register('age', { valueAsNumber: true })} /> {errors.age?.message && <p>{errors.age?.message}</p>} <input type="submit" /> </form> ); }; ``` ### [Superstruct](https://github.com/ianstormtaylor/superstruct) A simple and composable way to validate data in JavaScript (or TypeScript). [![npm](https://img.shields.io/bundlephobia/minzip/superstruct?style=for-the-badge)](https://bundlephobia.com/result?p=superstruct) ```typescript jsx import { useForm } from 'react-hook-form'; import { superstructResolver } from '@hookform/resolvers/superstruct'; import { object, string, number } from 'superstruct'; const schema = object({ name: string(), age: number(), }); const App = () => { const { register, handleSubmit } = useForm({ resolver: superstructResolver(schema), }); return ( <form onSubmit={handleSubmit((d) => console.log(d))}> <input {...register('name')} /> <input type="number" {...register('age', { valueAsNumber: true })} /> <input type="submit" /> </form> ); }; ``` ### [Joi](https://github.com/sideway/joi) The most powerful data validation library for JS. [![npm](https://img.shields.io/bundlephobia/minzip/joi?style=for-the-badge)](https://bundlephobia.com/result?p=joi) ```typescript jsx import { useForm } from 'react-hook-form'; import { joiResolver } from '@hookform/resolvers/joi'; import Joi from 'joi'; const schema = Joi.object({ name: Joi.string().required(), age: Joi.number().required(), }); const App = () => { const { register, handleSubmit } = useForm({ resolver: joiResolver(schema), }); return ( <form onSubmit={handleSubmit((d) => console.log(d))}> <input {...register('name')} /> <input type="number" {...register('age')} /> <input type="submit" /> </form> ); }; ``` ### [Vest](https://github.com/ealush/vest) Vest 🦺 Declarative Validation Testing. [![npm](https://img.shields.io/bundlephobia/minzip/vest?style=for-the-badge)](https://bundlephobia.com/result?p=vest) ```typescript jsx import { useForm } from 'react-hook-form'; import { vestResolver } from '@hookform/resolvers/vest'; import { create, test, enforce } from 'vest'; const validationSuite = create((data = {}) => { test('username', 'Username is required', () => { enforce(data.username).isNotEmpty(); }); test('password', 'Password is required', () => { enforce(data.password).isNotEmpty(); }); }); const App = () => { const { register, handleSubmit, errors } = useForm({ resolver: vestResolver(validationSuite), }); return ( <form onSubmit={handleSubmit((data) => console.log(data))}> <input {...register('username')} /> <input type="password" {...register('password')} /> <input type="submit" /> </form> ); }; ``` ### [Class Validator](https://github.com/typestack/class-validator) Decorator-based property validation for classes. [![npm](https://img.shields.io/bundlephobia/minzip/class-validator?style=for-the-badge)](https://bundlephobia.com/result?p=class-validator) > ⚠️ Remember to add these options to your `tsconfig.json`! ``` "strictPropertyInitialization": false, "experimentalDecorators": true ``` ```tsx import { useForm } from 'react-hook-form'; import { classValidatorResolver } from '@hookform/resolvers/class-validator'; import { Length, Min, IsEmail } from 'class-validator'; class User { @Length(2, 30) username: string; @IsEmail() email: string; } const resolver = classValidatorResolver(User); const App = () => { const { register, handleSubmit, formState: { errors }, } = useForm<User>({ resolver }); return ( <form onSubmit={handleSubmit((data) => console.log(data))}> <input type="text" {...register('username')} /> {errors.username && <span>{errors.username.message}</span>} <input type="text" {...register('email')} /> {errors.email && <span>{errors.email.message}</span>} <input type="submit" value="Submit" /> </form> ); }; ``` ### [io-ts](https://github.com/gcanti/io-ts) Validate your data with powerful decoders. [![npm](https://img.shields.io/bundlephobia/minzip/io-ts?style=for-the-badge)](https://bundlephobia.com/result?p=io-ts) ```typescript jsx import React from 'react'; import { useForm } from 'react-hook-form'; import { ioTsResolver } from '@hookform/resolvers/io-ts'; import t from 'io-ts'; // you don't have to use io-ts-types, but it's very useful import tt from 'io-ts-types'; const schema = t.type({ username: t.string, age: tt.NumberFromString, }); const App = () => { const { register, handleSubmit } = useForm({ resolver: ioTsResolver(schema), }); return ( <form onSubmit={handleSubmit((d) => console.log(d))}> <input {...register('username')} /> <input type="number" {...register('age')} /> <input type="submit" /> </form> ); }; export default App; ``` ### [Nope](https://github.com/bvego/nope-validator) A small, simple, and fast JS validator [![npm](https://img.shields.io/bundlephobia/minzip/nope-validator?style=for-the-badge)](https://bundlephobia.com/result?p=nope-validator) ```typescript jsx import { useForm } from 'react-hook-form'; import { nopeResolver } from '@hookform/resolvers/nope'; import Nope from 'nope-validator'; const schema = Nope.object().shape({ name: Nope.string().required(), age: Nope.number().required(), }); const App = () => { const { register, handleSubmit } = useForm({ resolver: nopeResolver(schema), }); return ( <form onSubmit={handleSubmit((d) => console.log(d))}> <input {...register('name')} /> <input type="number" {...register('age')} /> <input type="submit" /> </form> ); }; ``` ### [computed-types](https://github.com/neuledge/computed-types) TypeScript-first schema validation with static type inference [![npm](https://img.shields.io/bundlephobia/minzip/computed-types?style=for-the-badge)](https://bundlephobia.com/result?p=computed-types) ```tsx import { useForm } from 'react-hook-form'; import { computedTypesResolver } from '@hookform/resolvers/computed-types'; import Schema, { number, string } from 'computed-types'; const schema = Schema({ username: string.min(1).error('username field is required'), age: number, }); const App = () => { const { register, handleSubmit, formState: { errors }, } = useForm({ resolver: computedTypesResolver(schema), }); return ( <form onSubmit={handleSubmit((d) => console.log(d))}> <input {...register('name')} /> {errors.name?.message && <p>{errors.name?.message}</p>} <input type="number" {...register('age', { valueAsNumber: true })} /> {errors.age?.message && <p>{errors.age?.message}</p>} <input type="submit" /> </form> ); }; ``` ### [typanion](https://github.com/arcanis/typanion) Static and runtime type assertion library with no dependencies [![npm](https://img.shields.io/bundlephobia/minzip/typanion?style=for-the-badge)](https://bundlephobia.com/result?p=typanion) ```tsx import { useForm } from 'react-hook-form'; import { typanionResolver } from '@hookform/resolvers/typanion'; import * as t from 'typanion'; const isUser = t.isObject({ username: t.applyCascade(t.isString(), [t.hasMinLength(1)]), age: t.applyCascade(t.isNumber(), [ t.isInteger(), t.isInInclusiveRange(1, 100), ]), }); const App = () => { const { register, handleSubmit, formState: { errors }, } = useForm({ resolver: typanionResolver(isUser), }); return ( <form onSubmit={handleSubmit((d) => console.log(d))}> <input {...register('name')} /> {errors.name?.message && <p>{errors.name?.message}</p>} <input type="number" {...register('age')} /> {errors.age?.message && <p>{errors.age?.message}</p>} <input type="submit" /> </form> ); }; ``` ### [Ajv](https://github.com/ajv-validator/ajv) The fastest JSON validator for Node.js and browser [![npm](https://img.shields.io/bundlephobia/minzip/ajv?style=for-the-badge)](https://bundlephobia.com/result?p=ajv) ```tsx import { useForm } from 'react-hook-form'; import { ajvResolver } from '@hookform/resolvers/ajv'; // must use `minLength: 1` to implement required field const schema = { type: 'object', properties: { username: { type: 'string', minLength: 1, errorMessage: { minLength: 'username field is required' }, }, password: { type: 'string', minLength: 1, errorMessage: { minLength: 'password field is required' }, }, }, required: ['username', 'password'], additionalProperties: false, }; const App = () => { const { register, handleSubmit, formState: { errors }, } = useForm({ resolver: ajvResolver(schema), }); return ( <form onSubmit={handleSubmit((data) => console.log(data))}> <input {...register('username')} /> {errors.username && <span>{errors.username.message}</span>} <input {...register('password')} /> {errors.password && <span>{errors.password.message}</span>} <button type="submit">submit</button> </form> ); }; ``` ### [TypeBox](https://github.com/sinclairzx81/typebox) JSON Schema Type Builder with Static Type Resolution for TypeScript [![npm](https://img.shields.io/bundlephobia/minzip/@sinclair/typebox?style=for-the-badge)](https://bundlephobia.com/result?p=@sinclair/typebox) #### With `ValueCheck` ```typescript jsx import { useForm } from 'react-hook-form'; import { typeboxResolver } from '@hookform/resolvers/typebox'; import { Type } from '@sinclair/typebox'; const schema = Type.Object({ username: Type.String({ minLength: 1 }), password: Type.String({ minLength: 1 }), }); const App = () => { const { register, handleSubmit } = useForm({ resolver: typeboxResolver(schema), }); return ( <form onSubmit={handleSubmit((d) => console.log(d))}> <input {...register('username')} /> <input type="password" {...register('password')} /> <input type="submit" /> </form> ); }; ``` #### With `TypeCompiler` A high-performance JIT of `TypeBox`, [read more](https://github.com/sinclairzx81/typebox#typecompiler) ```typescript jsx import { useForm } from 'react-hook-form'; import { typeboxResolver } from '@hookform/resolvers/typebox'; import { Type } from '@sinclair/typebox'; import { TypeCompiler } from '@sinclair/typebox/compiler'; const schema = Type.Object({ username: Type.String({ minLength: 1 }), password: Type.String({ minLength: 1 }), }); const typecheck = TypeCompiler.Compile(schema); const App = () => { const { register, handleSubmit } = useForm({ resolver: typeboxResolver(typecheck), }); return ( <form onSubmit={handleSubmit((d) => console.log(d))}> <input {...register('username')} /> <input type="password" {...register('password')} /> <input type="submit" /> </form> ); }; ``` ### [ArkType](https://github.com/arktypeio/arktype) TypeScript's 1:1 validator, optimized from editor to runtime [![npm](https://img.shields.io/bundlephobia/minzip/arktype?style=for-the-badge)](https://bundlephobia.com/result?p=arktype) ```typescript jsx import { useForm } from 'react-hook-form'; import { arktypeResolver } from '@hookform/resolvers/arktype'; import { type } from 'arktype'; const schema = type({ username: 'string>1', password: 'string>1', }); const App = () => { const { register, handleSubmit } = useForm({ resolver: arktypeResolver(schema), }); return ( <form onSubmit={handleSubmit((d) => console.log(d))}> <input {...register('username')} /> <input type="password" {...register('password')} /> <input type="submit" /> </form> ); }; ``` ### [Valibot](https://github.com/fabian-hiller/valibot) The modular and type safe schema library for validating structural data [![npm](https://img.shields.io/bundlephobia/minzip/valibot?style=for-the-badge)](https://bundlephobia.com/result?p=valibot) ```typescript jsx import { useForm } from 'react-hook-form'; import { valibotResolver } from '@hookform/resolvers/valibot'; import * as v from 'valibot'; const schema = v.object({ username: v.pipe( v.string('username is required'), v.minLength(3, 'Needs to be at least 3 characters'), v.endsWith('cool', 'Needs to end with `cool`'), ), password: v.string('password is required'), }); const App = () => { const { register, handleSubmit } = useForm({ resolver: valibotResolver(schema), }); return ( <form onSubmit={handleSubmit((d) => console.log(d))}> <input {...register('username')} /> <input type="password" {...register('password')} /> <input type="submit" /> </form> ); }; ``` ### [TypeSchema](https://typeschema.com) Universal adapter for schema validation, compatible with [any validation library](https://typeschema.com/#coverage) [![npm](https://img.shields.io/bundlephobia/minzip/@typeschema/main?style=for-the-badge)](https://bundlephobia.com/result?p=@typeschema/main) ```typescript jsx import { useForm } from 'react-hook-form'; import { typeschemaResolver } from '@hookform/resolvers/typeschema'; import { z } from 'zod'; // Use your favorite validation library const schema = z.object({ username: z.string().min(1, { message: 'Required' }), password: z.number().min(1, { message: 'Required' }), }); const App = () => { const { register, handleSubmit } = useForm({ resolver: typeschemaResolver(schema), }); return ( <form onSubmit={handleSubmit((d) => console.log(d))}> <input {...register('username')} /> <input type="password" {...register('password')} /> <input type="submit" /> </form> ); }; ``` ### [effect-ts](https://github.com/Effect-TS/effect) A powerful TypeScript framework that provides a fully-fledged functional effect system with a rich standard library. [![npm](https://img.shields.io/bundlephobia/minzip/effect?style=for-the-badge)](https://bundlephobia.com/result?p=effect) ```typescript jsx import React from 'react'; import { useForm } from 'react-hook-form'; import { effectTsResolver } from '@hookform/resolvers/effect-ts'; import { Schema } from 'effect'; const schema = Schema.Struct({ username: Schema.String.pipe( Schema.nonEmptyString({ message: () => 'username required' }), ), password: Schema.String.pipe( Schema.nonEmptyString({ message: () => 'password required' }), ), }); type FormData = typeof schema.Type; interface Props { onSubmit: (data: FormData) => void; } function TestComponent({ onSubmit }: Props) { const { register, handleSubmit, formState: { errors }, // provide generic if TS has issues inferring types } = useForm<FormData>({ resolver: effectTsResolver(schema), }); return ( <form onSubmit={handleSubmit(onSubmit)}> <input {...register('username')} /> {errors.username && <span role="alert">{errors.username.message}</span>} <input {...register('password')} /> {errors.password && <span role="alert">{errors.password.message}</span>} <button type="submit">submit</button> </form> ); } ``` ### [VineJS](https://github.com/vinejs/vine) VineJS is a form data validation library for Node.js [![npm](https://img.shields.io/bundlephobia/minzip/@vinejs/vine?style=for-the-badge)](https://bundlephobia.com/result?p=@vinejs/vine) ```typescript jsx import { useForm } from 'react-hook-form'; import { vineResolver } from '@hookform/resolvers/vine'; import vine from '@vinejs/vine'; const schema = vine.compile( vine.object({ username: vine.string().minLength(1), password: vine.string().minLength(1), }), ); const App = () => { const { register, handleSubmit } = useForm({ resolver: vineResolver(schema), }); return ( <form onSubmit={handleSubmit((d) => console.log(d))}> <input {...register('username')} /> {errors.username && <span role="alert">{errors.username.message}</span>} <input {...register('password')} /> {errors.password && <span role="alert">{errors.password.message}</span>} <button type="submit">submit</button> </form> ); }; ``` ### [fluentvalidation-ts](https://github.com/AlexJPotter/fluentvalidation-ts) A TypeScript-first library for building strongly-typed validation rules [![npm](https://img.shields.io/bundlephobia/minzip/@vinejs/vine?style=for-the-badge)](https://bundlephobia.com/result?p=@vinejs/vine) ```typescript jsx import { useForm } from 'react-hook-form'; import { fluentValidationResolver } from '@hookform/resolvers/fluentvalidation-ts'; import { Validator } from 'fluentvalidation-ts'; class FormDataValidator extends Validator<FormData> { constructor() { super(); this.ruleFor('username') .notEmpty() .withMessage('username is a required field'); this.ruleFor('password') .notEmpty() .withMessage('password is a required field'); } } const App = () => { const { register, handleSubmit } = useForm({ resolver: fluentValidationResolver(new FormDataValidator()), }); return ( <form onSubmit={handleSubmit((d) => console.log(d))}> <input {...register('username')} /> {errors.username && <span role="alert">{errors.username.message}</span>} <input {...register('password')} /> {errors.password && <span role="alert">{errors.password.message}</span>} <button type="submit">submit</button> </form> ); }; ``` ### [standard-schema](https://github.com/standard-schema/standard-schema) A standard interface for TypeScript schema validation libraries [![npm](https://img.shields.io/bundlephobia/minzip/@standard-schema/spec?style=for-the-badge)](https://bundlephobia.com/result?p=@standard-schema/spec) Example zod ```typescript jsx import { useForm } from 'react-hook-form'; import { standardSchemaResolver } from '@hookform/resolvers/standard-schema'; import { z } from 'zod'; const schema = z.object({ name: z.string().min(1, { message: 'Required' }), age: z.number().min(10), }); const App = () => { const { register, handleSubmit, formState: { errors }, } = useForm({ resolver: standardSchemaResolver(schema), }); return ( <form onSubmit={handleSubmit((d) => console.log(d))}> <input {...register('name')} /> {errors.name?.message && <p>{errors.name?.message}</p>} <input type="number" {...register('age', { valueAsNumber: true })} /> {errors.age?.message && <p>{errors.age?.message}</p>} <input type="submit" /> </form> ); }; ``` Example arkType ```typescript jsx import { useForm } from 'react-hook-form'; import { standardSchemaResolver } from '@hookform/resolvers/standard-schema'; import { type } from 'arktype'; const schema = type({ username: 'string>1', password: 'string>1', }); const App = () => { const { register, handleSubmit } = useForm({ resolver: standardSchemaResolver(schema), }); return ( <form onSubmit={handleSubmit((d) => console.log(d))}> <input {...register('username')} /> <input type="password" {...register('password')} /> <input type="submit" /> </form> ); }; ``` ## Backers Thanks go to all our backers! [[Become a backer](https://opencollective.com/react-hook-form#backer)]. <a href="https://opencollective.com/react-hook-form#backers"> <img src="https://opencollective.com/react-hook-form/backers.svg?width=950" /> </a> ## Contributors Thanks go to these wonderful people! [[Become a contributor](CONTRIBUTING.md)]. <a href="https://github.com/react-hook-form/react-hook-form/graphs/contributors"> <img src="https://opencollective.com/react-hook-form/contributors.svg?width=950" /> </a>