Асинхронная валидация в контексте YupResolver приобретает ключевое значение при работе с формами, где часть правил зависит от удалённых источников данных: проверка уникальности логина, валидация email через сервер, сверка промокодов, динамические ограничения по бизнес-логике.
В основе экосистемы лежит взаимодействие между схемой валидации Yup и форм-менеджером React Hook Form через адаптер YupResolver.
Асинхронная логика в этой архитектуре не выполняется напрямую в UI-компонентах. Она инкапсулируется внутри схемы Yup или внутри resolver-слоя, который трансформирует результат в формат ошибок React Hook Form.
Асинхронные проверки в Yup реализуются через:
test с возвратом PromiseБазовая структура асинхронного теста:
const schema = yup.object({
email: yup.string()
.email()
.required()
.test("check-email", "Email уже занят", async (value) => {
const res = await fetch(`/api/check-email?email=${value}`);
const data = await res.json();
return data.available === true;
})
});
Ключевое поведение:
YupResolver выступает промежуточным слоем между формой и схемой.
При вызове resolver происходит следующая последовательность:
schema.validate(values, { abortEarly: false }){ values, errors }Важно, что весь процесс асинхронный, даже если часть правил синхронная.
Параметр abortEarly: false критичен для асинхронной
валидации.
yupResolver(schema, { abortEarly: false })
При true:
При false:
В контексте серверных проверок это предотвращает непредсказуемое состояние формы.
Асинхронные проверки внутри Yup не гарантируют строгую
последовательность. При нескольких test:
username: yup.string()
.test("check-username", async () => {...})
.test("check-reserved", async () => {...})
Поведение:
Это важно учитывать при зависимых проверках.
При частом вводе значений возникает ситуация:
a, затем ab, затем
abcБез защиты возможна ситуация, когда:
a приходит позже и перезаписывает
abcРешение реализуется вне Yup, на уровне формы:
useForm({
resolver: yupResolver(schema),
mode: "onChange"
});
Дополнительно используется debounce на уровне input.
.test("check-email", async function(value) {
const current = this.parent.email;
const res = await apiCheck(value);
return value === current ? res.available : true;
});
let controller;
async function checkEmail(email) {
if (controller) controller.abort();
controller = new AbortController();
return fetch(`/api/check?email=${email}`, {
signal: controller.signal
});
}
При интеграции с backend-валидацией важно минимизировать количество запросов.
Подходы:
const cache = new Map();
const checkEmail = async (email) => {
if (cache.has(email)) return cache.get(email);
const result = await api(email);
cache.set(email, result);
return result;
};
Вместо частых вызовов:
В Yup асинхронная ошибка может возникать в двух формах:
throw new yup.ValidationError("Ошибка сервера");
return false;
Разница:
false — простое нарушение правилаValidationError — структурированная ошибка с path и
typeReact Hook Form использует это для привязки ошибки к конкретному полю.
Асинхронное поведение сильно зависит от режима:
onSubmit — async проверки выполняются только при
сабмитеonBlur — при потере фокусаonChange — при каждом измененииПри onChange нагрузка на async слой максимальна.
Типичный сценарий:
password: yup.string()
.required()
.test("strength", async (value) => checkStrength(value))
.test("breached", async (value) => checkBreachDatabase(value))
Особенности:
Асинхронные проверки часто зависят от других полей:
.test("check-zip-city", async function(value) {
const city = this.parent.city;
return apiValidateCityZip(city, value);
});
Здесь критично использовать this.parent, поскольку
resolver передаёт актуальное состояние формы.
При больших формах с множеством async правил возникает проблема деградации производительности.
Применяются стратегии:
Выполнение async проверки только при определённых условиях:
.test("check-if-needed", async function(value) {
if (!this.options.context.enableCheck) return true;
return apiCheck(value);
});
when("role", {
is: "admin",
then: schema.test("admin-check", async () => {...})
});
Сначала выполняется быстрый sync-layer, затем async-layer.
Асинхронная валидация должна учитывать:
Типичная стратегия:
.test("network-safe", async (value) => {
try {
return await apiCheck(value);
} catch (e) {
return true;
}
});
В этом случае ошибка сети не блокирует ввод пользователя, но может логироваться отдельно.
React Hook Form не вызывает лишние рендеры при каждом async шаге, но:
formState.errors обновляется после завершения
PromiseisValidating может использоваться для индикаторов
загрузкиisValid пересчитывается после каждого
resolver-циклаАсинхронная валидация становится частью реактивного потока состояния формы, а не внешним эффектом.