Files
english/.opencode/skills/tanstack/references/tanstack-form.md
2026-04-12 01:06:31 +07:00

3.5 KiB

TanStack Form Reference

Setup

npm i @tanstack/react-form
npm i @tanstack/zod-form-adapter zod  # optional: Zod validation

useForm

const form = useForm({
  defaultValues: { name: '', email: '', age: 0 },
  validatorAdapter: zodValidator,  // optional global adapter
  onSubmit: async ({ value }) => {
    // value is fully typed: { name: string, email: string, age: number }
    await api.createUser(value)
  },
})

form.Field

<form.Field
  name="email"
  validators={{
    onChange: z.string().email('Invalid email'),
    onBlur: ({ value }) => !value ? 'Required' : undefined,
    onBlurAsync: async ({ value }) => {
      const taken = await checkEmailTaken(value)
      return taken ? 'Email already taken' : undefined
    },
    onBlurAsyncDebounceMs: 500,
  }}
>
  {(field) => (
    <div>
      <input
        value={field.state.value}
        onBlur={field.handleBlur}
        onChange={(e) => field.handleChange(e.target.value)}
      />
      {field.state.meta.errors.map((err) => <p key={err}>{err}</p>)}
    </div>
  )}
</form.Field>

Validation Events

Event When Use Case
onChange Every keystroke Format validation
onBlur Field loses focus Required checks
onBlurAsync Field loses focus Server-side checks
onSubmit Form submission Final validation
onSubmitAsync Form submission Async final validation

All support Zod schemas or inline functions returning string | undefined.

form.Subscribe — Reactive UI

<form.Subscribe selector={(s) => [s.canSubmit, s.isSubmitting]}>
  {([canSubmit, isSubmitting]) => (
    <button type="submit" disabled={!canSubmit}>
      {isSubmitting ? 'Saving...' : 'Submit'}
    </button>
  )}
</form.Subscribe>

Nested & Array Fields

// Nested object
<form.Field name="address.city">{...}</form.Field>

// Array field
<form.Field name="tags" mode="array">
  {(field) => (
    <>
      {field.state.value.map((_, i) => (
        <form.Field key={i} name={`tags[${i}]`}>
          {(subField) => <input value={subField.state.value} ... />}
        </form.Field>
      ))}
      <button onClick={() => field.pushValue('')}>Add Tag</button>
    </>
  )}
</form.Field>

Server-Side Validation (TanStack Start)

// server
import { createServerValidate } from '@tanstack/react-form/start'
const serverValidate = createServerValidate({
  validatorAdapter: zodValidator,
  onServerValidate: z.object({ email: z.string().email() }),
})

// client — merge server errors into form state
import { mergeForm, useTransform } from '@tanstack/react-form'
useTransform((state) => mergeForm(state, serverState), [serverState])

Form State Properties

Property Type Description
values T Current form values
errors string[] Form-level errors
canSubmit boolean No errors + not submitting
isSubmitting boolean Submission in progress
isDirty boolean Values differ from defaults
isTouched boolean Any field touched
isValid boolean No validation errors

Field State Meta

Property Type Description
errors string[] Field validation errors
isTouched boolean Field was blurred
isDirty boolean Value changed from default

Schema Adapters

  • @tanstack/zod-form-adapter — Zod
  • @tanstack/valibot-form-adapter — Valibot
  • @tanstack/yup-form-adapter — Yup (community)