AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Tanstack Form

skill-tanstack-skills-tanstack-skills-tanstack-form · by tanstack-skills

Headless, performant, and type-safe form state management for TS/JS, React, Vue, Angular, Solid, Lit, and Svelte.

No reviews yet
0 installs
37 views
0.0% view→install

Install

$ agentstack add skill-tanstack-skills-tanstack-skills-tanstack-form

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-tanstack-skills-tanstack-skills-tanstack-form)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
7mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Tanstack Form? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

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

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

Core: useForm

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 (
     {
        e.preventDefault()
        e.stopPropagation()
        form.handleSubmit()
      }}
    >
      {/* Fields */}
       ({ canSubmit: state.canSubmit, isSubmitting: state.isSubmitting })}
        children={({ canSubmit, isSubmitting }) => (
          
            {isSubmitting ? 'Submitting...' : 'Submit'}
          
        )}
      />
    
  )
}

Fields (form.Field)


      value.length  (
    
      First Name
       field.handleChange(e.target.value)}
      />
      {field.state.meta.isTouched && field.state.meta.errors.length > 0 && (
        {field.state.meta.errors.join(', ')}
      )}
    
  )}
/>

  {(field) => (
     field.handleChange(e.target.value)}
      onBlur={field.handleBlur}
    />
  )}

Validation

Validation Timing

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

Synchronous Validation

 {
      if (value  {
      if (!value) return 'Required'
      return undefined
    },
  }}
/>

Asynchronous Validation

 {
      const res = await fetch(`/api/check-username?q=${value}`)
      const { available } = await res.json()
      if (!available) return 'Username taken'
      return undefined
    },
  }}
>
  {(field) => (
    <>
       field.handleChange(e.target.value)} />
      {field.state.meta.isValidating && Checking...}
    
  )}

Schema Validation (Zod)

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

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

Form-Level Validation

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

Linked/Dependent Fields

 {
      const password = fieldApi.form.getFieldValue('password')
      if (value !== password) return 'Passwords do not match'
      return undefined
    },
  }}
/>

Array Fields


  {(field) => (
    
      {field.state.value.map((_, index) => (
        
          
            {(subField) => (
               subField.handleChange(e.target.value)}
              />
            )}
          
           field.removeValue(index)}>
            Remove
          
        
      ))}
       field.pushValue({ name: '', age: 0 })}>
        Add Person
      
    
  )}

Array Methods

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)

 {
      // Side effect: reset dependent fields
      form.setFieldValue('state', '')
      form.setFieldValue('postalCode', '')
    },
  }}
/>

Reactivity (form.Subscribe & useStore)

// Render-prop subscription (fine-grained)
 ({ canSubmit: state.canSubmit, isDirty: state.isDirty })}
  children={({ canSubmit, isDirty }) => (
    
      {isDirty && Unsaved changes}
      Save
    
  )}
/>

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

Form State

interface FormState {
  values: TFormData
  errors: ValidationError[]
  errorMap: Record
  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

interface FieldState {
  value: TData
  meta: {
    isTouched: boolean
    isDirty: boolean
    isPristine: boolean
    isValidating: boolean
    errors: ValidationError[]
    errorMap: Record
  }
}

FormApi Methods

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)

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

// 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

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

// TypeScript auto-completes all valid paths:
// 'name', 'address', 'address.street', 'address.city', 'tags', 'contacts'
     // OK
       // 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)

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.