Контекст в Yup и причины ошибок при его использовании
Контекст в Yup представляет собой дополнительный объект, который
может передаваться валидации и использоваться внутри пользовательских
правил, условных схем и функций test. Несмотря на кажущуюся
простоту механизма, именно работа с контекстом чаще всего становится
источником скрытых и трудноотлавливаемых ошибок в схемах валидации.
В Yup контекст передаётся через параметры методов валидации:
schema.validate(data, { context: { role: 'admin' } })
Или при использовании validateSync:
schema.validateSync(data, { context: { locale: 'ru' } })
Этот объект становится доступен внутри:
test функцийwhen условийОднако ключевая проблема заключается в том, что доступ к контексту не является прямым и одинаковым во всех частях API.
Одной из наиболее распространённых ошибок является использование
стрелочных функций в test. В Yup this внутри
test содержит полезные данные:
this.parent — родительский объектthis.context — переданный контекстthis.path — путь поляОднако стрелочные функции не имеют собственного this,
поэтому доступ к контексту теряется.
Пример неправильного использования:
const schema = yup.object({
age: yup.number().test('check-age', 'Invalid age', (value) => {
return this.parent.role === 'admin';
})
});
В этом случае this будет undefined, и
доступ к context или parent невозможен.
Правильный вариант:
const schema = yup.object({
age: yup.number().test('check-age', 'Invalid age', function (value) {
return this.parent.role === 'admin';
})
});
Контекст в Yup не является глобальным состоянием. Он передаётся
только в момент вызова validate.
Типичная ошибка возникает, когда разработчик предполагает, что контекст будет доступен автоматически во всех местах схемы:
const schema = yup.object({
name: yup.string().test('check', function (value) {
return this.options.context.isActive;
})
});
Проблема проявляется, если validate вызывается без
контекста:
schema.validate(data); // context undefined
В таком случае this.options.context будет
undefined, что приведёт к ошибкам выполнения.
.whenМетод .when позволяет создавать условные схемы,
зависящие от других полей или контекста. Ошибки возникают, когда
контекст используется как зависимость без проверки его наличия.
Пример проблемного кода:
const schema = yup.object({
discount: yup.number().when('$isPremium', (isPremium, schema) => {
return isPremium ? schema.max(50) : schema.max(10);
})
});
Если контекст не передан, isPremium будет
undefined, и логика условия может дать неожиданный
результат.
Более надёжный подход:
const schema = yup.object({
discount: yup.number().when('$isPremium', {
is: true,
then: (schema) => schema.max(50),
otherwise: (schema) => schema.max(10)
})
});
Однако даже здесь необходимо гарантировать передачу контекста при валидации.
Частая архитектурная ошибка — попытка использовать контекст Yup как замену состоянию приложения.
Например:
schema.validate(data, { context: window.appState });
Такой подход приводит к нескольким проблемам:
Контекст должен оставаться локальным и явным, а не использоваться как хранилище состояния.
Асинхронные проверки усиливают проблему. Контекст может быть актуален
в момент вызова, но устареть к моменту выполнения test.
const schema = yup.string().test(
'async-check',
'Invalid',
async function (value) {
const userRole = this.options.context.role;
return await apiCheck(value, userRole);
}
);
Если role изменится между вызовом и завершением запроса,
результат будет некорректным.
Особенно опасно при:
thisЕщё одна скрытая проблема — деструктуризация this,
которая приводит к потере ссылок на внутренний объект Yup.
Неправильно:
test('check', function (value) {
const { context } = this;
return context.flag === true;
});
Хотя в данном случае контекст сохранится, часто деструктурируют глубже:
const { parent, options } = this;
Это создаёт риск, что при изменении внутренней реализации Yup поведение изменится.
Более устойчивый подход — прямое обращение:
this.options.context
this.parent
Переиспользуемые схемы часто становятся источником контекстных ошибок.
const baseSchema = yup.object({
price: yup.number().test(function (value) {
return this.options.context.currency === 'USD';
})
});
При повторном использовании:
baseSchema.validate(data, { context: { currency: 'EUR' } });
Если разработчик забудет передать контекст, схема продолжит работать, но с некорректной логикой.
Это создаёт иллюзию корректной работы при фактической ошибке бизнес-логики.
Контекст в Yup не поддерживает глубокое слияние объектов. Передача вложенных структур часто приводит к неожиданному поведению:
validate(data, {
context: {
user: {
permissions: ['read']
}
}
});
При частичном обновлении:
validate(data, {
context: {
user: {
role: 'admin'
}
}
});
Предыдущие поля permissions будут потеряны, так как
контекст не объединяется автоматически.
При интеграции с библиотеками форм (например, React-экосистемой) контекст часто передаётся один раз при инициализации, а затем не обновляется.
Типичный сценарий:
Это происходит из-за кеширования схемы или отсутствия пересоздания validator instance.
Yup не гарантирует наличие контекста. Во всех местах его использование должно сопровождаться защитой:
const role = this.options.context?.role || 'guest';
Игнорирование этой особенности приводит к:
undefined в условияхwhen ветвлениямКонтекст и this.parent часто путаются, хотя имеют разную
природу:
parent — данные объекта, который валидируетсяcontext — внешние параметры вызоваОшибка возникает, когда логика строится на их взаимозаменяемости:
test(function (value) {
return this.parent.role === this.options.context.role;
});
Такая конструкция делает схему зависимой сразу от двух источников состояния, усложняя предсказуемость и тестирование.
Основная сложность работы с контекстом в Yup заключается не в самом API, а в его семантике: контекст является временным, необязательным и внешним состоянием, которое не встроено в структуру схемы.
Ошибки возникают, когда контекст используется как:
parent или refТакие подходы приводят к неявным зависимостям, которые сложно отслеживать в сложных схемах валидации и больших формах.