К основному содержанию
скилл CX Codex

tanstack-form — скилл для Claude Code: что делает и как установить

Скилл учит ассистента создавать формы на базе TanStack Form с типизацией TypeScript, валидацией через Zod или Valibot и динамическими массивами полей.

Установить в Claude Code · Через Skills CLI (Vercel) Все способы и платформы ↓
npx skills add tanstack-skills/tanstack-skills --skill tanstack-form -a claude-code
Открыть на GitHub

Что делает tanstack-form

Скилл передаёт агенту правила работы с библиотекой @tanstack/react-form версии v1. В инструкциях описана работа с хуком useForm, компонентами form.Field и точечной подпиской на состояние формы через form.Subscribe или form.useStore.

Библиотека поддерживает синхронную и асинхронную валидацию по событиям onChange, onBlur, onSubmit и onMount. В скилле есть примеры связывания зависимых полей через onChangeListenTo и управления массивами методами pushValue или removeValue.

Для валидации по схемам понадобятся отдельные пакеты вроде @tanstack/zod-form-adapter, так как в базовый пакет адаптеры не входят.

Какие задачи решает

  • настроить синхронную и асинхронную валидацию полей формы
  • связать зависимые поля через параметр onChangeListenTo
  • реализовать динамические списки полей с pushValue и removeValue
  • подключить валидацию схем Zod или Valibot к TanStack Form

Примеры запросов агенту

  • Сделай форму регистрации на @tanstack/react-form с валидацией email через Zod
  • Добавь динамический список пользователей с добавлением и удалением полей через TanStack Form
  • Настрой асинхронную проверку имени пользователя с задержкой asyncDebounceMs

Как установить tanstack-form

1Через Skills CLI (Vercel)

npx skills add tanstack-skills/tanstack-skills --skill tanstack-form -a claude-code

2Вручную: положить файлы

~/.claude/skills/tanstack-form/SKILL.md

Папка скилла целиком; для одного проекта — .claude/skills/tanstack-form/

По шагам, с командами для каждой платформы: Как установить скилл в Claude Code: пять способов

Вопросы о tanstack-form

Как установить tanstack-form в Claude Code?

Выполните команду npx skills add tanstack-skills/tanstack-skills --skill tanstack-form -a claude-code.

Что делает скилл tanstack-form?

Он даёт модели контекст по API TanStack Form v1: вызов useForm, рендеринг полей, реактивные подписки и настройка валидаторов.

Какие библиотеки схем поддерживает этот скилл?

В материалах описано подключение адаптеров для Zod (@tanstack/zod-form-adapter), Valibot (@tanstack/valibot-form-adapter) и Yup.

Исходный текст

SKILL.md · MIT
---
name: tanstack-form
description: Headless, performant, and type-safe form state management for TS/JS, React, Vue, Angular, Solid, Lit, and Svelte.
---


## Overview

TanStack Form is a headless form library with deep TypeScript integration. It provides field-level and form-level validation (sync/async), array fields, linked/dependent fields, fine-grained reactivity, and schema validation adapter support (Zod, Valibot, Yup).

**Package:** `@tanstack/react-form`
**Adapters:** `@tanstack/zod-form-adapter`, `@tanstack/valibot-form-adapter`
**Status:** Stable (v1)

## Installation

```bash
npm install @tanstack/react-form
# Optional schema adapters:
npm install @tanstack/zod-form-adapter zod
npm install @tanstack/valibot-form-adapter valibot
```

## Core: useForm

```tsx
import { useForm } from '@tanstack/react-form'

function MyForm() {
  const form = useForm({
    defaultValues: {
      firstName: '',
      lastName: '',
      email: '',
      age: 0,
    },
    onSubmit: async ({ value }) => {
      // value is fully typed
      await submitToServer(value)
    },
    onSubmitInvalid: ({ value, formApi }) => {
      console.log('Validation failed:', formApi.state.errors)
    },
  })

  return (
    <form
      onSubmit={(e) => {
        e.preventDefault()
        e.stopPropagation()
        form.handleSubmit()
      }}
    >
      {/* Fields */}
      <form.Subscribe
        selector={(state) => ({ canSubmit: state.canSubmit, isSubmitting: state.isSubmitting })}
        children={({ canSubmit, isSubmitting }) => (
          <button type="submit" disabled={!canSubmit}>
            {isSubmitting ? 'Submitting...' : 'Submit'}
          </button>
        )}
      />
    </form>
  )
}
```

## Fields (form.Field)

```tsx
<form.Field
  name="firstName"
  validators={{
    onChange: ({ value }) =>
      value.length < 3 ? 'Must be at least 3 characters' : undefined,
  }}
  children={(field) => (
    <div>
      <label htmlFor={field.name}>First Name</label>
      <input
        id={field.name}
        name={field.name}
        value={field.state.value}
        onBlur={field.handleBlur}
        onChange={(e) => field.handleChange(e.target.value)}
      />
      {field.state.meta.isTouched && field.state.meta.errors.length > 0 && (
        <em>{field.state.meta.errors.join(', ')}</em>
      )}
    </div>
  )}
/>

<!-- Nested fields use dot notation -->
<form.Field name="address.city">
  {(field) => (
    <input
      value={field.state.value}
      onChange={(e) => field.handleChange(e.target.value)}
      onBlur={field.handleBlur}
    />
  )}
</form.Field>
```

## Validation

### Validation Timing

| Cause | When |
|-------|------|
| `onChange` | After every value change |
| `onBlur` | When field loses focus |
| `onSubmit` | During submission |
| `onMount` | When field mounts |

### Synchronous Validation

```tsx
<form.Field
  name="age"
  validators={{
    onChange: ({ value }) => {
      if (value < 18) return 'Must be 18 or older'
      return undefined // undefined = valid
    },
    onBlur: ({ value }) => {
      if (!value) return 'Required'
      return undefined
    },
  }}
/>
```

### Asynchronous Validation

```tsx
<form.Field
  name="username"
  asyncDebounceMs={500}
  validators={{
    onChangeAsync: async ({ value }) => {
      const res = await fetch(`/api/check-username?q=${value}`)
      const { available } = await res.json()
      if (!available) return 'Username taken'
      return undefined
    },
  }}
>
  {(field) => (
    <>
      <input value={field.state.value} onChange={(e) => field.handleChange(e.target.value)} />
      {field.state.meta.isValidating && <span>Checking...</span>}
    </>
  )}
</form.Field>
```

### Schema Validation (Zod)

```tsx
import { zodValidator } from '@tanstack/zod-form-adapter'
import { z } from 'zod'

const form = useForm({
  defaultValues: { email: '', age: 0 },
  validatorAdapter: zodValidator(),
  onSubmit: async ({ value }) => { /* ... */ },
})

<form.Field
  name="email"
  validators={{
    onChange: z.string().email('Invalid email'),
    onBlur: z.string().min(1, 'Required'),
  }}
/>

<form.Field
  name="age"
  validators={{
    onChange: z.number().min(18, 'Must be 18+'),
  }}
/>
```

### Form-Level Validation

```tsx
const form = useForm({
  defaultValues: { password: '', confirmPassword: '' },
  validators: {
    onChange: ({ value }) => {
      if (value.password !== value.confirmPassword) {
        return 'Passwords do not match'
      }
      return undefined
    },
  },
})
```

### Linked/Dependent Fields

```tsx
<form.Field
  name="confirmPassword"
  validators={{
    onChangeListenTo: ['password'], // Re-validate when password changes
    onChange: ({ value, fieldApi }) => {
      const password = fieldApi.form.getFieldValue('password')
      if (value !== password) return 'Passwords do not match'
      return undefined
    },
  }}
/>
```

## Array Fields

```tsx
<form.Field name="people" mode="array">
  {(field) => (
    <div>
      {field.state.value.map((_, index) => (
        <div key={index}>
          <form.Field name={`people[${index}].name`}>
            {(subField) => (
              <input
                value={subField.state.value}
                onChange={(e) => subField.handleChange(e.target.value)}
              />
            )}
          </form.Field>
          <button type="button" onClick={() => field.removeValue(index)}>
            Remove
          </button>
        </div>
      ))}
      <button type="button" onClick={() => field.pushValue({ name: '', age: 0 })}>
        Add Person
      </button>
    </div>
  )}
</form.Field>
```

### Array Methods

```typescript
field.pushValue(item)              // Add to end
field.insertValue(index, item)     // Insert at index
field.replaceValue(index, item)    // Replace at index
field.removeValue(index)           // Remove at index
field.swapValues(indexA, indexB)    // Swap positions
field.moveValue(from, to)          // Move position
```

## Listeners (Side Effects)

```tsx
<form.Field
  name="country"
  listeners={{
    onChange: ({ value }) => {
      // Side effect: reset dependent fields
      form.setFieldValue('state', '')
      form.setFieldValue('postalCode', '')
    },
  }}
/>
```

## Reactivity (form.Subscribe & useStore)

```tsx
// Render-prop subscription (fine-grained)
<form.Subscribe
  selector={(state) => ({ canSubmit: state.canSubmit, isDirty: state.isDirty })}
  children={({ canSubmit, isDirty }) => (
    <div>
      {isDirty && <span>Unsaved changes</span>}
      <button disabled={!canSubmit}>Save</button>
    </div>
  )}
/>

// Hook-based subscription
function FormStatus() {
  const isValid = form.useStore((s) => s.isValid)
  return isValid ? null : <p>Fix errors</p>
}
```

## Form State

```typescript
interface FormState {
  values: TFormData
  errors: ValidationError[]
  errorMap: Record<string, ValidationError>
  isFormValid: boolean
  isFieldsValid: boolean
  isValid: boolean               // isFormValid && isFieldsValid
  isTouched: boolean
  isPristine: boolean
  isDirty: boolean
  isSubmitting: boolean
  isSubmitted: boolean
  isSubmitSuccessful: boolean
  submissionAttempts: number
  canSubmit: boolean             // isValid && !isSubmitting
}
```

## Field State

```typescript
interface FieldState<TData> {
  value: TData
  meta: {
    isTouched: boolean
    isDirty: boolean
    isPristine: boolean
    isValidating: boolean
    errors: ValidationError[]
    errorMap: Record<ValidationCause, ValidationError>
  }
}
```

## FormApi Methods

```typescript
form.handleSubmit()
form.reset()
form.getFieldValue(field)
form.setFieldValue(field, value)
form.getFieldMeta(field)
form.setFieldMeta(field, updater)
form.validateAllFields(cause)
form.validateField(field, cause)
form.deleteField(field)
```

## Shared Form Options (formOptions)

```tsx
import { formOptions } from '@tanstack/react-form'

const sharedOpts = formOptions({
  defaultValues: { firstName: '', lastName: '' },
})

// Reuse across components
const form = useForm({
  ...sharedOpts,
  onSubmit: async ({ value }) => { /* ... */ },
})
```

## Server-Side Validation

```tsx
// TanStack Start / Next.js server action
import { ServerValidateError } from '@tanstack/react-form/nextjs'

export async function validateForm(data: FormData) {
  const email = data.get('email') as string
  if (await checkEmailExists(email)) {
    throw new ServerValidateError({
      form: 'Submission failed',
      fields: { email: 'Email already registered' },
    })
  }
}
```

## TypeScript Integration

```tsx
// Type-safe field paths with DeepKeys
interface UserForm {
  name: string
  address: { street: string; city: string }
  tags: string[]
  contacts: Array<{ name: string; phone: string }>
}

// TypeScript auto-completes all valid paths:
// 'name', 'address', 'address.street', 'address.city', 'tags', 'contacts'
<form.Field name="address.city" />     // OK
<form.Field name="nonexistent" />       // Type Error!
```

## Best Practices

1. **Always call `e.preventDefault()` and `e.stopPropagation()`** on form submit
2. **Always attach `onBlur={field.handleBlur}`** for blur validation and isTouched tracking
3. **Use `mode="array"`** for array fields to get array methods
4. **Return `undefined`** (not null/false) for valid validators
5. **Use `asyncDebounceMs`** for async validators to prevent API spam
6. **Check `isTouched` before showing errors** for better UX
7. **Use `form.Subscribe` with selectors** to minimize re-renders
8. **Use `formOptions`** for shared configuration across components
9. **Use schema validators** (Zod/Valibot) for complex validation rules
10. **Use `onChangeListenTo`** for cross-field validation dependencies

## Common Pitfalls

- Forgetting `e.preventDefault()` on form submit (causes page reload)
- Not attaching `onBlur` to inputs (breaks blur validation and isTouched)
- Returning `null` or `false` instead of `undefined` for valid fields
- Using `mode="array"` incorrectly (only needed on the array field itself, not sub-fields)
- Subscribing to entire form state instead of using selectors (unnecessary re-renders)
- Not using `asyncDebounceMs` with async validators (fires on every keystroke)

© tanstack-skills · MIT · tanstack-skills/tanstack-skills

Похожие скиллы

Всё по теме «Фронтенд и UI» →
react-hook-form-zod ovachiever Скилл учит агента собирать формы на React Hook Form с валидацией через Zod. Одна схема работает и на клиенте, и на сервере. CX Codex +1 722установок form-wizard-builder patricio0312rev Скилл собирает многошаговые формы: схемы валидации, шаги, общее состояние, индикатор прогресса и шаг проверки перед отправкой. CX Codex +1 328установок mantine-form mantinedev Скилл для сборки форм на @mantine/form: валидация, схемы zod и valibot, вложенные поля и массивы. Пригодится, когда агент пишет формы в Mantine. CX Codex +1 2 700установок tanstack-router PatrickJS Правило для Cursor, которое задаёт агенту стандарты TanStack Router v1: файловые маршруты, типизация параметров и загрузка данных через loader. ★ 41 011звёзд репо tanstack-start-shadcn-tailwind официальный github Правило задаёт Copilot стек TanStack Start, Shadcn/ui и Tailwind CSS и требует валидировать внешние данные через Zod. ★ 40 418звёзд репо clerk-tanstack-patterns clerk Подсказывает агенту, как подключить Clerk к TanStack React Start: защита маршрутов, createServerFn, лоадеры и middleware. CX Codex +1 15 500установок

Из того же набора12

tanstack-query Скилл подсказывает агенту, как работать с TanStack Query v5: кэширование, инвалидация, мутации и оптимистичные обновления. CX Codex +1 9 900установок tanstack-table Скилл учит агента настраивать headless-таблицы на TanStack Table v8. Он помогает правильно собрать сортировку и фильтрацию без поломки рендеров. CX Codex +1 5 400установок tanstack-router Скилл по TanStack Router для React и Solid: файловый роутинг, поисковые параметры и загрузка данных с проверкой типов. CX Codex +1 4 900установок tanstack-pacer Скилл подсказывает агенту, как подключить TanStack Pacer: дебаунс, троттлинг, лимиты запросов, очереди и батчинг в одном пакете. CX Codex +1 3 900установок tanstack-start Скилл помогает создавать и сопровождать приложения на фреймворке TanStack Start с потоковым SSR, серверными функциями и маршрутизацией. CX Codex +1 3 900установок tanstack-virtual Скилл подсказывает агенту, как подключить TanStack Virtual и рисовать только видимые строки в больших списках, таблицах и сетках. CX Codex +1 2 500установок tanstack-devtools Скилл учит агента подключать единую панель отладки TanStack в проектах на React. Помогает настроить плагины для Query, Router и рабочих процессов TanStack AI. CX Codex +1 2 300установок tanstack-store Скилл по TanStack Store: реактивный стор без привязки к фреймворку, адаптеры для React, Vue, Solid, Angular и Svelte. Пригодится, когда агент пишет состояние на @tanstack/store. CX Codex +1 1 800установок

Спонсоры проекта

Компании, которые помогают держать аналитику открытой