Если ты только начинаешь работать с формами в React и чувствуешь, что каждый новый инпут - это как собирать паззл без картинки на коробке - ты не один. Верстка форм кажется простой: поле ввода, кнопка, стили. Но как только добавляешь валидацию, ошибки, асинхронные проверки и кастомные сообщения - всё рушится. И тут на помощь приходят React Hook Form и Yup. Вместе они делают формы не просто рабочими, а надёжными, чистыми и легко поддерживаемыми. Давай разберём, как это работает на практике, без лишней теории и сложных терминов.
Почему не просто useState и onChange?
Многие джуниоры начинают с простого: создают состояние для каждого поля через useState, ловят изменения через onChange, и вручную проверяют, например, что email содержит "@". Это работает - пока форм не становится больше трёх-четырёх. С каждым новым полем код растёт, становится запутанным, и ты начинаешь путать, где какое значение хранится. А если нужно проверить, что пароль совпадает с подтверждением? Или что поле не пустое только если выбран определённый чекбокс? Ты начинаешь писать костыли. И это нормально - все проходят через это. Но есть способ проще.
React Hook Form берёт на себя управление состоянием полей. Ты не пишешь onChange для каждого инпута. Ты просто даёшь ему register - и всё. Он сам отслеживает, что вводят, когда поле фокусируется, когда теряет фокус. Ты сосредотачиваешься на логике, а не на синтаксисе.
React Hook Form - как он работает
Представь, что у тебя есть форма для регистрации пользователя. Три поля: имя, email, пароль. С React Hook Form ты пишешь примерно так:
<form {...formMethods.handleSubmit(onSubmit)}>
<input {...register('name')} />
<input type="email" {...register('email')} />
<input type="password" {...register('password')} />
<button type="submit">Зарегистрироваться</button>
</form>
Всё. Никаких onChange, никаких value. React Hook Form сам связывает инпуты с данными. Когда пользователь что-то вводит - ты не пишешь код, чтобы обновить состояние. Форма делает это за тебя. А когда ты вызываешь handleSubmit, тебе приходит уже валидированный объект со всеми значениями. Это как если бы ты сказал: «Вот поля. Проверь их. Дай мне результат, когда всё ок».
Если нужно проверить, что поле обязательно - просто добавь required: true в register:
{...register('email', { required: 'Поле обязательно для заполнения' })}
Ошибка появится автоматически. Никаких ручных условий.
Yup - валидация как в законе
React Hook Form умеет валидировать, но только базово: «обязательно», «это email», «минимум 5 символов». А если нужно проверить, что пароль содержит хотя бы одну цифру и одну букву? Или что дата рождения не позже сегодняшнего дня? Тут на помощь приходит Yup.
Yup - это библиотека для описания схем валидации. Ты пишешь правила, как будто описываешь закон: «Если это email - он должен быть в формате [email protected]. Если это пароль - он должен быть не короче 8 символов и содержать цифру». Потом ты передаёшь эту схему в React Hook Form - и он сам применяет её.
Пример схемы для регистрации:
import * as yup from 'yup';
const schema = yup.object({
name: yup.string().required('Имя обязательно'),
email: yup.string().email('Неверный email').required('Поле обязательно'),
password: yup.string()
.min(8, 'Пароль должен быть не короче 8 символов')
.matches(/[0-9]/, 'Должен содержать цифру')
.matches(/[a-zA-Z]/, 'Должен содержать букву')
.required('Поле обязательно'),
});
Здесь всё просто: ты описываешь, каким должен быть каждый элемент. Yup сам знает, что значит email(), min(), matches(). Никаких регулярных выражений вручную - только читаемый код.
А потом ты связываешь схему с формой:
const formMethods = useForm({
resolver: yupResolver(schema),
});
И всё. Теперь при отправке формы Yup проверит каждое поле по твоим правилам. Если что-то не так - ошибки появятся рядом с полями. Если всё ок - ты получишь готовый объект с данными.
Как выглядит форма в действии
Допустим, пользователь ввёл email test@ - без домена. Yup сразу говорит: «Неверный email». React Hook Form показывает это сообщение рядом с полем. Пользователь исправляет - сообщение исчезает. Ты не пишешь ни одной строки кода для скрытия ошибки. Это всё работает автоматически.
А если нужно показать ошибку только после того, как пользователь покинул поле (а не при каждом нажатии клавиши)? Просто добавь shouldValidateOnBlur: true в настройки формы. Или если хочешь, чтобы проверка шла только при отправке - тогда оставь как есть. Никаких костылей. Только настройки.
Кастомные ошибки и асинхронная валидация
А что, если нужно проверить, не занят ли email? Это уже асинхронно - нужно сделать запрос на сервер. Yup это тоже умеет. Добавь test в схему:
email: yup.string()
.email()
.required()
.test('isUnique', 'Этот email уже занят', async (value) => {
if (!value) return true;
const response = await fetch('/api/check-email', {
method: 'POST',
body: JSON.stringify({ email: value }),
});
return response.ok;
}),
Ты пишешь асинхронную функцию - Yup ждёт ответа. Если сервер говорит «занят» - появляется ошибка. Если свободен - всё ок. И всё это в той же схеме, что и остальные правила. Никаких отдельных состояний, никаких хуков для загрузки. Просто добавил тест - и всё работает.
Стили и UX - как сделать красиво
React Hook Form даёт тебе объект formState, в котором есть errors, isSubmitting, isValid. Их можно использовать для стилизации.
Например, если поле с ошибкой - подсвечиваем его красным:
<input
{...register('email')}
className={errors.email ? 'input-error' : 'input'}
/>
А кнопку отправки можно отключить, пока форма не валидна:
<button type="submit" disabled={!formMethods.formState.isValid}>
Зарегистрироваться
</button>
Это не просто «красиво» - это улучшает UX. Пользователь не нажимает кнопку, пока не заполнит всё правильно. Никаких «ошибка при отправке» - только предупреждения прямо в форме.
Что не стоит делать
- Не смешивай React Hook Form с
useStateдля тех же полей - это приведёт к конфликтам. - Не пиши валидацию вручную, если Yup может сделать это за тебя - ты будешь поддерживать код вечно.
- Не используй
useEffectдля обработки ошибок - они уже есть вformState.errors. - Не отключай валидацию «на время разработки» - это потом будет сложно включить обратно.
Сравнение: Yup + React Hook Form vs ручная валидация
| Критерий | React Hook Form + Yup | Ручная валидация |
|---|---|---|
| Скорость разработки | Очень высокая | Низкая |
| Поддержка кода | Простая | Сложная |
| Асинхронные проверки | Встроены | Требуют костылей |
| Ошибки в UI | Автоматически | Вручную |
| Тестирование | Простое | Сложное |
Что дальше?
Когда ты освоишь эту пару - попробуй добавить маски для телефонов (через react-input-mask), кастомные компоненты (например, выпадающий список), или валидацию дат (через date-fns или dayjs). Всё это интегрируется в ту же схему. Ты не переписываешь форму - просто расширяешь правила.
Верстка форм - это не про <input> и <label>. Это про то, как ты управляешь данными. React Hook Form и Yup - это инструменты, которые дают тебе контроль, а не усложняют жизнь. И когда ты начинаешь использовать их - ты перестаёшь думать о том, как «сделать», и начинаешь думать о том, «что делать».
Можно ли использовать Yup без React Hook Form?
Да, можно. Yup - это просто библиотека для валидации. Но без React Hook Form тебе придётся самому отслеживать значения полей, обновлять состояние, показывать ошибки. Это возможно, но теряется главное преимущество - автоматизация. Если ты не используешь React Hook Form, то Yup становится просто сложной альтернативой регулярным выражениям. Лучше использовать их вместе.
Почему не использовать Formik?
Formik - это тоже популярная библиотека, но он требует больше кода и работает иначе. React Hook Form легче, быстрее и не перерисовывает компоненты при каждом вводе. Formik часто приводит к лишним ререндерам, особенно на больших формах. React Hook Form работает с DOM напрямую - это эффективнее. Если ты начинаешь сейчас - выбирай React Hook Form. Formik устаревает.
Как проверить, что два поля совпадают (например, пароль и подтверждение)?
В Yup это делается через oneOf или test. Пример: yup.string().oneOf([yup.ref('password')], 'Пароли не совпадают'). Второе поле будет проверяться на соответствие первому. Если пользователь меняет пароль - ошибка исчезает автоматически. Никаких костылей.
Как обновить значение поля программно?
Используй setValue('fieldName', newValue) из объекта formMethods. Например, если ты загружаешь данные с сервера - ты не пишешь setState, а вызываешь setValue. Это обновит поле, сбросит ошибки и обновит валидацию. Это безопаснее, чем пытаться изменить DOM вручную.
Нужно ли учить TypeScript для работы с Yup и React Hook Form?
Не обязательно, но очень помогает. TypeScript позволяет точно описывать типы данных формы, и Yup с ним работает идеально. Ты получаешь автодополнение, проверку типов и меньше ошибок. Если ты используешь TypeScript - добавь тип для схемы: yup.object<FormValues>({...}). Это сделает код намного надёжнее.
Если ты только начинаешь - не бойся пробовать. Создай простую форму: имя, email, пароль. Подключи React Hook Form и Yup. Попробуй добавить одну кастомную проверку. Потом вторую. Потом асинхронную. Через пару дней ты перестанешь думать о том, как «сделать валидацию» - и начнёшь думать о том, как сделать форму удобнее для пользователя. Это и есть настоящая верстка.