lenec ru

← все посты

React Hook Form vs TanStack Form

11K

В 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 ближе. Часто выбор — вопрос вкуса, не объективных метрик. Обе библиотеки хороши, и для большинства проектов выигрыш от «правильного» выбора меньше, чем от того, насколько ты глубоко её освоишь.

Комментарии 0

  • Будьте первым, кто оставит комментарий.

Войдите, чтобы оставить комментарий.