В связке с React Hook Form и Yup схема валидации выполняется на
уровне resolver-функции, которая преобразует результат
Yup.validate() в формат, понятный форме. Именно на этом
этапе чаще всего теряется прозрачность ошибок, если не настроено
корректное логирование.
Ключевой момент: YupResolver не выбрасывает ошибки
наружу, а возвращает структурированный объект:
{
values: T,
errors: FieldErrors<T>
}
Отладка сводится к пониманию того, как именно Yup формирует
ValidationError, и как resolver трансформирует его в
FieldErrors.
Yup генерирует объект ValidationError, который содержит
несколько важных полей:
message — текст ошибкиpath — путь к полю формыinner — массив вложенных ошибок (при
abortEarly: false)errors — список сообщенийvalue — исходное значениеПри стандартной настройке abortEarly: true возвращается
только первая ошибка, что существенно усложняет диагностику комплексных
форм.
Yup.object({
email: Yup.string().email().required(),
password: Yup.string().min(8).required(),
});
При ошибке password без abortEarly: false
отладка может скрывать остальные проблемы формы.
Для расширенного логирования критично отключать ранний выход:
const schema = Yup.object({
email: Yup.string().email().required(),
password: Yup.string().min(8).required(),
}).validate(data, { abortEarly: false });
В контексте YupResolver это прокидывается через
конфигурацию:
yupResolver(schema, {
abortEarly: false,
});
Это позволяет получить полный массив inner, который
используется для построения карты ошибок по полям.
Стандартный yupResolver скрывает внутреннюю обработку
ошибок. Для отладки часто используется обёртка:
import { yupResolver } from '@hookform/resolvers/yup';
const debugResolver = (schema) => {
const resolver = yupResolver(schema);
return async (values, context, options) => {
const result = await resolver(values, context, options);
console.group('YupResolver Debug');
console.log('Values:', values);
console.log('Errors:', result.errors);
console.groupEnd();
return result;
};
};
Такой подход позволяет отследить момент трансформации данных до попадания в React Hook Form.
ValidationError.innerНаиболее информативная часть при сложных схемах:
catch (err) {
if (err.name === 'ValidationError') {
console.log(err.inner);
}
}
Каждый элемент inner имеет:
path — ключ поля (user.email)message — текстtype — тип правила (required,
min, email)Типичная проблема: несовпадение path с названием поля в
форме. Это приводит к тому, что ошибка существует, но не
отображается.
При работе с вложенными схемами:
const schema = Yup.object({
user: Yup.object({
email: Yup.string().required(),
}),
});
React Hook Form ожидает ошибки вида:
errors.user.email
Но Yup может вернуть path: "user.email", что требует
корректного маппинга внутри resolver.
При кастомизации важно проверять:
dot notationdefaultValuesnullable() и defined()При логировании часто теряется часть информации из-за:
JSON.stringify(err) (теряет методы и внутренние
поля)Корректный подход:
console.dir(err, { depth: null });
или выборочное логирование:
console.log({
path: err.path,
message: err.message,
inner: err.inner,
});
После выполнения resolver результат попадает в
formState.errors:
const {
formState: { errors },
} = useForm({
resolver: yupResolver(schema),
});
Логирование на уровне компонента:
useEffect(() => {
console.log('Form errors updated:', errors);
}, [errors]);
Это позволяет отследить, как именно resolver преобразовал результат Yup.
TypeScript иногда скрывает реальные структуры ошибок. Для диагностики используется явное расширение:
const safeResolver = async (values, context, options) => {
try {
const result = await yupResolver(schema)(values, context, options);
if (Object.keys(result.errors).length) {
console.warn('Validation errors:', result.errors);
}
return result;
} catch (e) {
console.error('Resolver crash:', e);
throw e;
}
};
Несмотря на наличие ошибок в Yup, errors может быть
пустым:
defaultValues и schemaregister)shouldUnregister: truename в inputref в кастомных компонентахПри использовании Yup.lazy или асинхронных проверок:
Yup.string().test('async-check', async (value) => {
const res = await apiCheck(value);
return res.valid;
});
ошибки могут приходить с задержкой, что требует логирования промежуточных состояний:
console.log('Validating value:', value);
и фиксации результата каждого test.
Иногда требуется исключить resolver и проверить Yup напрямую:
try {
await schema.validate(data, { abortEarly: false });
} catch (e) {
console.log(e.inner);
}
Это позволяет определить, где именно возникает расхождение: в Yup или
в yupResolver.
В production-среде прямой console.log недостаточен.
Используются внешние системы:
Ключевой подход — логировать только нормализованные данные:
{
schema: 'userForm',
errors: normalizeYupError(err),
values: sanitize(values),
}
Функция нормализации обычно извлекает только path,
message, type, исключая чувствительные
данные.
При использовании yupResolver типизация ошибок может
расходиться с фактической структурой:
FieldErrors<T>
но Yup возвращает динамическую структуру. Для диагностики используется:
type DebugErrors = Record<string, any>;
или временное отключение строгой типизации при отладке.
Yup может вернуть несколько ошибок для одного path, но
React Hook Form отображает только одну. Это приводит к потере
информации.
Логирование inner позволяет увидеть полную картину:
err.inner.filter(e => e.path === 'email');
При сложных схемах логирование включает измерение времени:
const start = performance.now();
await resolver(values);
console.log('Validation time:', performance.now() - start);
Это помогает выявить тяжёлые участки схемы (matches,
test, вложенные object).
В development режиме:
inner массив полон данныхВ production:
Логирование должно учитывать это различие, иначе диагностика становится неполной.