Интеграция библиотеки валидации Yup с формами на основе React Hook Form строится вокруг идеи разделения ответственности: React Hook Form управляет состоянием и жизненным циклом формы, а Yup отвечает за декларативное описание и выполнение правил валидации.
Основной механизм связывания этих двух инструментов реализуется через резолверы (resolvers). Они выступают адаптером между схемой валидации и системой регистрации полей формы, преобразуя результаты проверки Yup в формат, понятный React Hook Form.
Ключевой точкой интеграции является пакет
@hookform/resolvers. Он предоставляет функцию
yupResolver, которая преобразует схему Yup в формат,
используемый React Hook Form.
Типовой сценарий:
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(8, "Минимум 8 символов").required(),
});
И подключается к форме:
const {
register,
handleSubmit,
formState: { errors },
} = useForm({
resolver: yupResolver(schema),
});
На этом уровне происходит ключевая интеграция: React Hook Form больше не выполняет встроенную валидацию, а делегирует её Yup через resolver.
Интеграция строится по следующему принципу:
Важно, что Yup возвращает объект ошибок, который затем нормализуется:
{
email: { message: "Некорректный email", type: "validation" }
}
Эта структура напрямую используется React Hook Form для отображения состояния ошибок.
При использовании TypeScript схема Yup может быть источником типов формы:
import * as yup from "yup";
const schema = yup.object({
name: yup.string().required(),
age: yup.number().min(18),
});
type FormData = yup.InferType<typeof schema>;
Далее тип передаётся в useForm:
const { register } = useForm<FormData>({
resolver: yupResolver(schema),
});
Такой подход исключает дублирование типов между UI и валидацией.
React Hook Form вызывает resolver в следующих ситуациях:
handleSubmit)mode)onBlur)triggerYup при этом может выполнять:
Пример асинхронного правила:
username: yup.string().test(
"check-username",
"Имя занято",
async (value) => {
const res = await fetch(`/api/check?u=${value}`);
const data = await res.json();
return data.available;
}
);
React Hook Form не всегда отправляет всю форму в Yup. В зависимости от события может передаваться:
Yup при этом должен корректно обрабатывать partial validation. Для
этого важно использовать abortEarly: false:
yupResolver(schema, { abortEarly: false });
Это позволяет собрать все ошибки за один проход, а не останавливаться на первой.
Yup поддерживает вложенные объекты и массивы, что напрямую транслируется в структуру формы React Hook Form.
const schema = yup.object({
user: yup.object({
profile: yup.object({
firstName: yup.string().required(),
}),
}),
});
Использование в форме:
register("user.profile.firstName");
React Hook Form автоматически сопоставляет вложенные ключи с результатами Yup.
Особенно важный кейс — динамические поля:
const schema = yup.object({
items: yup.array().of(
yup.object({
title: yup.string().required(),
})
),
});
В React Hook Form:
useFieldArrayОшибки возвращаются с индексной структурой:
items[0].title
items[1].title
Интеграция с Yup сильно зависит от режима React Hook Form:
onSubmit — минимальная нагрузка, проверка только при
отправкеonChange — постоянная валидацияonBlur — баланс между UX и производительностьюuseForm({
resolver: yupResolver(schema),
mode: "onBlur",
});
При частой валидации важно учитывать стоимость Yup-схем, особенно при больших формах.
Yup поддерживает context, который может использоваться
для динамических условий:
const schema = yup.object({
password: yup.string().required(),
confirmPassword: yup.string().oneOf(
[yup.ref("password")],
"Пароли не совпадают"
),
});
React Hook Form позволяет передавать context:
useForm({
resolver: yupResolver(schema),
context: { mode: "register" },
});
Это используется для условной логики в сложных формах.
Ошибки, возвращаемые через resolver, попадают в:
formState.errors
Структура ошибок соответствует полям формы:
errors.email?.message
Важная особенность: React Hook Form не хранит ошибки Yup напрямую, а только нормализованную версию, что снижает связанность систем.
При больших формах с десятками полей Yup может стать узким местом. Основные оптимизации:
abortEarly: falseuseMemoconst schema = useMemo(() => createSchema(), []);
Практика разделения схем повышает масштабируемость:
const addressSchema = yup.object({
city: yup.string().required(),
zip: yup.string().required(),
});
const userSchema = yup.object({
name: yup.string().required(),
address: addressSchema,
});
Такой подход позволяет переиспользовать части валидации между формами.
Для сложных компонентов (select, datepicker) используется
Controller:
<Controller
name="email"
control={control}
render={({ field }) => <input {...field} />}
/>
Yup при этом остаётся полностью независимым от UI-слоя, что сохраняет чистоту архитектуры: схема описывает данные, а не компоненты.
React Hook Form позволяет запускать Yup вручную:
trigger("email");
или
trigger(["email", "password"]);
Это приводит к повторному запуску resolver и повторной проверке Yup.
Yup позволяет расширять поведение через test:
yup.addMethod(yup.string, "noSpaces", function (message) {
return this.test("no-spaces", message, (value) => !/\s/.test(value));
});
Это делает возможным построение доменно-ориентированных правил валидации, не выходя за пределы схемы.
Главное архитектурное свойство интеграции — отсутствие дублирования состояния:
Такой подход минимизирует побочные эффекты и делает систему предсказуемой даже при сложной логике форм.