Перейти к содержимому

20. Form: введение

Существующие библиотеки форм (React Hook Form, Formik) имеют ограничения: слабая типизация, сложная интеграция с TypeScript, отсутствие поддержки других фреймворков.

TanStack Form создан с нуля с акцентом на:

  • TypeScript-first: полная типизация полей, значений и ошибок
  • Фреймворк-независимость: React, Vue, Solid, Angular
  • Headless: никаких встроенных UI-компонентов
  • Производительность: точечные обновления, нет лишних перерендеров
Окно терминала
npm install @tanstack/react-form
import { useForm } from '@tanstack/react-form'
const form = useForm({
defaultValues: {
name: '',
email: '',
age: 0,
},
onSubmit: async ({ value }) => {
await saveUser(value)
// value полностью типизирован: { name: string; email: string; age: number }
},
})

Поля создаются через form.Field:

<form.Field
name="email"
validators={{
onChange: ({ value }) =>
!value ? 'Обязательное поле' :
!value.includes('@') ? 'Невалидный email' :
undefined,
onBlur: ({ value }) =>
value.length < 5 ? 'Слишком короткий email' : undefined,
}}
>
{(field) => (
<div>
<label>Email</label>
<input
name={field.name}
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
/>
{field.state.meta.errors.map(error => (
<span key={error}>{error}</span>
))}
</div>
)}
</form.Field>

field.state содержит всю информацию о поле:

field.state = {
value: string, // Текущее значение
meta: {
errors: string[], // Массив ошибок
errorMap: {...}, // Ошибки по источнику (onChange, onBlur...)
isDirty: boolean, // Значение изменилось
isTouched: boolean, // Пользователь взаимодействовал
isValidating: boolean, // Идёт асинхронная валидация
},
}

Глобальное состояние формы:

form.state = {
values: FormValues, // Текущие значения всех полей
errors: FormErrors, // Все ошибки
isSubmitting: boolean, // Форма отправляется
isSubmitted: boolean, // Форма была отправлена
isDirty: boolean, // Есть несохранённые изменения
isValid: boolean, // Нет ошибок
canSubmit: boolean, // Можно отправить
}
<form
onSubmit={(e) => {
e.preventDefault()
form.handleSubmit()
}}
>
{/* поля */}
<form.Subscribe
selector={(state) => [state.canSubmit, state.isSubmitting]}
>
{([canSubmit, isSubmitting]) => (
<button type="submit" disabled={!canSubmit || isSubmitting}>
{isSubmitting ? 'Сохранение...' : 'Сохранить'}
</button>
)}
</form.Subscribe>
</form>