В связке react-hook-form и Yup через
@hookform/resolvers/yup механизм обработки ошибок строится
вокруг преобразования ValidationError в объект
formState.errors. Основная задача резолвера — сопоставить
ошибки схемы валидации с конкретными полями формы по их путям
(path) и обеспечить удобный доступ к ним на уровне UI.
Каждая ошибка, возвращаемая через YupResolver, имеет
строгую привязку к структуре данных формы. Это означает, что доступ к
ошибкам отдельных полей всегда осуществляется через ключи,
соответствующие структуре объекта формы.
После выполнения валидации YupResolver формирует
объект:
formState.errors
Его структура повторяет структуру формы:
{
name: { message: "Обязательное поле", type: "required" },
email: { message: "Неверный email", type: "email" },
profile: {
firstName: { message: "Слишком короткое имя", type: "min" }
}
}
Ключевой принцип: каждое поле получает собственный объект ошибки, если валидация для него не прошла.
Yup формирует ошибку в виде дерева
ValidationError, где каждое нарушение содержит:
path — путь к полю
(profile.firstName)message — текст ошибкиtype — тип нарушения (если задано через
yup тесты)YupResolver преобразует это дерево в плоскую структуру,
но сохраняет вложенность объектов, соответствующую путям.
Пример схемы:
const schema = yup.object({
profile: yup.object({
firstName: yup.string().required(),
lastName: yup.string().min(3)
})
});
Результат ошибок:
errors.profile.firstName.message
errors.profile.lastName.message
Для плоских форм доступ осуществляется напрямую через имя поля:
errors.email?.message
errors.password?.message
Использование optional chaining обязательно, поскольку до момента возникновения ошибки поле может отсутствовать в объекте.
Типичный шаблон:
const emailError = formState.errors.email?.message;
При работе с объектными структурами форма становится иерархической:
{
user: {
address: {
city: string
}
}
}
Ошибки будут иметь идентичную вложенность:
errors.user?.address?.city?.message
Ключевой момент: каждый уровень вложенности может отсутствовать, если валидация не сработала для этого пути.
При использовании yup.array() структура ошибок
индексируется:
{
users: [
{ name: "Ошибка" },
{ name: "Ошибка" }
]
}
Доступ:
errors.users?.[0]?.name?.message
errors.users?.[1]?.name?.message
Для динамических списков это основной способ адресации ошибок.
Особенность: при частичной валидации массива ошибки могут существовать только для отдельных индексов, что требует проверки через optional chaining на каждом уровне.
При работе с динамическими ключами (например,
fields.map) прямой доступ через точечную нотацию
невозможен. Используется индексирование:
errors.items?.[index]?.title?.message
Или универсальный доступ через строковый путь:
const error = errors?.[fieldName]?.message;
Однако такой подход применим только для плоских структур. Для вложенных объектов требуется разбор пути.
При глубокой вложенности и динамических путях часто используется
get:
import get from "lodash/get";
const message = get(errors, "user.address.city.message");
Это позволяет избежать длинных цепочек ?..
Важно учитывать, что структура errors содержит объекты с
полем message, поэтому путь должен оканчиваться на
.message.
В TypeScript структура errors зависит от типа формы:
type FormValues = {
user: {
name: string;
};
};
useForm<FormValues> формирует:
FieldErrors<FormValues>
Доступ:
errors.user?.name?.message
Типизация обеспечивает автодополнение путей, но не исключает
необходимость проверки undefined.
YupResolver отличается тем, что:
Это влияет на доступ к полям: нельзя ожидать массива ошибок, только дерево.
Yup по умолчанию возвращает первую ошибку для поля, если
не включен abortEarly: false.
При конфигурации:
yup.object().shape(
{
email: yup.string().required().email()
},
{ abortEarly: false }
)
структура может содержать несколько ошибок, но
react-hook-form обычно отображает только первую:
errors.email?.message
Перед отображением ошибки используется проверка:
if (errors.password) {
// поле невалидно
}
или более точно:
if (errors.password?.message) {
// есть сообщение ошибки
}
Это важно для отделения состояния “ошибка существует” от “ошибка содержит текст”.
При сложных формах:
{
order: {
items: [
{
product: {
title: string
}
}
]
}
}
доступ к ошибке:
errors.order?.items?.[i]?.product?.title?.message
Каждый сегмент пути соответствует уровню вложенности Yup-схемы.
Объект errors не содержит пустых веток. Это
означает:
undefinedtype, message,
refСледовательно, обращение без проверки приводит к runtime-ошибкам:
errors.user.address.city.message // небезопасно
Правильный вариант:
errors.user?.address?.city?.message
Каждая ошибка может содержать ref:
errors.email.ref
Это используется для:
Хотя ref редко используется напрямую, он сохраняется в
структуре ошибок и может быть полезен при кастомной обработке UI.
В типичной архитектуре UI доступ к ошибкам абстрагируется:
const getError = (errors, name) => errors?.[name]?.message;
или для вложенных структур:
const getNestedError = (errors, path) => get(errors, `${path}.message`);
Это снижает связность компонентов с конкретной структурой формы.
При изменении одного поля YupResolver пересчитывает всю
схему, но обновляет только затронутые ветки:
errors остается согласованной с формойЭто важно при динамических формах с частыми изменениями значений.
?.lodash/get