При работе с формами и сложными схемами валидации ошибки часто возникают не в пользовательских данных, а в самой конфигурации схемы. Логирование позволяет:
transform);nullable,
required, default;В экосистеме React чаще всего используется связка:
Yup@hookform/resolversreact-hook-formyupResolver выступает посредником между схемой Yup и
системой обработки формы.
При использовании yupResolver цепочка выглядит следующим
образом:
Форма → react-hook-form → yupResolver → Yup Schema → Validation Result
Каждый этап может быть источником ошибок:
| Этап | Возможные проблемы |
|---|---|
| react-hook-form | неправильная регистрация полей |
| yupResolver | некорректная передача context |
| Yup schema | ошибки в правилах |
| transform | неожиданные преобразования |
| async validation | race condition |
| UI rendering | устаревшие ошибки |
Логирование помогает определить проблемный участок.
npm install yup react-hook-form @hookform/resolvers
import * as yup from 'yup';
export const schema = yup.object({
email: yup
.string()
.email('Некорректный email')
.required('Email обязателен'),
age: yup
.number()
.min(18, 'Минимальный возраст 18')
});
import { useForm } from 'react-hook-form';
import { yupResolver } from '@hookform/resolvers/yup';
const {
register,
handleSubmit,
formState: { errors }
} = useForm({
resolver: yupResolver(schema)
});
Перед интеграцией в форму удобно тестировать схему напрямую.
schema.validate(data)
.then(result => {
console.log('VALID:', result);
})
.catch(error => {
console.log('ERROR:', error);
});
schema.validate(data)
.catch(error => {
console.log(error.name);
console.log(error.message);
console.log(error.path);
console.log(error.errors);
console.log(error.inner);
});
ValidationError {
name: 'ValidationError',
message: 'Email обязателен',
path: 'email',
errors: [
'Email обязателен'
],
inner: [...]
}
По умолчанию Yup возвращает первую найденную ошибку.
schema.validate(data, {
abortEarly: false
})
.catch(error => {
console.log(error.inner);
});
error.inner.forEach(item => {
console.log({
path: item.path,
message: item.message,
value: item.value
});
});
Вывод:
{
path: 'email',
message: 'Email обязателен',
value: ''
}
Метод test() — ключевая точка глубокой диагностики.
const schema = yup.object({
username: yup.string().test(
'username-check',
'Некорректное имя',
function(value) {
console.log('VALUE:', value);
return value !== 'admin';
}
)
});
test(
'age-check',
'Ошибка возраста',
function(value) {
console.log(this);
return true;
}
)
function(value) {
console.log(this.path);
console.log(this.parent);
console.log(this.options);
console.log(this.schema);
return true;
}
Позволяет видеть весь объект формы.
password: yup.string(),
confirmPassword: yup.string()
.test(
'match',
'Пароли не совпадают',
function(value) {
console.log(this.parent);
return value === this.parent.password;
}
)
transform() особенно важен, потому что преобразование
происходит до валидации.
const schema = yup.object({
age: yup.number()
.transform((value, originalValue) => {
console.log('TRANSFORM');
console.log('value:', value);
console.log('original:', originalValue);
return Number(originalValue);
})
});
.transform((value, originalValue) => {
const parsed = Number(originalValue);
console.log({
originalValue,
parsed,
isNaN: Number.isNaN(parsed)
});
return parsed;
})
Метод cast() позволяет проверить преобразования без
запуска валидации.
const result = schema.cast({
age: '25'
});
console.log(result);
const rawData = {
age: '18',
isAdmin: 'true'
};
console.log('RAW:', rawData);
const parsed = schema.cast(rawData);
console.log('PARSED:', parsed);
Один из лучших способов диагностики — создание собственного wrapper
вокруг yupResolver.
import { yupResolver } from '@hookform/resolvers/yup';
const loggingResolver = (schema) => {
const resolver = yupResolver(schema);
return async (data, context, options) => {
console.log('INPUT DATA:', data);
const result = await resolver(
data,
context,
options
);
console.log('RESOLVER RESULT:', result);
return result;
};
};
useForm({
resolver: loggingResolver(schema)
});
const {
formState: { errors }
} = useForm({
resolver: yupResolver(schema)
});
useEffect(() => {
console.log(errors);
}, [errors]);
{
email: {
type: 'required',
message: 'Email обязателен'
}
}
const loggingResolver = (schema) => {
const resolver = yupResolver(schema);
return async (data, context, options) => {
console.group('VALIDATION START');
console.log('DAT A:', data);
console.log('CONTEXT:', context);
console.log('OPTIONS:', options);
const start = performance.now();
try {
const result = await resolver(
data,
context,
options
);
console.log('RESULT:', result);
return result;
} catch(error) {
console.error('VALIDATION ERROR:', error);
throw error;
} finally {
const end = performance.now();
console.log('TIME:', end - start);
console.groupEnd();
}
};
};
Асинхронные проверки — частый источник нестабильного поведения.
email: yup.string().test(
'email-exists',
'Email уже используется',
async function(value) {
console.log('CHECK EMAIL:', value);
const response = await fetch('/api/check');
const result = await response.json();
console.log(result);
return !result.exists;
}
)
async function(value) {
const start = Date.now();
const response = await fetch('/api/check');
const end = Date.now();
console.log('REQUEST TIME:', end - start);
return true;
}
При быстром вводе могут завершаться устаревшие запросы.
let requestId = 0;
test(
'async-check',
'Ошибка',
async function(value) {
requestId++;
const currentId = requestId;
console.log('START:', currentId);
await fakeRequest();
console.log('END:', currentId);
return true;
}
)
const schema = yup.object({
type: yup.string(),
taxNumber: yup.string()
.when('type', {
is: 'company',
then: (schema) => {
console.log('COMPANY VALIDATION');
return schema.required();
},
otherwise: (schema) => {
console.log('PERSON VALIDATION');
return schema;
}
})
});
const dynamicSchema = yup.lazy((value) => {
console.log('LAZY VALUE:', value);
if (typeof value === 'string') {
return yup.string();
}
if (typeof value === 'number') {
return yup.number();
}
return yup.mixed();
});
context позволяет передавать внешние данные в схему.
useForm({
resolver: yupResolver(schema),
context: {
role: 'admin'
}
});
test(
'role-check',
'Недостаточно прав',
function(value) {
console.log(this.options.context);
return true;
}
)
const schema = yup.object({
profile: yup.object({
firstName: yup.string(),
contacts: yup.object({
phone: yup.string()
})
})
});
.catch(error => {
error.inner.forEach(item => {
console.log(item.path);
});
});
Вывод:
profile.contacts.phone
const schema = yup.object({
tags: yup.array()
.of(
yup.string().required()
)
});
.catch(error => {
error.inner.forEach(item => {
console.log(item.path);
});
});
Вывод:
tags[0]
tags[1]
const logger = {
info(message, payload) {
console.log(message, payload);
},
error(message, payload) {
console.error(message, payload);
}
};
test(
'custom-check',
'Ошибка',
function(value) {
logger.info('VALIDATION', {
path: this.path,
value
});
return true;
}
)
В production нельзя бесконтрольно использовать
console.log.
const isDev = process.env.NODE_ENV === 'development';
if (isDev) {
console.log(data);
}
export const debugValidation = (...args) => {
if (
process.env.NODE_ENV === 'development'
) {
console.log(...args);
}
};
Большие схемы могут замедлять интерфейс.
const start = performance.now();
await schema.validate(data);
const end = performance.now();
console.log(`Validation: ${end - start}ms`);
test(
'heavy-check',
'Ошибка',
async function(value) {
const start = performance.now();
await heavyOperation();
const end = performance.now();
console.log(this.path, end - start);
return true;
}
)
export const withValidationDebug = (
schema
) => {
return {
async validate(data, options) {
console.group('YUP DEBUG');
console.log('INPUT:', data);
try {
const result = await schema.validate(
data,
options
);
console.log('SUCCESS:', result);
return result;
} catch(error) {
console.error('FAILED:', error);
throw error;
} finally {
console.groupEnd();
}
}
};
};
console.log(value === undefined);
console.log(value === null);
.transform((value, originalValue) => {
console.log(typeof originalValue);
return originalValue === ''
? undefined
: value;
})
console.log(error.path);
console.log(schema.cast(data));
export const createValidationDebugger = (
schema
) => {
const resolver = yupResolver(schema);
return async (
data,
context,
options
) => {
const start = performance.now();
console.group('VALIDATION');
console.log('DAT A:', data);
console.log('CONTEXT:', context);
try {
const result = await resolver(
data,
context,
options
);
console.log('VALUES:', result.values);
console.log('ERRORS:', result.errors);
return result;
} catch(error) {
console.error(error);
throw error;
} finally {
const end = performance.now();
console.log(
'DURATION:',
`${end - start}ms`
);
console.groupEnd();
}
};
};
console.log(value);
console.log({
type: 'validation',
field: this.path,
value,
timestamp: Date.now()
});
logger.info('Validation started');
logger.warn('Suspicious value');
logger.error('Validation failed');
console.log(schema.describe());
{
type: 'object',
fields: {
email: {
type: 'string',
tests: [...]
}
}
}
const description = schema.describe();
console.log(
JSON.stringify(description, null, 2)
);
await schema.fields.email.validate(
'wrong-email'
);
schema.fields.email
.validate(value)
.catch(console.error);
console.group('EMAIL VALIDATION');
console.log(value);
console.groupEnd();
console.table(error.inner);
console.trace('Validation stack');
const audit = [];
test(
'audit-check',
'Ошибка',
function(value) {
audit.push({
field: this.path,
value,
time: Date.now()
});
return true;
}
)
const watched = watch();
useEffect(() => {
console.log(watched);
}, [watched]);
Избыточное логирование усложняет диагностику.
Оптимальная стратегия:
Качественная система логирования Yup должна: