Простейшая интеграция с React Hook Form

Валидация данных в формах чаще всего выносится в отдельный слой, чтобы отделить бизнес-логику от UI-компонентов. В экосистеме React наиболее распространённой комбинацией является использование React Hook Form совместно с Yup-схемами через yupResolver. Такой подход позволяет описывать правила валидации декларативно и минимизировать количество перерендеров формы.

Ключевая идея интеграции заключается в том, что react-hook-form не выполняет валидацию сам по себе, а делегирует её резолверу. YupResolver выступает адаптером между схемой Yup и API формы.


Установка необходимых зависимостей

Для работы требуется три основных пакета:

  • react-hook-form
  • yup
  • @hookform/resolvers

Установка:

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

или

yarn add react-hook-form yup @hookform/resolvers

Создание схемы валидации Yup

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

Пример базовой схемы:

import * as yup from "yup";

const schema = yup.object({
  email: yup
    .string()
    .email("Некорректный email")
    .required("Email обязателен"),

  password: yup
    .string()
    .min(6, "Минимум 6 символов")
    .required("Пароль обязателен"),
});

Важный момент: Yup возвращает не просто булево значение, а объект ошибок, который затем интерпретируется резолвером.


Подключение yupResolver к React Hook Form

Основная точка интеграции — функция yupResolver, которая передаётся в 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 {
  register,
  handleSubmit,
  formState: { errors },
} = useForm({
  resolver: yupResolver(schema),
});

Механика работы resolver

При каждом сабмите или изменении поля (в зависимости от режима валидации) происходит следующий процесс:

  1. React Hook Form собирает текущие значения формы
  2. Передаёт их в yupResolver
  3. Yup выполняет проверку схемы
  4. Возвращается либо пустой объект ошибок, либо структурированный набор ошибок
  5. react-hook-form обновляет formState.errors

Этот процесс полностью синхронизирован, но не требует ручного управления состоянием ошибок.


Простейший пример формы

import React from "react";
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("Некорректный email").required("Обязательное поле"),
  password: yup.string().min(6, "Минимум 6 символов").required("Обязательное поле"),
});

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

  const onSub mit = (data) => {
    console.log("Отправленные данные:", data);
  };

  return (
    <form onSub mit={handleSubmit(onSubmit)}>
      <div>
        <input placeholder="Email" {...register("email")} />
        {errors.email && <p>{errors.email.message}</p>}
      </div>

      <div>
        <input type="password" placeholder="Пароль" {...register("password")} />
        {errors.password && <p>{errors.password.message}</p>}
      </div>

      <button type="submit">Войти</button>
    </form>
  );
}

Режимы валидации и влияние на resolver

react-hook-form позволяет задавать стратегию проверки:

  • onSubmit — проверка только при отправке формы
  • onBlur — проверка при потере фокуса
  • onChange — проверка при каждом изменении
  • all — комбинация событий

Пример:

useForm({
  resolver: yupResolver(schema),
  mode: "onChange",
});

При использовании yupResolver режим влияет только на частоту вызова схемы, но не на её логику.


Обработка ошибок и структура errors

Ошибки приходят в объекте formState.errors и имеют вложенную структуру, соответствующую полям схемы.

Пример структуры:

{
  email: {
    type: "validation",
    message: "Некорректный email"
  },
  password: {
    type: "min",
    message: "Минимум 6 символов"
  }
}

Доступ к ошибке всегда осуществляется через имя поля:

errors.email?.message

Валидация сложных объектов

Yup поддерживает вложенные структуры, что напрямую транслируется в React Hook Form.

const schema = yup.object({
  user: yup.object({
    name: yup.string().required("Имя обязательно"),
    age: yup.number().min(18, "Возраст должен быть >= 18"),
  }),
});

Использование в форме:

<input {...register("user.name")} />
<input {...register("user.age")} />

Массивы и динамические поля

Yup также поддерживает массивы через array().of():

const schema = yup.object({
  friends: yup.array().of(
    yup.object({
      name: yup.string().required(),
    })
  ),
});

В связке с useFieldArray это позволяет строить динамические формы с полноценной валидацией.


Значения по умолчанию и синхронизация со схемой

При использовании yupResolver важно учитывать соответствие defaultValues структуре схемы:

useForm({
  resolver: yupResolver(schema),
  defaultValues: {
    email: "",
    password: "",
  },
});

Несоответствие структуры может привести к отсутствию валидации отдельных полей или к undefined в errors.


Асинхронная валидация в Yup

Yup поддерживает асинхронные проверки, например запросы к API:

const schema = yup.object({
  email: yup
    .string()
    .email()
    .test("checkEmail", "Email уже занят", async (value) => {
      const res = await fetch(`/api/check-email?email=${value}`);
      const data = await res.json();
      return !data.exists;
    }),
});

yupResolver корректно обрабатывает промисы, возвращаемые схемой, и интегрирует результат в lifecycle формы.


Типизация с TypeScript

При использовании TypeScript можно вывести тип формы из схемы:

import { InferType } from "yup";

type FormData = InferType<typeof schema>;

И передать его в useForm:

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

Это обеспечивает синхронизацию типов между UI и валидацией.


Частые ошибки при интеграции

  • Несоответствие структуры defaultValues и схемы
  • Использование register без совпадения ключей Yup
  • Попытка валидировать поле, отсутствующее в схеме
  • Неправильная обработка вложенных объектов (user.name vs userName)

Особенности производительности

yupResolver выполняет полную проверку схемы при каждом запуске. При больших формах это может быть затратным.

Для оптимизации применяются:

  • mode: "onBlur" вместо onChange
  • разделение схем на под-секции
  • мемоизация схемы через useMemo при динамическом создании