Валидационные сообщения в схемах 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. Это позволяет формировать текст без внешних условий и
дополнительных вычислений.
contextYup поддерживает передачу контекста при выполнении валидации. Этот механизм особенно важен для сценариев, где сообщение зависит от внешних параметров: ролей пользователя, настроек формы или бизнес-логики.
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);
}
);
Такой подход особенно полезен при обработке пользовательского ввода, где ошибка должна отражать конкретное значение, а не только тип нарушения.
YupResolverYupResolver из @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} символов`),
});
Глобальные правила покрывают базовые сценарии, а локальные функции уточняют контекст конкретного поля.
Несмотря на гибкость, механизм имеет ряд особенностей:
setLocaleЭти ограничения компенсируются явным контролем над формированием
текста, что делает систему предсказуемой при интеграции с
YupResolver.
После прохождения через YupResolver структура ошибки
становится стандартизированной:
{
type: "min",
message: "Минимальная длина пароля — 8 символов",
ref: ...
}
Динамическое содержимое сообщения уже зафиксировано, что упрощает дальнейшую обработку на уровне UI-компонентов.