React Hook Form useWatch и Yup: как избежать ошибок с undefined
При использовании react-hook-form совместно с yup для валидации форм разработчики часто сталкиваются с несоответствием типов. Схема Yup помечает поле как обязательное (required), и TypeScript через InferType считает его всегда определённым. Однако на практике, пока пользователь не заполнит форму, значение может быть undefined. Это приводит к runtime-ошибкам при обращении к свойствам объекта через useWatch. В статье разберём, как правильно типизировать и безопасно работать с полями без задания defaultValues.
Почему возникает ошибка с useWatch и Yup
Когда вы используете InferType из Yup, TypeScript выводит типы строго по схеме. Если поле объявлено как .required(), то в типе оно помечается как обязательное. Однако useWatch возвращает актуальное значение из состояния формы. Если поле не было заполнено и не имеет значения по умолчанию, оно равно undefined. Таким образом, TypeScript не предупреждает о возможном отсутствии данных, и вы получаете ошибку во время исполнения.
Как безопасно типизировать useWatch
Лучший способ - явно указать тип для возвращаемого значения useWatch, добавив undefined как возможный вариант. Это не требует задания defaultValues и сохраняет строгую валидацию Yup.
import { useWatch } from 'react-hook-form';
const testField = useWatch<FormValues, 'testField' | undefined>({
control,
name: 'testField'
});
// Теперь testField имеет тип { id: string } | undefined
const id = testField?.id; // безопасноТакой подход явно указывает, что значение может отсутствовать, и вы обязаны проверить его перед использованием. Это решает проблему runtime-ошибок.
Использование условного рендеринга
Другой распространённый способ - проверять наличие значения перед обращением к свойствам. Это особенно удобно при рендеринге UI.
if (testField) {
const { id } = testField;
// используем id
} else {
// показываем заглушку или спиннер
}Такой код полностью типобезопасен и не требует изменения типов.
Почему не стоит полагаться только на defaultValues
Хотя задание значений по умолчанию в useForm решает проблему undefined, это не всегда удобно. Например, когда данные для полей (список опций дропдауна) подгружаются с сервера асинхронно. Установка defaultValues в таком случае может привести к избыточным ререндерам или конфликтам с серверными данными. Предложенный выше метод с явным указанием типа более гибкий и не требует дополнительных данных.
Альтернатива: кастомный хук-обёртка
Для больших проектов можно создать собственный хук, который автоматически добавляет undefined к типу поля.
function useSafeWatch<TFieldName extends FieldPath<FormValues>>(name: TFieldName) {
return useWatch<FormValues, TFieldName | undefined>({ control, name });
}Этот хук можно переиспользовать во всей форме, обеспечивая единый подход к типизации.
Заключение
Чтобы избежать ошибок с undefined при использовании useWatch и Yup, достаточно явно указать тип с возможным undefined или использовать условную проверку. Это сохраняет строгую валидацию Yup, не требует defaultValues и делает код безопасным. Для сложных проектов рекомендуется создать кастомный хук-обёртку.