When - условная валидация

Условная валидация в схемах Yup — это механизм, позволяющий изменять правила проверки данных в зависимости от значения других полей. В контексте YupResolver эта логика сохраняется полностью, поскольку резолвер лишь передаёт данные валидационной схеме Yup без изменения её поведения.

Ключевым инструментом реализации условной логики является метод when, который позволяет описывать зависимости между полями и динамически менять требования к значению.


Базовая концепция when

Метод when используется для создания зависимостей между полями схемы. Он позволяет реагировать на изменения других значений формы и изменять правила валидации текущего поля.

Общий синтаксис:

field: yup.string().when('otherField', (otherValue, schema) => {
  return otherValue ? schema.required() : schema.notRequired();
})

В этом примере поле становится обязательным только при наличии значения в otherField.


Простая зависимость одного поля от другого

Рассмотрим классический сценарий: поле “пароль подтверждения” должно быть обязательным только если пользователь ввёл пароль.

import * as yup from 'yup';

const schema = yup.object({
  password: yup.string().min(6, 'Минимум 6 символов'),
  confirmPassword: yup.string().when('password', (password, schema) => {
    if (password) {
      return schema.required('Подтверждение обязательно')
        .oneOf([yup.ref('password')], 'Пароли должны совпадать');
    }
    return schema.notRequired();
  })
});

Здесь важно понимать:

  • when получает значение зависимого поля (password)
  • возвращается модифицированная схема
  • правила могут полностью изменяться в зависимости от состояния формы

Несколько зависимостей

when поддерживает массив зависимостей. Это позволяет строить более сложные условия.

age: yup.number().when(['isAdult', 'country'], (isAdult, country, schema) => {
  if (isAdult && country === 'KZ') {
    return schema.min(21, 'Возраст должен быть не менее 21');
  }
  return schema.min(18, 'Минимальный возраст 18');
})

В этом случае логика зависит сразу от двух полей. Порядок аргументов соответствует порядку зависимостей.


Использование объекта контекста вместо позиционных аргументов

Более современный и читаемый подход — использование объекта context:

age: yup.number().when(['isAdult', 'country'], {
  is: (isAdult, country) => isAdult && country === 'KZ',
  then: (schema) => schema.min(21, 'Возраст 21+'),
  otherwise: (schema) => schema.min(18, 'Возраст 18+')
})

Преимущества подхода:

  • более декларативная структура
  • легче расширять условия
  • меньше ошибок с порядком аргументов

Условная обязательность полей

Частый сценарий — динамическое добавление required().

deliveryAddress: yup.string().when('deliveryType', {
  is: 'courier',
  then: (schema) => schema.required('Укажите адрес доставки'),
  otherwise: (schema) => schema.notRequired()
})

Такая схема часто используется в формах оформления заказов, анкетах и многошаговых формах.


Условная валидация числовых полей

Для чисел when особенно полезен при проверке диапазонов.

discount: yup.number().when('hasDiscount', {
  is: true,
  then: (schema) => schema.min(1).max(50),
  otherwise: (schema) => schema.oneOf([0])
})

Здесь поле discount либо строго равно 0, либо находится в диапазоне скидок.


Условная логика для массивов

Массивы часто требуют динамических ограничений длины.

tags: yup.array().when('allowMultipleTags', {
  is: true,
  then: (schema) => schema.min(1, 'Добавьте минимум один тег'),
  otherwise: (schema) => schema.max(1, 'Можно выбрать только один тег')
})

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


Вложенные зависимости

when корректно работает и во вложенных объектах.

const schema = yup.object({
  user: yup.object({
    role: yup.string(),
    permissions: yup.array().when('role', {
      is: 'admin',
      then: (schema) => schema.min(5, 'Администратор должен иметь минимум 5 прав'),
      otherwise: (schema) => schema.max(2)
    })
  })
});

При работе с вложенностью важно учитывать путь к полю в объекте.


Динамическая логика с функцией is

Функция is позволяет реализовать сложные условия:

status: yup.string().when(['age', 'country'], {
  is: (age, country) => age > 18 && country === 'KZ',
  then: (schema) => schema.oneOf(['active', 'verified']),
  otherwise: (schema) => schema.oneOf(['guest'])
})

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


Поведение в YupResolver

При использовании YupResolver в связке с React Hook Form условная логика работает на этапе валидации схемы.

import { useForm } from 'react-hook-form';
import { yupResolver } from '@hookform/resolvers/yup';

const form = useForm({
  resolver: yupResolver(schema)
});

Особенности поведения:

  • YupResolver не кеширует результаты условий
  • каждое изменение формы пересчитывает правила заново
  • зависимости when работают на основе текущего состояния формы
  • нет необходимости вручную триггерить пересборку схемы

Влияние порядка полей

В сложных схемах порядок объявления может влиять на читаемость, но не на исполнение.

Однако важно учитывать:

  • поля, используемые в when, должны быть объявлены в схеме
  • циклические зависимости могут привести к непредсказуемому поведению
  • рекомендуется избегать взаимных зависимостей A → B и B → A

Частые ошибки при использовании when

1. Потеря this контекста

Некорректно:

when('field', (value, schema) => {
  return this.min(5); // ошибка контекста
})

Корректно:

when('field', (value, schema) => {
  return schema.min(5);
})

2. Неправильный порядок аргументов

when(['a', 'b'], (b, a, schema) => {}) // логическая ошибка

Аргументы всегда соответствуют порядку зависимостей.


3. Попытка использовать when вне yup-схемы

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


Комбинация when с другими методами

Условная валидация часто используется вместе с:

  • test() для кастомных проверок
  • oneOf() для ограниченных списков
  • required() для обязательности
  • nullable() для работы с пустыми значениями

Пример комбинированной логики:

email: yup.string()
  .email('Некорректный email')
  .when('subscribe', {
    is: true,
    then: (schema) => schema.required('Email обязателен при подписке'),
    otherwise: (schema) => schema.notRequired().nullable()
  })

Глубокие сценарии бизнес-логики

В реальных приложениях when используется для моделирования сложных бизнес-правил:

  • разные правила для ролей пользователя
  • динамическая форма оплаты
  • адаптивные анкеты
  • конфигурационные формы админ-панелей

Пример:

paymentMethod: yup.string().when('country', {
  is: (country) => country === 'KZ',
  then: (schema) => schema.oneOf(['card', 'kaspi']),
  otherwise: (schema) => schema.oneOf(['card', 'paypal'])
})

Производительность условной валидации

Хотя when добавляет гибкость, он также увеличивает сложность схемы.

Рекомендации:

  • избегать избыточных зависимостей
  • не строить глубокие цепочки when внутри when
  • минимизировать количество вычисляемых условий
  • использовать object().shape() для логического разделения схем

Читаемость и поддержка кода

При большом количестве условий схема может стать трудно читаемой. В таких случаях полезно:

  • выносить условия в отдельные функции
  • использовать именованные схемы
  • группировать зависимости по логическим блокам

Пример выделения логики:

const isKZAdult = (age, country) => age > 18 && country === 'KZ';

const schema = yup.object({
  age: yup.number().when(['age', 'country'], {
    is: isKZAdult,
    then: (schema) => schema.min(21),
    otherwise: (schema) => schema.min(18)
  })
});

Поведение с пустыми значениями

Важно учитывать, что when срабатывает даже при undefined или null.

field: yup.string().when('other', {
  is: (val) => !!val,
  then: (schema) => schema.required(),
})

Проверка должна учитывать falsy-значения, иначе логика может быть нарушена.


Совместимость с динамическими формами

В формах, где поля добавляются или удаляются динамически, when остаётся стабильным механизмом:

  • схема не требует пересоздания
  • YupResolver автоматически учитывает изменения
  • условия пересчитываются при каждом submit/validate вызове

Архитектурное значение when

when позволяет перенести бизнес-логику из компонентов формы в уровень схемы валидации. Это приводит к:

  • централизованной логике проверки данных
  • уменьшению связности UI и бизнес-правил
  • упрощению тестирования схем
  • повышению предсказуемости поведения формы