Неправильное использование контекста

Контекст в 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 изменится между вызовом и завершением запроса, результат будет некорректным.

Особенно опасно при:

  • debounce-валидации
  • React rerender циклах
  • повторных submit-операциях

Ошибки при деструктуризации 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 ветвлениям
  • падениям в runtime при строгих проверках

Смешивание контекста и parent-данных

Контекст и this.parent часто путаются, хотя имеют разную природу:

  • parent — данные объекта, который валидируется
  • context — внешние параметры вызова

Ошибка возникает, когда логика строится на их взаимозаменяемости:

test(function (value) {
  return this.parent.role === this.options.context.role;
});

Такая конструкция делает схему зависимой сразу от двух источников состояния, усложняя предсказуемость и тестирование.


Итоговая архитектурная проблема контекста

Основная сложность работы с контекстом в Yup заключается не в самом API, а в его семантике: контекст является временным, необязательным и внешним состоянием, которое не встроено в структуру схемы.

Ошибки возникают, когда контекст используется как:

  • глобальное состояние
  • источник бизнес-логики
  • механизм хранения данных
  • замена parent или ref

Такие подходы приводят к неявным зависимостям, которые сложно отслеживать в сложных схемах валидации и больших формах.