Типичные ошибки при начале работы

Несоответствие формы и схемы Yup является одной из самых частых причин некорректной работы YupResolver в связке с React Hook Form. Схема описывает структуру данных строго, тогда как форма часто содержит упрощённые или частично заполненные значения. При несовпадении ключей или типов Yup начинает возвращать ошибки, которые на первый взгляд кажутся «рандомными».

Основная проблема возникает, когда поля формы и поля схемы имеют разные уровни вложенности или разные имена.

const schema = yup.object({
  user: yup.object({
    email: yup.string().email().required()
  })
});

const { register } = useForm({
  resolver: yupResolver(schema)
});

// Ошибка: форма использует "email", а схема ожидает "user.email"
<input {...register("email")} />

В подобных случаях YupResolver корректно валидирует структуру схемы, но форма передаёт данные в другой форме, из-за чего ошибки либо не отображаются, либо отображаются не там, где ожидается.


Неверная интеграция YupResolver с React Hook Form

Ошибки часто возникают при неправильной передаче resolver или импорте несовместимых версий библиотек.

import { yupResolver } from "@hookform/resolvers/yup";
import * as yup from "yup";

Проблемы появляются при:

  • использовании старых версий @hookform/resolvers
  • смешивании yup v1 и v0.32
  • установке нескольких экземпляров yup в node_modules

В результате валидатор может не запускаться или возвращать пустой errors объект.


Проблемы с TypeScript-инференсом

При использовании YupResolver в TypeScript часто возникает несоответствие типов между схемой и формой. Это приводит к тому, что ошибки типизации скрывают реальные проблемы в рантайме.

const schema = yup.object({
  age: yup.number().required()
});

type FormData = yup.InferType<typeof schema>;

Типичная ошибка — ручное объявление интерфейса, который расходится со схемой:

type FormData = {
  age: string; // ошибка: должно быть number
};

В таком случае YupResolver корректно валидирует число, но форма ожидает строку, что приводит к постоянным ошибкам преобразования.


Пересоздание схемы на каждом рендере

Одной из скрытых проблем является создание Yup-схемы внутри компонента. Это приводит к тому, что resolver получает новую ссылку на схему при каждом рендере.

function Form() {
  const schema = yup.object({
    name: yup.string().required()
  });

  const form = useForm({
    resolver: yupResolver(schema)
  });
}

Последствия:

  • деградация производительности
  • сброс внутреннего кеша resolver
  • нестабильная валидация при вводе

Правильный подход — вынос схемы за пределы компонента.


Ошибки работы с defaultValues

YupResolver не исправляет несоответствие между defaultValues и схемой. Если начальные значения отсутствуют или имеют неправильный тип, Yup начинает интерпретировать их как undefined.

const form = useForm({
  defaultValues: {
    age: ""
  },
  resolver: yupResolver(schema)
});

При схеме:

age: yup.number().required()

возникает конфликт: пустая строка не приводится автоматически к числу без дополнительной настройки.


Проблемы с преобразованием типов (cast / transform)

Yup по умолчанию пытается приводить типы, что может неожиданно влиять на результат валидации.

age: yup.number().transform((value, originalValue) => {
  return originalValue === "" ? undefined : value;
})

Без подобных трансформаций часто возникают ошибки:

  • пустая строка вместо undefined
  • NaN после преобразования
  • некорректная работа .required()

abortEarly и множественные ошибки

По умолчанию Yup может возвращать только первую ошибку валидации, что создаёт иллюзию «неполной проверки».

yup.object({
  email: yup.string().email().required()
}).validate(data, { abortEarly: false });

При использовании YupResolver важно учитывать, что агрегирование ошибок влияет на структуру formState.errors, особенно при вложенных объектах.


Вложенные объекты и массивы

Работа с массивами и объектами является источником сложных ошибок.

const schema = yup.object({
  users: yup.array().of(
    yup.object({
      email: yup.string().required()
    })
  )
});

Типичные проблемы:

  • отсутствие ключа key в динамических списках React
  • несовпадение индексов массива и структуры ошибок
  • неправильное использование fieldArray без синхронизации схемы

YupResolver возвращает ошибки с глубокой вложенностью, что требует корректного маппинга в UI.


Условная валидация и метод when

Метод when часто используется неправильно, особенно при попытке зависеть от внешних полей формы.

password: yup.string().when("hasPassword", {
  is: true,
  then: yup.string().required()
});

Ошибки возникают, когда:

  • зависимое поле отсутствует в форме
  • значение приходит как строка вместо boolean
  • не учитывается initial state

В таких случаях resolver может некорректно пересчитывать схему.


required, nullable и undefined

Семантика Yup часто приводит к путанице:

  • required() — запрещает undefined
  • nullable() — допускает null
  • отсутствие значения в форме часто трактуется как undefined
yup.string().nullable().required()

Такая комбинация может вести к неожиданному поведению: null допускается, но пустая строка — нет.


Частые проблемы с производительностью YupResolver

При больших формах с десятками полей YupResolver может стать узким местом.

Причины:

  • синхронная валидация всей схемы при каждом вводе
  • отсутствие мемоизации схемы
  • глубокая рекурсия в вложенных объектах

Особенно заметно при режиме mode: "onChange" в React Hook Form.


Несовместимость версий библиотек

Распространённая проблема — использование несовместимых версий:

  • react-hook-form v6/v7
  • @hookform/resolvers разных поколений
  • yup 0.32 vs 1.x

Симптомы:

  • resolver не вызывается
  • ошибки не отображаются
  • formState не обновляется

Ошибки работы с режимами валидации React Hook Form

YupResolver тесно связан с режимами:

useForm({
  mode: "onBlur",
  resolver: yupResolver(schema)
});

Неправильный выбор режима приводит к:

  • отсутствию мгновенной валидации
  • задержкам отображения ошибок
  • несоответствию UX ожиданиям

Особенно проблемным является смешивание onSubmit и onChange поведения.


Игнорирование stripUnknown и лишних полей

Yup может возвращать очищенные данные, но при отсутствии настройки stripUnknown валидация не удаляет лишние поля.

yup.object().noUnknown(true)

Без этого в форму могут попадать:

  • неожиданные ключи из API
  • временные поля UI
  • устаревшие значения из состояния

Это влияет на стабильность resolver и итоговые данные submit.