React Hook Form vs TanStack Form
В 2026 году у нас два серьёзных кандидата на главную форму-библиотеку для React: React Hook Form (RHF) и TanStack Form. Обе хорошо типизированы, обе работают с любым валидатором (Zod, Valibot, Yup), у обеих живая команда. Я работала с обеими — RHF на двух проектах год, TanStack Form в новом продукте полгода. Сравню по реальным сценариям.
Зачем вообще выбирать форму-библиотеку
Можно ведь написать useState и не страдать. Когда форма из 2-3 полей — да. Когда из 15 полей с условной валидацией, динамическими секциями и многошаговыми wizard'ами — без библиотеки сходишь с ума. Главные задачи, которые решает форма-библиотека:
- Управление состоянием полей без ререндера всей формы.
- Валидация (синхронная и асинхронная).
- Обработка ошибок и focus management.
- Submission state (pending, success, error).
- Field arrays и динамические поля.
React Hook Form: краткое описание
RHF — самая популярная форма-библиотека для React. Минимизирует ререндеры через uncontrolled inputs (поля живут в DOM, библиотека читает их через ref'ы). Малый размер бандла (8 КБ gzip), отличная производительность.
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
const schema = z.object({
email: z.string().email(),
password: z.string().min(8),
});
type FormData = z.infer<typeof schema>;
export function LoginForm() {
const { register, handleSubmit, formState: { errors } } = useForm<FormData>({
resolver: zodResolver(schema),
});
const onSubmit = (data: FormData) => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register('email')} />
{errors.email && <span>{errors.email.message}</span>}
<input type="password" {...register('password')} />
{errors.password && <span>{errors.password.message}</span>}
<button type="submit">Войти</button>
</form>
);
}API очень простой. register возвращает props, которые ты разбрасываешь по input'ам. Валидация запускается на blur или submit (настраивается).
TanStack Form: краткое описание
TanStack Form — относительно молодая библиотека от автора TanStack Query. Использует controlled inputs и собственный механизм фокусной перерисовки только тех полей, которые изменились.
import { useForm } from '@tanstack/react-form';
import { z } from 'zod';
export function LoginForm() {
const form = useForm({
defaultValues: { email: '', password: '' },
validators: {
onSubmit: z.object({
email: z.string().email(),
password: z.string().min(8),
}),
},
onSubmit: async ({ value }) => {
console.log(value);
},
});
return (
<form onSubmit={(e) => { e.preventDefault(); form.handleSubmit(); }}>
<form.Field name="email">
{(field) => (
<>
<input value={field.state.value} onChange={(e) => field.handleChange(e.target.value)} />
{field.state.meta.errors[0] && <span>{field.state.meta.errors[0]}</span>}
</>
)}
</form.Field>
<form.Field name="password">
{(field) => (
<input type="password" value={field.state.value} onChange={(e) => field.handleChange(e.target.value)} />
)}
</form.Field>
<button type="submit">Войти</button>
</form>
);
}API многословнее, но более явный: каждое поле — render-prop, который получает field-объект.
Производительность
RHF выигрывает на простых формах. Uncontrolled inputs не вызывают ререндер компонента при каждом вводе. Это ощутимо на формах с 30+ полями: ввод буквы — 0 ререндеров, только когда триггерится валидация.
TanStack Form тоже эффективен, но через другую механику: subscribers подписываются на конкретные слайсы стейта формы. Это даёт сравнимую производительность, но требует чуть больше работы со стороны разработчика — нужно правильно использовать form.useStore и селекторы.
На большой форме с 60 полями я не заметила разницы в реальном использовании. Обе работают плавно.
Типизация
TanStack Form имеет более строгую типизацию из коробки. Если ты опечатаешься в имени поля — TS подскажет:
// TanStack Form
<form.Field name="email"> ✓
<form.Field name="emial"> // TS error: 'emial' is not assignableВ RHF имена полей — обычные строки, и опечатки ловятся только в рантайме. Для большой типизированной формы я предпочитаю TanStack Form.
Field arrays
В RHF удобный useFieldArray:
const { fields, append, remove } = useFieldArray({ control, name: 'tags' });
return fields.map((f, i) => (
<div key={f.id}>
<input {...register(`tags.${i}.value`)} />
<button onClick={() => remove(i)}>-</button>
</div>
));В TanStack Form через field.pushValue, field.removeValue:
<form.Field name="tags">
{(field) => (
<>
{field.state.value.map((_, i) => (
<form.Field key={i} name={`tags[${i}]`}>
{(sub) => (
<input value={sub.state.value} onChange={(e) => sub.handleChange(e.target.value)} />
)}
</form.Field>
))}
<button onClick={() => field.pushValue('')}>+</button>
</>
)}
</form.Field>RHF проще для типичных кейсов. TanStack — гибче, но многословнее.
Серверные actions и React 19
В Next 15 с server actions:
RHF: оборачиваешь action в handleSubmit, передаёшь FormData или объект.
TanStack Form имеет встроенную интеграцию с React 19 server actions через asyncDebounceMs и асинхронные валидаторы. Это новее и местами лучше работает с React Suspense.
Для проектов на Next 15 я склоняюсь к TanStack Form — у него ощутимо чище интеграция со server actions.
Адаптация валидаторов
Оба умеют Zod, Valibot, Yup, ArkType. RHF использует resolver-обёртки, TanStack — встроенный механизм с разными точками валидации (onChange, onBlur, onSubmit, onMount).
Точки валидации в TanStack дают более гибкое поведение: можно валидировать поле на blur, а форму целиком — на submit. В RHF это тоже есть, но менее очевидно настраивается.
Размер бандла
- RHF: ~8 КБ gzip.
- TanStack Form: ~12-14 КБ gzip.
Разница маленькая, но если для тебя каждый килобайт важен (легкий лендинг), RHF — выбор.
Документация и сообщество
RHF — миллионы установок, тонна примеров на StackOverflow, видеокурсы. Любой типичный кейс уже описан.
TanStack Form — растущее, но меньшее коммьюнити. Документация хорошая, но примеров на edge-cases меньше.
Когда я беру что
- Простая форма (логин, регистрация, контакт) — RHF. Меньше кода, меньше зависимостей.
- Сложная форма с динамической логикой и серверной валидацией — TanStack Form. Лучше типизация, лучше интеграция с server actions.
- Команда уже знает RHF — оставаться на RHF.
- Новый проект на Next 15 — пробовать TanStack Form.
- Очень большая форма с 50+ полями — RHF за счёт uncontrolled inputs.
Грабли в RHF
uncontrolled inputs не подходят для всех сценариев. Если нужно «реактивно» подстраивать UI под значение поля (показать/скрыть блок в зависимости от значения) — нужен watch, а он триггерит ререндеры. На сложных формах это съедает ту самую производительность, ради которой RHF и брался.
Defaults для controlled-сценариев. Если используешь Controller для интеграции с UI-китами (radix, mantine), приходится возвращать controlled-режим, и преимущество RHF теряется.
Грабли в TanStack Form
API многословный. На простой форме код выглядит избыточно. Для кодовой базы, где много простых форм, это ощутимо.
Render-prop. Чтобы передать поле в компонент, нужно через render-prop вытаскивать field. Это не страшно, но первое время режет глаз.
Что копать дальше
Если ты только начинаешь — поставь обе на одной задаче, реализуй простой логин. Через час у тебя будет ощущение, какой API ближе. Часто выбор — вопрос вкуса, не объективных метрик. Обе библиотеки хороши, и для большинства проектов выигрыш от «правильного» выбора меньше, чем от того, насколько ты глубоко её освоишь.