Динамические сообщения с интерполяцией

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

В связке с YupResolver динамические сообщения сохраняют свою природу до момента выполнения валидации, после чего результат преобразуется в структуру ошибок, совместимую с React Hook Form.


Базовая интерполяция через функции сообщений

Наиболее прямой способ динамической генерации текста ошибок — использование функций вместо строк.

import * as yup from "yup";

const schema = yup.object({
  password: yup
    .string()
    .min(8, ({ min }) => `Минимальная длина пароля — ${min} символов`)
    .required("Пароль обязателен"),
});

Функция сообщения получает объект параметров, зависящий от типа валидации. В случае .min() доступны значения min, max, value, path. Это позволяет формировать текст без внешних условий и дополнительных вычислений.


Контекстная интерполяция через context

Yup поддерживает передачу контекста при выполнении валидации. Этот механизм особенно важен для сценариев, где сообщение зависит от внешних параметров: ролей пользователя, настроек формы или бизнес-логики.

const schema = yup.object({
  username: yup.string().test(
    "check-role",
    ({ path }, context) => `Поле ${path} недоступно для роли ${context.options.context.role}`,
    function (value) {
      const role = this.options.context?.role;
      return role !== "guest" || value?.length > 0;
    }
  ),
});

При вызове валидации:

schema.validate(data, {
  context: { role: "guest" }
});

Контекст становится частью вычисления как логики, так и сообщения. Это позволяет синхронизировать текст ошибки с бизнес-правилами без дублирования условий.


Интерполяция внутри .test() как основной инструмент гибких сообщений

Метод .test() предоставляет полный контроль над валидацией и генерацией сообщений. Он используется, когда встроенные методы (required, min, max) недостаточны.

const schema = yup.object({
  age: yup.number().test(
    "age-range",
    ({ min, max, value }) =>
      `Возраст ${value} не входит в допустимый диапазон ${min}–${max}`,
    function (value) {
      const min = 18;
      const max = 60;

      if (value == null) return false;

      return value >= min && value <= max;
    }
  ),
});

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


Использование label и подстановочных значений

Yup позволяет задавать метки полей через .label(), которые затем используются в сообщениях.

const schema = yup.object({
  email: yup
    .string()
    .label("Электронная почта")
    .required("${label} обязательно для заполнения"),
});

Однако строковая интерполяция ${label} не является встроенной в чистом Yup. Для её реализации применяется либо пользовательская функция сообщения, либо глобальная локализация через yup.setLocale.


Глобальная локализация сообщений и шаблоны

Механизм setLocale позволяет централизованно задавать шаблоны сообщений с параметрами.

import * as yup from "yup";

yup.setLocale({
  mixed: {
    required: "${path} обязательно",
  },
  string: {
    min: "${path} должен содержать не менее ${min} символов",
  },
});

Внутренние плейсхолдеры (${path}, ${min}, ${max}) заменяются во время выполнения валидации. Это один из немногих встроенных механизмов интерполяции, поддерживаемых Yup на уровне конфигурации.


Динамические сообщения на основе значений поля

Часто сообщение зависит от самого введённого значения. В таких случаях используется доступ к value в функциях сообщений.

yup.string().test(
  "check-domain",
  ({ value }) => `Домен ${value} не поддерживается`,
  (value) => {
    const allowed = ["example.com", "test.com"];
    return value && allowed.includes(value);
  }
);

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


Интерполяция в связке с YupResolver

YupResolver из @hookform/resolvers/yup не изменяет и не трансформирует сообщения. Его роль ограничивается преобразованием результата validate() или validateSync() в структуру ошибок React Hook Form.

import { yupResolver } from "@hookform/resolvers/yup";

const resolver = yupResolver(schema);

При этом динамические сообщения, сформированные внутри Yup, передаются в неизменном виде:

  • строковые шаблоны уже интерполированы
  • функции сообщений выполнены во время валидации
  • контекст уже применён

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


Композиция сообщений с зависимыми полями

В случаях, когда сообщение зависит от других полей формы, используется доступ к родительскому объекту через this.parent.

const schema = yup.object({
  password: yup.string().required(),
  confirmPassword: yup.string().test(
    "match",
    () => "Пароли не совпадают",
    function (value) {
      return value === this.parent.password;
    }
  ),
});

Хотя сообщение здесь статическое, оно может быть легко превращено в динамическое:

(value) => `Подтверждение "${value}" не совпадает с паролем`

Комбинация шаблонов и вычисляемых сообщений

На практике часто используется гибридный подход: глобальные шаблоны + локальные функции.

yup.setLocale({
  string: {
    min: "${path} слишком короткое",
  },
});

const schema = yup.object({
  login: yup
    .string()
    .min(5, ({ min }) => `Логин должен быть не короче ${min} символов`),
});

Глобальные правила покрывают базовые сценарии, а локальные функции уточняют контекст конкретного поля.


Ограничения интерполяции в Yup

Несмотря на гибкость, механизм имеет ряд особенностей:

  • отсутствует полноценный шаблонизатор
  • нет автоматической локализации значений вне setLocale
  • функции сообщений не кешируются и вызываются при каждой валидации
  • интерполяция зависит от типа валидатора и структуры ошибки

Эти ограничения компенсируются явным контролем над формированием текста, что делает систему предсказуемой при интеграции с YupResolver.


Поведение ошибок после преобразования в React Hook Form

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

{
  type: "min",
  message: "Минимальная длина пароля — 8 символов",
  ref: ...
}

Динамическое содержимое сообщения уже зафиксировано, что упрощает дальнейшую обработку на уровне UI-компонентов.