Одна из самых распространённых проблем возникает при несовпадении структуры объекта формы и схемы Yup.
Некорректный пример:
const schema = yup.object({
user: yup.object({
email: yup.string().email().required()
})
})
Форма:
useForm({
resolver: yupResolver(schema),
defaultValues: {
email: ''
}
})
Поле email находится в корне формы, тогда как схема
ожидает его внутри user.
Правильный вариант:
useForm({
resolver: yupResolver(schema),
defaultValues: {
user: {
email: ''
}
}
})
Либо схема должна быть изменена:
const schema = yup.object({
email: yup.string().email().required()
})
Cannot read property of undefinedПроблема часто появляется при вложенных объектах.
Пример:
const schema = yup.object({
profile: yup.object({
age: yup.number().required()
})
})
Если profile отсутствует:
defaultValues: {}
Yup может выбрасывать ошибки при обращении к вложенным полям.
Решение:
defaultValues: {
profile: {
age: ''
}
}
Дополнительно рекомендуется использовать .default():
const schema = yup.object({
profile: yup.object({
age: yup.number().required()
}).default({})
})
required()
не работает для пустых строкМногие ожидают, что поле автоматически отфильтрует строки из пробелов.
Пример:
yup.string().required()
Строка " " считается валидной.
Решение:
yup.string()
.trim()
.required('Поле обязательно')
Теперь пробелы будут удаляться перед проверкой.
required() и
number()Частая проблема:
yup.number().required()
Пустое поле формы обычно приходит как строка:
''
Yup пытается преобразовать значение в число и получает
NaN.
Результат — сообщение о неправильном типе вместо сообщения об обязательности поля.
Решение:
yup.number()
.typeError('Введите число')
.required('Поле обязательно')
Дополнительно можно использовать transform():
yup.number()
.transform((value, originalValue) => {
return originalValue === '' ? undefined : value
})
.required()
HTML input всегда возвращает строку.
Даже если используется:
<input type="number" />
значение всё равно приходит как строка.
Проблемный пример:
const schema = yup.object({
age: yup.number()
})
Решение №1:
register('age', {
valueAsNumber: true
})
Решение №2:
yup.number().transform(value => {
return isNaN(value) ? undefined : value
})
Checkbox может возвращать:
'on'
или:
undefined
Некорректная схема:
yup.boolean().required()
Для checkbox лучше использовать:
yup.boolean().oneOf([true], 'Необходимо согласие')
Асинхронная проверка:
const schema = yup.object({
email: yup.string().test(
'unique-email',
'Email уже существует',
async (value) => {
const response = await checkEmail(value)
return response.ok
}
)
})
Если валидация запускается на каждый ввод символа:
mode: 'onChange'
возникает большое количество запросов.
Решения:
const debouncedCheck = debounce(checkEmail, 500)
useForm({
mode: 'onBlur'
})
const cache = new Map()
nullable()
не делает поле необязательнымНеверное ожидание:
yup.string().nullable().required()
nullable() разрешает только null, но поле
всё ещё обязательно.
Корректное использование:
yup.string().nullable().notRequired()
Пример:
yup.string().nullable()
Допускает:
null
Но не всегда корректно работает с:
undefined
Для полной гибкости:
yup.string().nullable().optional()
Причина — неправильный порядок вызова методов.
Проблемный код:
yup.string()
.required()
.transform(value => value.trim())
required() может выполниться раньше transform.
Правильный вариант:
yup.string()
.transform(value => value?.trim())
.required()
Опасный пример:
yup.number().transform(() => 'text')
После transform схема перестаёт соответствовать типу number.
Рекомендуется сохранять тип данных:
yup.number().transform(value => {
return Number(value)
})
Проблемный код:
yup.array(
yup.string().required()
)
Корректный вариант:
yup.array().of(
yup.string().required()
)
Пример:
yup.array().required()
Пустой массив [] считается валидным.
Решение:
yup.array()
.min(1, 'Минимум один элемент')
При удалении элементов индексы могут смещаться.
Пример:
users[2]
после удаления элемента:
users[1]
React Hook Form может сохранять старые ошибки.
Решение:
clearErrors('users')
или:
reset(getValues())
Частая ошибка — отсутствие поля-зависимости.
Проблемный пример:
yup.object({
password: yup.string(),
confirmPassword: yup.string().when('password', {
is: value => value.length > 0,
then: schema => schema.required()
})
})
Если password равен undefined, возникает
ошибка.
Безопасный вариант:
is: value => !!value
Ошибка:
fieldA -> fieldB
fieldB -> fieldA
Пример:
a: yup.string().when('b')
b: yup.string().when('a')
Yup может вызвать бесконечную рекурсию.
Решение — вынести логику в test():
yup.object({
a: yup.string(),
b: yup.string()
}).test(
'cross-validation',
'Одно из полей обязательно',
values => {
return !!values.a || !!values.b
}
)
Крупные формы с десятками полей могут вызывать задержки.
Причины:
when() условия;test().Плохой пример:
const schema = yup.object({
...
})
внутри компонента React.
Решение:
const schema = useMemo(() => {
return yup.object({
...
})
}, [])
Если используется:
mode: 'onChange'
валидация запускается при каждом символе.
Оптимизация:
useForm({
mode: 'onBlur',
reValidateMode: 'onSubmit'
})
Пример:
type FormData = {
age: number
}
Схема:
yup.object({
age: yup.string()
})
TypeScript не сможет корректно сопоставить типы.
Решение:
const schema = yup.object({
age: yup.number().required()
})
type FormData = yup.InferType<typeof schema>
Проблемный пример:
yup.string().nullable()
TypeScript иногда выводит:
string
Решение:
type FormData = yup.Asserts<typeof schema>
или явное указание типа:
const schema: yup.ObjectSchema<{
name: string | null
}>
Причина — неправильное имя поля.
Форма:
register('user.email')
Ошибка проверяется так:
errors.email
Правильно:
errors.user?.email
Частая ошибка:
useForm({
schema
})
Корректно:
useForm({
resolver: yupResolver(schema)
})
После reset ошибки могут оставаться.
Решение:
reset({}, {
keepErrors: false
})
или:
clearErrors()
Некорректный пример:
test(value => {
if (value === 'admin') {
return
}
})
Всегда необходимо возвращать значение:
test(value => {
return value !== 'admin'
})
Нельзя использовать стрелочную функцию, если нужен
this.
Проблемный код:
test(
'custom',
'Ошибка',
(value) => {
return this.parent
}
)
Правильно:
test(
'custom',
'Ошибка',
function(value) {
return this.parent
}
)
Плохой пример:
yup.string().required('Required')
во всех схемах.
Решение — централизованная локализация:
yup.setLocale({
mixed: {
required: 'Поле обязательно'
}
})
Причина — setLocale() вызывается после создания
схем.
Неправильно:
const schema = yup.object({...})
yup.setLocale({...})
Правильно:
yup.setLocale({...})
const schema = yup.object({...})
После отправки формы:
JSON.stringify(data)
объект Date становится строкой.
Решение:
const parsed = {
...data,
date: new Date(data.date)
}
JSON удаляет:
undefined
Если сервер ожидает поле:
null
необходимо преобразование:
Object.entries(data).reduce((acc, [key, value]) => {
acc[key] = value === undefined ? null : value
return acc
}, {})
Полезный приём:
const onEr ror = (errors) => {
console.log(errors)
}
schema.validate(data)
или:
schema.validateSync(data)
По умолчанию Yup может остановиться на первой ошибке.
Для полного списка:
schema.validate(data, {
abortEarly: false
})
const resolver = yupResolver(schema)
const result = await resolver(data)
console.log(result)
Позволяет увидеть: