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 и делает код безопасным. Для сложных проектов рекомендуется создать кастомный хук-обёртку.

    Часто задаваемые вопросы