Интеграция YupResolver с инструментами разработчика
значительно упрощает анализ ошибок валидации, контроль состояния формы и
диагностику сложных сценариев. При использовании связки
React Hook Form + Yup особую роль играют:
Официальный пакет DevTools позволяет визуально отслеживать:
Установка:
npm install @hookform/devtools
или:
yarn add @hookform/devtools
Базовый пример:
import React from "react";
import { useForm } from "react-hook-form";
import { DevTool } from "@hookform/devtools";
function App() {
const {
register,
control,
handleSubmit
} = useForm();
return (
<>
<form onSub mit={handleSubmit(console.log)}>
<input {...register("email")} />
<button>Send</button>
</form>
<DevTool control={control} />
</>
);
}
После запуска приложения в интерфейсе появляется интерактивная панель состояния формы.
Типичная интеграция:
import { useForm } from "react-hook-form";
import { yupResolver } from "@hookform/resolvers/yup";
import { DevTool } from "@hookform/devtools";
import * as yup from "yup";
const schema = yup.object({
email: yup.string().email().required(),
password: yup.string().min(6).required()
});
function App() {
const {
register,
control,
handleSubmit,
formState
} = useForm({
resolver: yupResolver(schema)
});
return (
<>
<form onSub mit={handleSubmit(console.log)}>
<input {...register("email")} />
<input type="password" {...register("password")} />
<button>Login</button>
</form>
<DevTool control={control} />
</>
);
}
DevTools показывает внутреннее состояние
react-hook-form.
Текущие значения формы:
{
"email": "admin@mail.com",
"password": "123456"
}
Полезно для:
Ошибки, возвращаемые YupResolver.
Пример:
{
"email": {
"message": "email must be a valid email"
}
}
DevTools позволяет увидеть:
Показывает поля, с которыми уже взаимодействовали.
Пример:
{
"email": true
}
Особенно полезно при:
Отображает изменённые поля.
{
"password": true
}
Помогает:
Состояние общей валидности формы.
formState.isValid
Для корректной работы:
useForm({
mode: "onChange",
resolver: yupResolver(schema)
});
DevTools позволяет отслеживать обновление isValid в
реальном времени.
Прямой вызов схемы:
schema.validate(data)
.then(console.log)
.catch(console.error);
Пример:
const data = {
email: "wrong"
};
schema.validate(data)
.catch(error => {
console.log(error);
});
Yup генерирует объект ValidationError.
catch(error => {
console.log(error.path);
console.log(error.message);
console.log(error.errors);
});
Результат:
email
email must be a valid email
["email must be a valid email"]
По умолчанию Yup останавливается на первой ошибке.
Для полного анализа:
schema.validate(data, {
abortEarly: false
});
Пример:
const schema = yup.object({
email: yup.string().email().required(),
password: yup.string().min(8).required()
});
Результат:
[
"email must be a valid email",
"password must be at least 8 characters"
]
Resolver можно протестировать отдельно:
const resolver = yupResolver(schema);
resolver(
{
email: "wrong"
},
{},
{}
).then(console.log);
Результат:
{
values: {},
errors: {
email: {
message: "email must be a valid email"
}
}
}
Resolver возвращает:
{
values,
errors
}
Если валидация успешна:
{
values: {
email: "admin@mail.com"
},
errors: {}
}
При ошибке:
{
values: {},
errors: {
email: {
type: "email",
message: "Invalid email"
}
}
}
Для больших форм удобен табличный вывод:
console.table(formState.errors);
или:
console.table(getValues());
Пример:
| field | message |
|---|---|
| Invalid email | |
| password | Too short |
Избыточные ререндеры — частая проблема сложных форм.
React DevTools показывает:
console.count("Form render");
или:
function Input() {
console.count("Input render");
return <input />;
}
Проблемный пример:
<Input errors={errors} />
Объект errors меняется часто.
Оптимизация:
const error = errors.email;
<Input error={error} />
или:
export default React.memo(Input);
const schema = yup.object({
user: yup.object({
profile: yup.object({
email: yup.string().required()
})
})
});
DevTools показывает:
{
"user": {
"profile": {
"email": {
"message": "Required"
}
}
}
}
register("user.profile.email")
Ошибки пути — одна из самых распространённых проблем.
const values = watch();
useEffect(() => {
console.log(values);
}, [values]);
const email = watch("email");
useEffect(() => {
console.log(email);
}, [email]);
Метод позволяет получать состояние отдельного поля.
const state = getFieldState("email");
console.log(state);
Результат:
{
invalid: true,
isDirty: true,
isTouched: true,
error: {
message: "Invalid email"
}
}
const schema = yup.object({
email: yup.string().test(
"check-email",
"Email already exists",
async (value) => {
const response = await fetch("/api/check");
return response.ok;
}
)
});
const onSub mit = async (data) => {
console.log("before");
await schema.validate(data);
console.log("after");
};
При зависаниях проверяются:
Тяжёлые схемы Yup могут:
React Profiler помогает определить:
Плохо:
const schema = yup.object({
items: yup.array().of(
yup.object({
deep: yup.object({
another: yup.object({
field: yup.string().required()
})
})
})
)
});
Лучше разбивать:
const itemSchema = yup.object({
field: yup.string().required()
});
const schema = yup.object({
items: yup.array().of(itemSchema)
});
const schema = yup.string().transform(value => {
console.log(value);
return value.trim();
});
schema.cast(" admin ");
Результат:
"admin"
const schema = yup.object({
role: yup.string(),
company: yup.string().when("role", {
is: "admin",
then: schema => schema.required()
})
});
when("role", (role, schema) => {
console.log(role);
return schema.required();
});
const schema = yup.object({
users: yup.array().of(
yup.object({
email: yup.string().email()
})
)
});
DevTools показывает:
{
"users": [
{
"email": "wrong"
}
]
}
register(`users.${index}.email`)
Частая ошибка:
users[0].email
Для react-hook-form предпочтителен dot notation.
Проблемный пример:
defaultValues: {
user: undefined
}
Корректно:
defaultValues: {
user: {
email: ""
}
}
const onSub mit = (data) => {
console.log("submit");
console.log(data);
};
const onEr ror = (errors) => {
console.log(errors);
};
<form onSub mit={handleSubmit(onSubmit, onError)}>
useForm({
mode: "onChange"
});
Ошибки появляются сразу.
useForm({
mode: "onBlur"
});
Проверка после потери фокуса.
useForm({
mode: "onSubmit"
});
Удобно для анализа полного submit cycle.
В React Strict Mode функции могут вызываться дважды.
Пример:
<React.StrictMode>
<App />
</React.StrictMode>
Это влияет на:
console.count("resolver");
Создание собственного resolver-обёртки:
const debugResolver = (schema) => async (data, context, options) => {
console.log("DATA", data);
const result = await yupResolver(schema)(
data,
context,
options
);
console.log("RESULT", result);
return result;
};
Использование:
useForm({
resolver: debugResolver(schema)
});
Yup позволяет получить описание схемы.
console.log(schema.describe());
Результат:
{
type: "object",
fields: {
email: {
type: "string"
}
}
}
Полезно для:
yup.string().nullable()
Разрешает:
null
yup.string().optional()
Разрешает:
undefined
console.log(value === null);
console.log(value === undefined);
Yup автоматически преобразует типы.
Пример:
yup.number().validate("42")
Результат:
42
Отключение преобразований:
yup.number().strict()
Теперь:
"42"
вызовет ошибку валидации.
Полезен для анализа:
Позволяет определить:
Используется при:
Проверяются:
resolver: yupResolver(schema)
register("field")
mode
name атрибуты
Проверяются:
handleSubmit
onSubmit
formState.errors
Проверяются:
Проверяются:
defaultValues
Controller
reset
setValue
<pre>
{JSON.stringify(formState.errors, null, 2)}
</pre>
<pre>
{JSON.stringify(watch(), null, 2)}
</pre>
const resolver = async (...args) => {
console.time("resolver");
const result = await yupResolver(schema)(...args);
console.timeEnd("resolver");
return result;
};
test("email validation", async () => {
await expect(
schema.validate({
email: "wrong"
})
).rejects.toThrow();
});
test("resolver errors", async () => {
const resolver = yupResolver(schema);
const result = await resolver(
{ email: "wrong" },
{},
{}
);
expect(result.errors.email).toBeDefined();
});