yup and joi are schema validation libraries used to define rules for data structures, while @hookform/resolvers is an integration layer that connects these validators to React Hook Form. yup is designed with React in mind, offering a lightweight and fluent API for browser-based validation. joi is a robust, feature-rich validator often used on the server but adaptable for the client. @hookform/resolvers eliminates the need to write custom glue code, allowing React Hook Form to seamlessly consume schemas from yup, joi, or other libraries like Zod.
Building reliable forms requires more than just checking if a field is empty. You need a system that handles complex rules, async checks, and user feedback without slowing down the interface. yup, joi, and @hookform/resolvers are key tools in this space, but they serve different roles. Let's break down how they work together and where they differ.
The way you write validation rules affects how easy your code is to read and change later.
yup uses a chainable API that reads like a sentence.
.required() or .min() are attached directly to the type.// yup: Schema definition
import * as yup from 'yup';
const schema = yup.object({
username: yup.string().required('Name is required'),
age: yup.number().min(18, 'Must be an adult')
});
joi also uses chaining but offers more built-in rules out of the box.
yup but can be more verbose for complex cases.// joi: Schema definition
import Joi from 'joi';
const schema = Joi.object({
username: Joi.string().required().messages({ 'string.empty': 'Name is required' }),
age: Joi.number().min(18).messages({ 'number.min': 'Must be an adult' })
});
@hookform/resolvers does not define rules itself.
yup or joi).// @hookform/resolvers: Importing the bridge
import { yupResolver } from '@hookform/resolvers/yup';
// OR
import { joiResolver } from '@hookform/resolvers/joi';
// You pass the schema defined above into this resolver
const resolver = yupResolver(schema);
React Hook Form needs a way to run your schema against the form data. This is where integration matters.
yup is typically used with the resolver package.
yupResolver into the useForm hook.yup schema on every change or submit.// yup: Integration with React Hook Form
import { useForm } from 'react-hook-form';
import { yupResolver } from '@hookform/resolvers/yup';
const { register, handleSubmit } = useForm({
resolver: yupResolver(schema)
});
joi follows the same pattern but uses the joiResolver.
// joi: Integration with React Hook Form
import { useForm } from 'react-hook-form';
import { joiResolver } from '@hookform/resolvers/joi';
const { register, handleSubmit } = useForm({
resolver: joiResolver(schema)
});
@hookform/resolvers is the actual glue in both examples above.
// @hookform/resolvers: The underlying mechanism
// This package exports the resolver functions used above
// It ensures error paths match React Hook Form's field names
export { yupResolver, joiResolver, zodResolver } from '@hookform/resolvers';
Users need clear feedback when they make a mistake. Each library handles messages differently.
yup lets you pass messages directly into the validation methods.
// yup: Custom messages
const schema = yup.object({
email: yup.string()
.email('Invalid email format')
.required('Email is mandatory')
});
joi uses a .messages() object or specific method arguments.
// joi: Custom messages
const schema = Joi.object({
email: Joi.string()
.email()
.required()
.messages({
'string.email': 'Invalid email format',
'any.required': 'Email is mandatory'
})
});
@hookform/resolvers passes these messages through unchanged.
yup or joi.// @hookform/resolvers: Message passthrough
// The resolver captures the error object from yup/joi
// and maps it to React Hook Form's errors state
// No extra config needed for messages
const { errors } = useForm({ resolver: yupResolver(schema) });
// errors.email.message will contain 'Email is mandatory'
Sometimes you need to check a database (e.g., is the username taken?) before validating.
yup supports async tests using the .test() method.
// yup: Async validation
const schema = yup.object({
username: yup.string().test(
'is-unique',
'Username taken',
async (value) => {
const response = await checkUsername(value);
return response.available;
}
)
});
joi also supports async validation but requires enabling it.
{ allowAsync: true } when validating.// joi: Async validation
const schema = Joi.object({
username: Joi.string()
.external(async (value) => {
const response = await checkUsername(value);
if (!response.available) throw new Error('Username taken');
return value;
})
});
@hookform/resolvers manages the async state for you.
isSubmitting flags for validation.// @hookform/resolvers: Async handling
// The resolver wraps the validation call
// React Hook Form handles the pending state automatically
const { formState: { isSubmitting } } = useForm({
resolver: joiResolver(schema)
});
// isSubmitting is true while async test runs
Where your code runs matters. Some libraries are heavier than others.
yup is built for the browser.
joi.// yup: Browser optimization
// Import only what you need if using ESM
import { object, string } from 'yup';
// Tree-shaking friendly
joi is heavier and originally built for Node.js.
// joi: Bundle consideration
import Joi from 'joi';
// Full library is often pulled in
// Consider code-splitting if used only on specific admin pages
@hookform/resolvers is very lightweight.
// @hookform/resolvers: Overhead
import { yupResolver } from '@hookform/resolvers/yup';
// Minimal code added to bundle
// Focus is on integration, not logic
Despite their differences, these tools work together to solve the same problems. Here are key overlaps:
@hookform/resolvers ensures yup and joi speak the same language as the form.// Shared pattern
const { register, handleSubmit, formState: { errors } } = useForm({
resolver: anyResolver(schema) // Works for yup, joi, zod, etc.
});
yup and joi can infer TypeScript types from schemas.@hookform/resolvers preserves these types in the form hooks.// TypeScript inference
type FormData = yup.InferType<typeof schema>;
const { register } = useForm<FormData>({ ... });
// Reactive errors
<input {...register('username')} />
{errors.username && <span>{errors.username.message}</span>}
| Feature | yup | joi | @hookform/resolvers |
|---|---|---|---|
| Primary Role | π Validation Schema | π Validation Schema | π Integration Bridge |
| Bundle Size | β‘ Lightweight | π Heavier | πͺΆ Minimal |
| Best Environment | π Browser / Frontend | π₯οΈ Node / Full-stack | βοΈ React Hook Form |
| Async Support | β
Via .test() | β
Via .external() | β Auto-handled |
| Message Config | π£οΈ Inline arguments | π£οΈ .messages() object | β‘οΈ Passthrough |
| Feature | Shared by All Three |
|---|---|
| Ecosystem | βοΈ React Hook Form compatible |
| TypeScript | π‘οΈ Full type inference |
| Error Handling | π Reactive updates |
| Maintenance | π’ Active community support |
yup is like a lightweight toolkit π β perfect for frontend teams that want fast, readable validation without the bloat. It is the default choice for most React projects.
joi is like a heavy-duty safe π¦ β ideal when you need strict rules that match your backend or require complex data transformations. Use it when consistency across server and client is a priority.
@hookform/resolvers is the adapter plug π β you almost always need this if you use React Hook Form with an external validator. It saves you from writing boilerplate code and keeps your components clean.
Final Thought: For most new React applications, start with yup paired with @hookform/resolvers. Switch to joi only if you have a specific need for its advanced features or backend alignment. The resolver package is the key to making either choice feel effortless.
Choose @hookform/resolvers when you are already using React Hook Form and want to offload validation logic to an external schema library. It is the standard bridge that prevents you from writing manual validation wrappers, ensuring type safety and performance. This package is essential if you prefer defining rules outside of your component logic.
Choose joi if your project requires complex validation rules that mirror a Node.js backend schema, or if you need to share validation logic between server and client. It is powerful and strict, making it suitable for enterprise applications where data integrity is critical. However, be mindful of its larger bundle size compared to alternatives when targeting mobile browsers.
Choose yup if you want a validation library that feels native to the React ecosystem with a smaller footprint. It is ideal for forms where you need a fluent, chainable API that is easy to read and maintain. yup integrates effortlessly with @hookform/resolvers and is generally the go-to choice for new frontend-heavy projects.
Performant, flexible and extensible forms with easy to use validation.
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 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
| resolver | Infer values from schema | criteriaMode |
|---|---|---|
| AJV | β | firstError | all |
| ata-validator | β | 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 |
Most of the resolvers can infer the output type from the schema. See comparison table for more details.
useForm<Input, Context, Output>()
Example:
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod'; // or 'zod/v4'
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),
});
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 |
Dead simple Object schema validation.
β οΈ Pass context via
useForm({ context }), not viayupResolver'sschemaOptions.schemaOptions.contextis overridden by the form context, so use theuseFormcontext object instead.
// Correct
useForm({
resolver: yupResolver(schema),
context: { foo: true },
});
// Avoid - schemaOptions.context will be ignored/overridden
yupResolver(schema, { context: { foo: true } });
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>
);
};
TypeScript-first schema validation with static type inference
β οΈ Example below uses the
valueAsNumber, which requiresreact-hook-formv6.12.0 (released Nov 28, 2020) or later.
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod'; // or 'zod/v4'
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>
);
};
β οΈ If your schema uses
.default(...)on a field, that field becomes optional on the schema's input type (z.input) but stays required on its output type (z.output/z.infer) β the default only fills it in after validation. Passing a single generic touseForm<T>pins both to the same type and will conflict withzodResolver, which infers input and output separately. Either omit the generic and let it infer fromresolver, or specify all three explicitly:const schema = z.object({ debug_mode: z.boolean().default(true) }); useForm<z.input<typeof schema>, unknown, z.output<typeof schema>>({ resolver: zodResolver(schema), });
A simple and composable way to validate data in JavaScript (or TypeScript).
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>
);
};
The most powerful data validation library for JS.
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 π¦Ί Declarative Validation Testing.
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 } = useForm({
resolver: vestResolver(validationSuite),
});
return (
<form onSubmit={handleSubmit((data) => console.log(data))}>
<input {...register('username')} />
<input type="password" {...register('password')} />
<input type="submit" />
</form>
);
};
Decorator-based property validation for classes.
β οΈ Remember to add these options to your
tsconfig.json!
"strictPropertyInitialization": false,
"experimentalDecorators": true
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>
);
};
Validate your data with powerful decoders.
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;
A small, simple, and fast JS validator
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>
);
};
TypeScript-first schema validation with static type inference
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>
);
};
Static and runtime type assertion library with no dependencies
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>
);
};
The fastest JSON validator for Node.js and browser
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>
);
};
JSON Schema Type Builder with Static Type Resolution for TypeScript
ValueCheckimport { 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>
);
};
TypeCompilerA high-performance JIT of TypeBox, read more
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>
);
};
t.Files())typeboxResolver validates using @sinclair/typebox's own Value.Errors/Value.Check, so any schema
Kind not built into TypeBox (custom types, or ones defined by a third-party library such as ElysiaJS)
must be registered with TypeBox's own TypeRegistry (and FormatRegistry for string formats) before
it's used β this is a TypeBox-level extension point, not something @hookform/resolvers wraps or needs
to expose separately:
import { TypeRegistry } from '@sinclair/typebox';
if (!TypeRegistry.Has('Files')) {
TypeRegistry.Set('Files', (schema, value) => Array.isArray(value));
}
Make sure this registration runs against the same @sinclair/typebox module instance the schema was
built with β if a library vendors its own copy of @sinclair/typebox instead of relying on the shared
peer dependency, its registrations won't be visible here, and you'll see a runtime "Unknown type" error.
TypeScript's 1:1 validator, optimized from editor to runtime
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>
);
};
The modular and type safe schema library for validating structural data
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>
);
};
β οΈ If your schema uses
v.transform(...)(orv.pipe(..., v.transform(...))) on a field, that field's parsed output type can differ from what the user actually types (its input type) β e.g. a string input transformed into a number. Passing a single generic touseForm<T>pins both the submitted values and thehandleSubmitpayload to that one type, which conflicts withvalibotResolver, since it infers input and output separately. Either omit the generic and let it infer fromresolver, or specify all three explicitly:const schema = v.object({ name: v.string(), number: v.pipe(v.string(), v.transform(Number)), }); useForm<v.InferInput<typeof schema>, unknown, v.InferOutput<typeof schema>>({ resolver: valibotResolver(schema), });
Universal adapter for schema validation, compatible with any validation library
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>
);
};
A powerful TypeScript framework that provides a fully-fledged functional effect system with a rich standard library.
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 is a form data validation library for Node.js
import { useForm } from 'react-hook-form';
import { vineResolver } from '@hookform/resolvers/vine';
import vine from '@vinejs/vine';
const schema = vine.create({
username: vine.string().minLength(1),
password: vine.string().minLength(1),
});
const App = () => {
const {
register,
handleSubmit,
formState: { errors },
} = 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>
);
};
A TypeScript-first library for building strongly-typed validation rules
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,
formState: { errors },
} = 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>
);
};
A standard interface for TypeScript schema validation libraries
Example zod
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
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>
);
};
A JSON Schema-based validator for JavaScript and TypeScript
import { useForm } from 'react-hook-form';
import { ataResolver } from '@hookform/resolvers/ata-validator';
const schema = {
type: 'object',
properties: {
username: {
type: 'string',
minLength: 1,
},
password: {
type: 'string',
minLength: 1,
},
},
required: ['username', 'password'],
};
const App = () => {
const {
register,
handleSubmit,
formState: { errors },
} = useForm({
resolver: ataResolver(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>
);
};
Thanks go to all our backers! [Become a backer].
Thanks go to these wonderful people! [Become a contributor].