| | |

React Hook Form 16 ๐Ÿ“‹ Other Resolvers โ€” Yup, Joi, Superstruct, and Valibot integration

โฑ๏ธ Estimated read: 17 min | |

The @hookform/resolvers package is the single bridge between React Hook Form and the validation libraries, and the Zod chapter covered one of its adapters. The package ships the adapters for the several librariesโ€”Yup, Joi, Superstruct, Valibot, AJV, ArkType, TypeBox, and the othersโ€”and each adapter follows the same contract: it receives the schema, validates the form values, and returns the normalized values and the errors in the shape that the formState expects . The choice of the library is the choice of the schema authoring style, and the adapter is the mechanism that makes the choice independent of the form’s code.

The four libraries in this chapter occupy the different points on the schema-authoring spectrum. Yup is the object-schema builder with the chainable methods, and it is the oldest of the four. Joi is the most powerful of the four, with the rich validation vocabulary and the descriptive error messages. Superstruct is the lightweight structural validator with the composable types. Valibot is the modular successor to Zod’s style, with the tree-shakeable functions and the pipe-based validation . The adapters are the mechanism that lets the form use any of them, and the differences are the authoring style, not the form integration.

Key point: The yupResolver, the joiResolver, the superstructResolver, and the valibotResolver are the adapters from the @hookform/resolvers package. Each adapter receives the schema and returns the resolver that the useForm accepts. The Yup, the Joi, the Superstruct, and the Valibot differ in the schema authoring style, the type inference, and the criteriaMode support, but the form’s integration code is the same: the resolver: libraryResolver(schema).


Why the resolver package exists

The adapter problem. Each validation library has its own API, its own error shape, and its own validation timing. The useForm accepts a single resolver function, and the function must return the values and the errors in the specific shape. The @hookform/resolvers package is the mechanism that translates each library’s output to that shape, and the translation is the reason the form does not need the library-specific code . Without the adapter, the developer would write the translation for each library, and the translation would drift.

The type-inference problem. The resolver can infer the form values type from the schema, and the inference is the mechanism that keeps the form and the schema aligned. The Yup, the Superstruct, and the Valibot infer the values, and the Joi does not . The inference is the reason the useForm without the explicit generic is still type-safe for the three libraries, and the explicit generic is the requirement for the Joi.

The criteriaMode problem. The criteriaMode option controls whether the resolver returns the first error per field or all the errors. The Yup, the Joi, and the Valibot support both the firstError and the all, and the Superstruct supports only the firstError . The difference is the mechanism that determines whether the UI can show the multiple messages for the same field, and the difference is the reason the library choice affects the error display.

The installation problem. The adapter is the separate package, and the schema library is the separate dependency. The installation is the npm install react-hook-form @hookform/resolvers <library>, and the three packages are the mechanism that makes the integration work . The adapter is the bridge, and the library is the schema engine.


a. The Yup and the Joi adapters

The Yup is the object-schema builder with the chainable methods. The yupResolver connects the Yup schema to the form, and the yup.InferType derives the TypeScript type.

// The Yup resolver
import { useForm } from "react-hook-form";
import { yupResolver } from "@hookform/resolvers/yup";
import * as yup from "yup";

// The Yup schema with the chainable methods.
const schema = yup
  .object({
    name: yup.string().required("Name is required"),
    email: yup.string().email("Enter a valid email").required(),
  })
  .required();

// The Yup type inference.
type FormValues = yup.InferType<typeof schema>;

function ContactForm() {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<FormValues>({
    // The yupResolver connects the schema to the form.
    resolver: yupResolver(schema),
  });

  return (
    <form onSubmit={handleSubmit((data) => console.log(data))}>
      <input {...register("name")} />
      {errors.name && <p role="alert">{errors.name.message}</p>}
      <input {...register("email")} />
      {errors.email && <p role="alert">{errors.email.message}</p>}
      <button type="submit">Send</button>
    </form>
  );
}

The yupResolver(schema) is the mechanism that connects the Yup schema to the useForm. The yup.InferType<typeof schema> is the mechanism that derives the form values type, and the type is the same as the schema’s output . The errors.name.message is the Yup error message, and the message is mapped to the formState.errors by the resolver.

The Yup context is the mechanism for the dynamic validation. The useForm‘s context option is passed to the schema, and the schema reads it through the $ context. The context is the mechanism that lets the validation depend on the external state, such as the user’s plan .

// The Yup context
const schema = yup.object({
  email: yup.string().when("$plan", {
    is: "paid",
    then: (schema) => schema.email(),
    otherwise: (schema) => schema,
  }),
});

useForm<FormValues, { plan: "free" | "paid" }>({
  resolver: yupResolver(schema),
  context: { plan: "paid" },
});

The context: { plan: "paid" } is the mechanism that passes the context to the Yup schema, and the $plan is the mechanism that the schema reads it. The context is the reason the Yup can do the conditional validation, and the conditional is the reason the context exists.

The Joi is the most powerful of the four, and the joiResolver connects the Joi schema to the form. The Joi does not infer the values type, and the explicit generic is the requirement.

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

// The Joi schema with the descriptive methods.
const schema = Joi.object({
  name: Joi.string().required(),
  age: Joi.number().required(),
});

// The Joi does not infer the values type.
type FormValues = {
  name: string;
  age: number;
};

function ProfileForm() {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<FormValues>({
    // The joiResolver connects the schema to the form.
    resolver: joiResolver(schema),
  });

  return (
    <form onSubmit={handleSubmit((data) => console.log(data))}>
      <input {...register("name")} />
      {errors.name && <p role="alert">{errors.name.message}</p>}
      <input type="number" {...register("age", { valueAsNumber: true })} />
      {errors.age && <p role="alert">{errors.age.message}</p>}
      <button type="submit">Save</button>
    </form>
  );
}

The joiResolver(schema) is the mechanism that connects the Joi schema to the form. The explicit FormValues type is the mechanism that types the form, because the Joi does not infer it . The valueAsNumber: true is the mechanism that converts the input string to the number before the Joi validation, and the conversion is the reason the Joi’s Joi.number() accepts the value.


b. The Superstruct and the Valibot adapters

The Superstruct is the lightweight structural validator with the composable types. The superstructResolver connects the Superstruct schema to the form, and the Infer utility derives the type.

// The Superstruct resolver
import { useForm } from "react-hook-form";
import { superstructResolver } from "@hookform/resolvers/superstruct";
import { object, string, number, Infer } from "superstruct";

// The Superstruct schema with the composable types.
const schema = object({
  name: string(),
  age: number(),
});

// The Superstruct type inference.
type FormValues = Infer<typeof schema>;

function ProfileForm() {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<FormValues>({
    // The superstructResolver connects the schema to the form.
    resolver: superstructResolver(schema),
  });

  return (
    <form onSubmit={handleSubmit((data) => console.log(data))}>
      <input {...register("name")} />
      {errors.name && <p role="alert">{errors.name.message}</p>}
      <input type="number" {...register("age", { valueAsNumber: true })} />
      {errors.age && <p role="alert">{errors.age.message}</p>}
      <button type="submit">Save</button>
    </form>
  );
}

The superstructResolver(schema) is the mechanism that connects the Superstruct schema to the form. The Infer<typeof schema> is the mechanism that derives the type, and the type is the same as the schema’s output . The Superstruct supports only the firstError criteria mode, and the criteriaMode: "all" is the option that the Superstruct does not support .

The Valibot is the modular successor to the Zod style, and the valibotResolver connects the Valibot schema to the form. The Valibot uses the pipe for the validation chain, and the InferInput and the InferOutput are the types.

// The Valibot resolver
import { useForm } from "react-hook-form";
import { valibotResolver } from "@hookform/resolvers/valibot";
import * as v from "valibot";

// The Valibot schema with the pipe-based validation.
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"),
});

// The Valibot type inference.
type FormValues = v.InferOutput<typeof schema>;

function SignUpForm() {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<FormValues>({
    // The valibotResolver connects the schema to the form.
    resolver: valibotResolver(schema),
  });

  return (
    <form onSubmit={handleSubmit((data) => console.log(data))}>
      <input {...register("username")} />
      {errors.username && <p role="alert">{errors.username.message}</p>}
      <input {...register("password")} type="password" />
      {errors.password && <p role="alert">{errors.password.message}</p>}
      <button type="submit">Sign up</button>
    </form>
  );
}

The valibotResolver(schema) is the mechanism that connects the Valibot schema to the form. The v.InferOutput<typeof schema> is the mechanism that derives the output type, and the v.InferInput is the input type . The Valibot supports both the firstError and the all criteria modes, and the criteriaMode: "all" is the option that the Valibot supports .

The Valibot’s v.transform is the mechanism that changes the output type, and the three generics are the requirement when the transform is used. The useForm<v.InferInput<typeof schema>, unknown, v.InferOutput<typeof schema>> is the mechanism that types the fields as the input and the submit result as the output . The transform is the reason the input and the output types differ, and the difference is the reason the three generics exist.


Complete Example Session

// ============================================
// PART 1: THE YUP RESOLVER
// ============================================

import { useForm } from "react-hook-form";
import { yupResolver } from "@hookform/resolvers/yup";
import * as yup from "yup";

const yupSchema = yup.object({
  name: yup.string().required("Name is required"),
  email: yup.string().email("Enter a valid email").required(),
});

type YupValues = yup.InferType<typeof yupSchema>;

function YupForm() {
  const { register, handleSubmit, formState: { errors } } = useForm<YupValues>({
    resolver: yupResolver(yupSchema),
  });

  return (
    <form onSubmit={handleSubmit((data) => console.log(data))}>
      <input {...register("name")} />
      {errors.name && <p>{errors.name.message}</p>}
      <input {...register("email")} />
      {errors.email && <p>{errors.email.message}</p>}
    </form>
  );
}

// ============================================
// PART 2: THE JOI RESOLVER
// ============================================

import { joiResolver } from "@hookform/resolvers/joi";
import Joi from "joi";

const joiSchema = Joi.object({
  name: Joi.string().required(),
  age: Joi.number().required(),
});

type JoiValues = { name: string; age: number };

function JoiForm() {
  const { register, handleSubmit, formState: { errors } } = useForm<JoiValues>({
    resolver: joiResolver(joiSchema),
  });

  return (
    <form onSubmit={handleSubmit((data) => console.log(data))}>
      <input {...register("name")} />
      {errors.name && <p>{errors.name.message}</p>}
      <input type="number" {...register("age", { valueAsNumber: true })} />
      {errors.age && <p>{errors.age.message}</p>}
    </form>
  );
}

// ============================================
// PART 3: THE SUPERSTRUCT RESOLVER
// ============================================

import { superstructResolver } from "@hookform/resolvers/superstruct";
import { object, string, number, Infer } from "superstruct";

const superstructSchema = object({
  name: string(),
  age: number(),
});

type SuperstructValues = Infer<typeof superstructSchema>;

function SuperstructForm() {
  const { register, handleSubmit, formState: { errors } } = useForm<SuperstructValues>({
    resolver: superstructResolver(superstructSchema),
  });

  return (
    <form onSubmit={handleSubmit((data) => console.log(data))}>
      <input {...register("name")} />
      {errors.name && <p>{errors.name.message}</p>}
      <input type="number" {...register("age", { valueAsNumber: true })} />
      {errors.age && <p>{errors.age.message}</p>}
    </form>
  );
}

// ============================================
// PART 4: THE VALIBOT RESOLVER
// ============================================

import { valibotResolver } from "@hookform/resolvers/valibot";
import * as v from "valibot";

const valibotSchema = v.object({
  username: v.pipe(
    v.string("username is required"),
    v.minLength(3, "Needs to be at least 3 characters"),
  ),
  password: v.string("password is required"),
});

type ValibotValues = v.InferOutput<typeof valibotSchema>;

function ValibotForm() {
  const { register, handleSubmit, formState: { errors } } = useForm<ValibotValues>({
    resolver: valibotResolver(valibotSchema),
  });

  return (
    <form onSubmit={handleSubmit((data) => console.log(data))}>
      <input {...register("username")} />
      {errors.username && <p>{errors.username.message}</p>}
      <input {...register("password")} type="password" />
      {errors.password && <p>{errors.password.message}</p>}
    </form>
  );
}

// ============================================
// PART 5: THE YUP CONTEXT
// ============================================

const contextSchema = yup.object({
  email: yup.string().when("$plan", {
    is: "paid",
    then: (schema) => schema.email(),
    otherwise: (schema) => schema,
  }),
});

useForm<YupValues, { plan: "free" | "paid" }>({
  resolver: yupResolver(contextSchema),
  context: { plan: "paid" },
});

// ============================================
// PART 6: THE VALIBOT TRANSFORM
// ============================================

const transformSchema = v.object({
  name: v.string(),
  number: v.pipe(v.string(), v.transform(Number)),
});

// The transform changes the output type.
// The three generics type the input and the output.
useForm<
  v.InferInput<typeof transformSchema>,
  unknown,
  v.InferOutput<typeof transformSchema>
>({
  resolver: valibotResolver(transformSchema),
});

// ============================================
// PART 7: THE CRITERIA MODE
// ============================================

// The Yup, the Joi, and the Valibot support the criteriaMode: "all".
// The Superstruct supports only the firstError.
useForm({ criteriaMode: "all" });

// ============================================
// PART 8: THE INSTALLATION
// ============================================

// The Yup
// npm install react-hook-form @hookform/resolvers yup

// The Joi
// npm install react-hook-form @hookform/resolvers joi

// The Superstruct
// npm install react-hook-form @hookform/resolvers superstruct

// The Valibot
// npm install react-hook-form @hookform/resolvers valibot

// ============================================
// PART 9: THE RESOLVER COMPARISON
// ============================================

// The Yup: the type inference yes, the criteriaMode all.
// The Joi: the type inference no, the criteriaMode all.
// The Superstruct: the type inference yes, the criteriaMode firstError.
// The Valibot: the type inference yes, the criteriaMode all.

// ============================================
// PART 10: THE CHECKLIST
// ============================================

// [ ] The resolver package is installed.
// [ ] The schema library is installed.
// [ ] The resolver is the correct adapter.
// [ ] The type inference is used or the explicit generic is set.
// [ ] The criteriaMode is set if the multiple errors are needed.
// [ ] The valueAsNumber is set for the Joi and the Superstruct numeric fields.

The ten parts covered the Yup resolver, the Joi resolver, the Superstruct resolver, the Valibot resolver, the Yup context, the Valibot transform, the criteria mode, the installation, the resolver comparison, and the checklist. The through-line is that the adapter is the same, and the schema library is the choice.


Quick Reference

The resolver comparison

LibraryResolverInstallInfers valuescriteriaMode
YupyupResolveryupYesfirstError | all
JoijoiResolverjoiNofirstError | all
SuperstructsuperstructResolversuperstructYesfirstError
ValibotvalibotResolvervalibotYesfirstError | all

The installation

LibraryCommand
Yupnpm install react-hook-form @hookform/resolvers yup
Joinpm install react-hook-form @hookform/resolvers joi
Superstructnpm install react-hook-form @hookform/resolvers superstruct
Valibotnpm install react-hook-form @hookform/resolvers valibot

The type inference

LibraryUtility
Yupyup.InferType<typeof schema>
JoiThe manual type
SuperstructInfer<typeof schema>
Valibotv.InferOutput<typeof schema>

The resolver options

OptionPurpose
mode: "async"The async validation (default)
mode: "sync"The sync validation
raw: trueThe unparsed values

Best Practices

โœ… Do This:

// Use the yupResolver for the Yup schema
useForm({ resolver: yupResolver(schema) });                        // โœ…

// Use the yup.InferType for the type
type FormValues = yup.InferType<typeof schema>;                    // โœ…

// Use the valueAsNumber for the Joi numeric field
<input type="number" {...register("age", { valueAsNumber: true })} /> // โœ…

// Use the three generics for the Valibot transform
useForm<v.InferInput<typeof schema>, unknown, v.InferOutput<typeof schema>>(); // โœ…

โŒ Don’t Do This:

// Assume the Joi infers the type
useForm({ resolver: joiResolver(schema) }); // the type is any          // โŒ

// Use the criteriaMode: "all" with the Superstruct
useForm({ criteriaMode: "all" }); // the Superstruct supports only firstError // โŒ

Common Pitfalls

PitfallWhy It HappensFix
The Joi type is anyThe Joi does not inferSet the explicit generic
The Superstruct criteriaMode: "all" does nothingThe Superstruct supports only firstErrorUse the default
The numeric field is a stringThe valueAsNumber is missingAdd the valueAsNumber: true
The Valibot transform type is wrongThe three generics are not usedUse the InferInput and the InferOutput

Real-World Examples

1. The Yup resolver

useForm({ resolver: yupResolver(schema) });

2. The Joi resolver

useForm({ resolver: joiResolver(schema) });

3. The Superstruct resolver

useForm({ resolver: superstructResolver(schema) });

4. The Valibot resolver

useForm({ resolver: valibotResolver(schema) });

5. The Yup type

type FormValues = yup.InferType<typeof schema>;

6. The Joi type

type FormValues = { name: string; age: number };

7. The Superstruct type

type FormValues = Infer<typeof schema>;

8. The Valibot type

type FormValues = v.InferOutput<typeof schema>;

9. The Yup context

useForm({ resolver: yupResolver(schema), context: { plan: "paid" } });

10. The Valibot transform

useForm<v.InferInput<typeof schema>, unknown, v.InferOutput<typeof schema>>();

Visual

The Adapter Pattern

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  THE ADAPTER PATTERN                                         โ”‚
โ”‚                                                              โ”‚
โ”‚  The schema library                                          โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                                    โ”‚
โ”‚  โ”‚ Yup / Joi /         โ”‚  The validation rules              โ”‚
โ”‚  โ”‚ Superstruct /       โ”‚                                    โ”‚
โ”‚  โ”‚ Valibot             โ”‚                                    โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                                    โ”‚
โ”‚             โ”‚                                                โ”‚
โ”‚             โ–ผ                                                    โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                                    โ”‚
โ”‚  โ”‚ @hookform/resolvers โ”‚  The adapter                       โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                                    โ”‚
โ”‚             โ”‚                                                โ”‚
โ”‚             โ–ผ                                                    โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                                    โ”‚
โ”‚  โ”‚ useForm             โ”‚  The form                          โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                                    โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

The Type Inference

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  THE TYPE INFERENCE                                          โ”‚
โ”‚                                                              โ”‚
โ”‚  โœ… The Yup, the Superstruct, the Valibot:                     โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                                    โ”‚
โ”‚  โ”‚ The resolver infers โ”‚  The useForm without the generic   โ”‚
โ”‚  โ”‚ the type            โ”‚                                    โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                                    โ”‚
โ”‚                                                              โ”‚
โ”‚  โŒ The Joi:                                                   โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                                    โ”‚
โ”‚  โ”‚ The resolver does   โ”‚  The explicit generic is required  โ”‚
โ”‚  โ”‚ not infer           โ”‚                                    โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                                    โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

The criteriaMode

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  THE criteriaMode                                            โ”‚
โ”‚                                                              โ”‚
โ”‚  firstError (the default):                                   โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                                    โ”‚
โ”‚  โ”‚ The first failing   โ”‚  The single message                โ”‚
โ”‚  โ”‚ rule                โ”‚                                    โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                                    โ”‚
โ”‚                                                              โ”‚
โ”‚  all:                                                        โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                                    โ”‚
โ”‚  โ”‚ Every failing rule  โ”‚  The multiple messages             โ”‚
โ”‚  โ”‚                     โ”‚  The Yup, the Joi, the Valibot      โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                                    โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Summary

ItemValue
The adapter package@hookform/resolvers
The Yup resolveryupResolver from @hookform/resolvers/yup
The Joi resolverjoiResolver from @hookform/resolvers/joi
The Superstruct resolversuperstructResolver from @hookform/resolvers/superstruct
The Valibot resolvervalibotResolver from @hookform/resolvers/valibot
The type inferenceThe Yup, the Superstruct, the Valibot infer; the Joi does not
The criteriaModeThe Superstruct supports only firstError

Key takeaways:

  • The @hookform/resolvers package is the single adapter for the multiple libraries. The Yup, the Joi, the Superstruct, and the Valibot each have the adapter, and the adapter translates the library’s output to the formState.errors shape . The form’s integration code is the same for all four.
  • The type inference differs. The Yup, the Superstruct, and the Valibot infer the form values type from the schema. The Joi does not, and the explicit generic is the requirement . The inference is the convenience, and the explicit generic is the fallback.
  • The criteriaMode support differs. The Yup, the Joi, and the Valibot support both the firstError and the all. The Superstruct supports only the firstError . The difference is the mechanism that determines whether the UI can show the multiple messages.
  • The Yup context is the dynamic validation mechanism. The useForm‘s context option is passed to the Yup schema, and the schema reads it through the $ context . The context is the reason the Yup can do the conditional validation.
  • The Valibot transform requires the three generics. The v.transform changes the output type, and the useForm<v.InferInput, unknown, v.InferOutput> is the mechanism that types the fields as the input and the submit result as the output . The transform is the reason the input and the output types differ.

Remember: The React Hook Form has the adapters for the multiple schema libraries, and the @hookform/resolvers package is the single bridge. The Yup, the Joi, the Superstruct, and the Valibot differ in the authoring style, the type inference, and the criteria mode, but the form’s integration is the same: the resolver: libraryResolver(schema). The adapter is the mechanism that makes the library choice independent of the form’s code.

Was this helpful?


Stop using slow, ad-bloated tool sites! ๐Ÿคฎ

๐Ÿ”Ž Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
โœ… Finance (Mortgage, Interest, Inflation)
โœ… Tech (Base64, JSON, Dev Suite, IP)
โœ… Health (BMI, BMR, TDEE)
โœ… Productivity (Timer, Workspace, QR)

โšก๏ธ Fast & Private
๐Ÿ”’ No data leaves your device
๐Ÿ’Ž 100% Free

๐Ÿ”— Use it now: https://tools.kandz.me
๐Ÿ”– Bookmark itโ€”youโ€™ll need it later!