В библиотеке Yup все ошибки валидации сводятся к единому типу —
ValidationError. Именно этот объект выбрасывается при
несоответствии данных схеме и содержит всю необходимую информацию для
дальнейшей обработки.
Ключевая особенность модели ошибок Yup заключается в том, что ошибка не является простой строкой. Это структурированный объект, который позволяет анализировать сразу несколько проблем валидации, включая вложенные поля и множественные нарушения правил.
Основные свойства ValidationError:
ValidationErrorYup формирует ошибки в виде дерева, особенно при работе с объектами и массивами. Это позволяет точно определить, какое именно поле не прошло проверку.
Пример структуры:
{
name: "ValidationError",
message: "Validation failed",
path: "user.email",
value: "not-an-email",
inner: [],
errors: ["email must be a valid email"]
}
При сложных схемах:
{
path: "user",
inner: [
{
path: "user.email",
message: "Invalid email"
},
{
path: "user.password",
message: "Password is too short"
}
]
}
Поле inner становится ключевым источником информации при
массовой обработке ошибок.
Одним из важных механизмов управления ошибками является параметр
abortEarly.
По умолчанию Yup останавливает валидацию после первой ошибки:
schema.validate(data, { abortEarly: true })
Это поведение приводит к тому, что:
inner часто пустоеПри отключении:
schema.validate(data, { abortEarly: false })
валидация продолжает выполняться для всех полей, и inner
наполняется полным списком ошибок.
Это особенно важно для форм, где требуется показать все ошибки одновременно.
Стандартный способ обработки ошибок — использование
try/catch при вызове validate.
try {
await schema.validate(data, { abortEarly: false });
} catch (err) {
if (err.name === "ValidationError") {
console.log(err.errors);
}
}
При синхронной валидации:
try {
schema.validateSync(data, { abortEarly: false });
} catch (err) {
console.log(err.errors);
}
Важно учитывать, что validate возвращает Promise, а
validateSync выбрасывает исключение сразу.
Yup поддерживает асинхронные проверки через .test() и
внешние запросы.
Пример:
const schema = yup.string().test(
"check-username",
"Username already exists",
async (value) => {
const res = await api.checkUsername(value);
return res.available;
}
);
При такой валидации ошибки формируются так же, как и в синхронном
режиме, но требуют await при обработке:
try {
await schema.validate(data);
} catch (err) {
console.log(err.message);
}
Асинхронные ошибки не отличаются по структуре, но могут увеличивать
время полного формирования inner.
При работе с формами часто требуется преобразовать
ValidationError в структуру вида:
{
email: "Invalid email",
password: "Too short"
}
Для этого используется обход inner:
function mapYupErrors(err) {
const errors = {};
err.inner.forEach((e) => {
if (e.path) {
errors[e.path] = e.message;
}
});
return errors;
}
Если abortEarly: true, необходимо учитывать
fallback:
if (err.path) {
errors[err.path] = err.message;
}
Yup использует точечную нотацию для вложенных объектов:
const schema = yup.object({
user: yup.object({
email: yup.string().email().required()
})
});
При ошибке путь будет:
user.email
Это позволяет напрямую связывать ошибки с UI-компонентами или структурами состояния.
Для массивов используется индекс:
items[0].name
или в нормализованной форме:
items.0.name
Механизм .test() позволяет управлять формированием
ошибок вручную.
yup.number().test(
"is-positive",
"Value must be positive",
(value) => value > 0
);
В случае возврата false автоматически создаётся
ValidationError с указанным сообщением.
Также возможно динамическое формирование ошибки:
yup.string().test(
"custom-check",
function (value) {
if (!value) {
return this.createError({ message: "Required field" });
}
return true;
}
);
Использование this.createError позволяет задавать:
Yup поддерживает глобальную настройку сообщений об ошибках:
import * as yup from "yup";
yup.setLocale({
mixed: {
required: "Поле обязательно"
},
string: {
email: "Некорректный email"
}
});
После этого все ошибки, не переопределённые вручную, будут использовать локализованные сообщения.
Это влияет на поле message в
ValidationError, но не изменяет структуру
inner.
При сложных схемах полезно приводить ошибки к единому формату:
function normalizeErrors(error) {
return error.inner.reduce((acc, curr) => {
const path = curr.path || "form";
acc[path] = acc[path] || [];
acc[path].push(curr.message);
return acc;
}, {});
}
Такой подход позволяет:
При использовании when() структура ошибок может зависеть
от состояния других полей:
yup.string().when("role", {
is: "admin",
then: (schema) => schema.required("Admin email required")
});
В таких случаях path остаётся стабильным, но
message формируется динамически.
Ошибки могут появляться или исчезать в зависимости от входных данных, что усложняет кеширование результатов валидации.
При работе с массивами объектов Yup генерирует множественные ошибки с
одинаковым path, но разными индексами:
items[0].name
items[1].name
Для корректной агрегации важно учитывать полный путь:
errors[curr.path] = curr.message;
или преобразовывать в структуру:
{
items: [
{ name: "Error" },
{ name: "Another error" }
]
}
inner заполняется только при
abortEarly: falsemessage содержит только первую ошибку при одиночной
валидацииerrors может содержать дубликаты сообщений при сложных
схемахpath может быть undefined для корневых
ошибокПри использовании .concat() или объединении схем ошибки
не теряют контекст, но могут дублироваться:
const schema = baseSchema.concat(extraSchema);
В этом случае inner может содержать пересекающиеся
path, что требует дедупликации при обработке.
В прикладных системах часто применяется единый слой обработки:
ValidationErrorinner в mapТакой слой отделяет Yup от бизнес-логики и упрощает поддержку сложных форм и API-валидаций.