Передача resolver в useForm

В React Hook Form механизм валидации отделён от ядра формы и реализуется через абстракцию resolver. Именно он определяет, как внешняя схема валидации интегрируется в жизненный цикл useForm, какие данные считаются валидными и каким образом формируется объект ошибок.

Resolver передаётся напрямую в конфигурацию useForm и становится центральной точкой обработки данных перед их попаданием в состояние формы.

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(),
  password: yup.string().min(6).required(),
});

const form = useForm({
  resolver: yupResolver(schema),
});

Роль resolver в архитектуре useForm

useForm по умолчанию не привязан ни к одной библиотеке валидации. Все проверки выполняются только если разработчик явно подключает resolver. Это позволяет:

  • отделить UI от логики валидации;
  • использовать разные схемы (Yup, Zod, Joi);
  • унифицировать обработку ошибок;
  • централизовать правила проверки данных.

Resolver работает как промежуточный слой между значениями формы и системой React Hook Form.

Как работает yupResolver

yupResolver адаптирует схему Yup к интерфейсу, который ожидает React Hook Form. На уровне исполнения он выполняет три ключевые задачи:

  1. Получает значения формы.
  2. Прогоняет их через Yup-схему.
  3. Преобразует результат в формат RHF.

Формально resolver имеет следующую сигнатуру:

(values, context, options) => Promise<{
  values: object,
  errors: object
}>

При использовании Yup:

resolver: yupResolver(schema)

внутри создаётся функция, которая вызывает:

schema.validate(values, { abortEarly: false })

и преобразует ошибки Yup в структуру FieldErrors.

Передача resolver через useForm

Resolver передаётся в объект настроек useForm и активируется на уровне всей формы.

const form = useForm({
  resolver: yupResolver(schema),
  mode: "onSubmit",
});

После этого каждая операция валидации (submit, change, blur — в зависимости от mode) будет проходить через Yup.

Ключевой момент: React Hook Form больше не использует встроенные правила required, minLength, pattern, если подключён resolver. Все правила определяются исключительно схемой.

Поток данных при использовании resolver

При вызове handleSubmit происходит следующий процесс:

  1. Собираются текущие значения полей формы.

  2. Эти значения передаются в resolver.

  3. Yup выполняет синхронную или асинхронную проверку.

  4. Resolver возвращает:

    • values — валидные данные;
    • errors — структурированный объект ошибок.
  5. React Hook Form обновляет состояние:

    • записывает ошибки в formState.errors;
    • передаёт валидные данные в onSubmit.

Структура ошибок, возвращаемых Yup

Yup формирует ошибки в формате, который требует трансформации. Например:

{
  email: "Email is invalid",
  password: "Password must be at least 6 characters"
}

React Hook Form ожидает более сложную структуру:

{
  email: {
    type: "validation",
    message: "Email is invalid"
  },
  password: {
    type: "min",
    message: "Password must be at least 6 characters"
  }
}

yupResolver выполняет эту трансформацию автоматически, используя информацию из ValidationError.

Асинхронная природа resolver

Несмотря на то что Yup может работать синхронно, resolver всегда возвращает Promise. Это обеспечивает единый интерфейс для всех валидаторов, включая асинхронные схемы.

const form = useForm({
  resolver: yupResolver(schema),
});

Даже если проверка простая, React Hook Form ожидает Promise-результат, что позволяет унифицировать обработку.

Влияние mode на работу resolver

Параметр mode в useForm определяет, когда именно вызывается resolver:

  • onSubmit — при отправке формы;
  • onChange — при каждом изменении значения;
  • onBlur — при потере фокуса;
  • all — комбинированная стратегия.
useForm({
  resolver: yupResolver(schema),
  mode: "onChange",
});

В режиме onChange resolver может вызываться очень часто, что делает производительность схемы важным фактором.

Контекст и кастомизация через resolver

Некоторые сценарии требуют передачи дополнительного контекста в схему Yup. Resolver поддерживает это через второй аргумент:

useForm({
  resolver: yupResolver(schema, { context: { role: "admin" } }),
});

Внутри Yup это становится доступно через this.options.context:

yup.string().test("role-check", function (value) {
  const { role } = this.options.context || {};
  return role === "admin" ? true : value !== "restricted";
});

Связь resolver и типизации (TypeScript)

При использовании TypeScript resolver влияет на типизацию useForm. Yup позволяет извлекать типы из схемы:

import { InferType } from "yup";

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

type FormValues = InferType<typeof schema>;

const form = useForm<FormValues>({
  resolver: yupResolver(schema),
});

Это создаёт синхронизацию между схемой и формой, исключая рассинхронизацию типов и фактической структуры данных.

Поведение при частичной валидации

Resolver всегда получает все значения формы, но ошибки могут быть привязаны только к части полей. Это важно при динамических формах, где поля могут добавляться или удаляться.

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

Если в форме есть дополнительные поля, не описанные в схеме, они:

  • игнорируются Yup;
  • не попадают в errors;
  • могут быть возвращены в values, если схема позволяет noUnknown.

Производительность при использовании resolver

Каждый вызов resolver — это полный прогон схемы. При сложных формах это может включать:

  • десятки полей;
  • вложенные объекты;
  • массивы;
  • кастомные тесты.

Оптимизация достигается за счёт:

  • использования abortEarly: false (уже применяется в yupResolver);
  • минимизации вложенности схем;
  • разделения больших форм на независимые части;
  • мемоизации схемы вне компонента.

Взаимодействие resolver с formState

После выполнения resolver обновляет formState:

  • errors — текущие ошибки;
  • isValid — валидность всей формы;
  • isSubmitting — состояние отправки;
  • dirtyFields — изменённые поля.
const {
  register,
  handleSubmit,
  formState: { errors, isValid },
} = useForm({
  resolver: yupResolver(schema),
});

Важно, что isValid зависит от последнего результата resolver, а не от локальных проверок отдельных полей.

Особенности поведения при reset

При вызове reset значения формы сбрасываются, но resolver не вызывается автоматически. Ошибки также очищаются, если это не переопределено вручную.

form.reset({
  email: "",
  password: "",
});

После reset следующая валидация снова будет проходить через resolver как при первом запуске.

Использование нескольких схем через условный resolver

В некоторых случаях schema может зависеть от состояния:

const resolver = (values, context, options) => {
  return userType === "admin"
    ? yupResolver(adminSchema)(values, context, options)
    : yupResolver(userSchema)(values, context, options);
};

useForm({
  resolver,
});

Такой подход позволяет динамически менять правила валидации без изменения структуры формы.

Ошибки асинхронной валидации

Если Yup содержит асинхронные тесты:

yup.string().test("check-db", async (value) => {
  const exists = await api.checkEmail(value);
  return !exists;
});

resolver ожидает завершения Promise перед обновлением состояния формы. Это влияет на UX:

  • задержка появления ошибок;
  • блокировка submit до завершения проверки.

Роль resolver как контрактного слоя

Resolver задаёт строгий контракт между:

  • входными данными формы;
  • правилами валидации;
  • структурой ошибок;
  • результатом submit.

React Hook Form не интерпретирует правила схемы — он работает только с результатом resolver, что делает систему расширяемой и независимой от конкретной библиотеки валидации.