TanStack
Guides

Content temporarily unavailable

As a form grows, keeping every field in one component can make the code harder to navigate and maintain. You can move related fields into smaller components while keeping one form instance, but each new component boundary needs a type for the form or field it receives.

Split a large form into sections

First define the form's reusable options with formOptions. In this example, the same module also exports a named form type derived from those options. The parent can add page-specific options, such as onSubmit, when it creates the form, while extracted sections can import the same form type.

React's ReactFormType derives the form prop from the shared options. The result keeps the field names and values available to form.Field in the extracted component.

shared-form.ts
import { formOptions } from '@tanstack/react-form'
import type { ReactFormType } from '@tanstack/react-form'

export const profileFormOptions = formOptions({
  defaultValues: {
    name: '',
    address: {
      street: '',
      city: '',
    },
  },
})

export type ProfileForm = ReactFormType<typeof profileFormOptions>

Extract a field component by value type

After splitting the form into sections, you may still have repeated controls for values such as strings, numbers, or dates. These controls don't need to know the form's complete data shape or the path of a particular field. They only need the type of the value they read and update.

FieldWithValue<T> describes a field by the value type a reusable component handles. This preserves type checking when the component reads or updates the value without tying it to a particular field path or form shape.

Type the React component's field prop as FieldWithValue<string>, then pass it the field from the form.Field render prop.

TextField.tsx
import type { FieldWithValue } from '@tanstack/react-form'

interface TextFieldProps {
  field: FieldWithValue<string>
  label: string
}

export function TextField({ field, label }: TextFieldProps) {
  return (
    <label>
      <span>{label}</span>
      <input
        name={field.name}
        value={field.value}
        onBlur={field.handleBlur}
        onChange={(event) => field.handleChange(event.target.value)}
      />
    </label>
  )
}

Accept any form or field

Some extracted controls behave the same regardless of the form's data. An error display only needs field metadata, while a submit or reset button may only need form state and methods. Giving these controls the concrete ProfileForm type would couple them to details they don't use.

Use AnyFieldApi when a control doesn't depend on the field's value type. For form-level controls, use the adapter's Any*FormApi type. These types deliberately remove information about values and field paths, so use a concrete type when a component reads either one.

React provides AnyReactFormApi for form-level components. Pass the field from form.Field to FieldErrors, and pass the form instance to SubmitButton.

FieldErrors.tsx
import type { AnyFieldApi } from '@tanstack/react-form'

export function FieldErrors({ field }: { field: AnyFieldApi }) {
  return (
    <small role="alert" aria-live="polite">
      {field.errors.map((error) => error.message).join(', ')}
    </small>
  )
}