Skip to main content
The useField hook is used to create and manage individual form fields with full type safety and validation support.

Import

import { useField } from '@tanstack/react-form'

Signature

function useField<
  TParentData,
  TName extends DeepKeys<TParentData>,
  TData extends DeepValue<TParentData, TName>
>(opts: UseFieldOptions<TParentData, TName, TData>): FieldApi<TParentData, TName, TData>

Parameters

opts
UseFieldOptions<TParentData, TName, TData>
Configuration options for the field
opts.form
FormApi<TParentData>
required
The form API instance this field belongs to
opts.name
DeepKeys<TParentData>
required
The field name as a dot-notation path (e.g., "user.name" or "users[0].email")
opts.mode
'value' | 'array'
Controls reactivity behavior:
  • 'value': Re-renders on any value change (default)
  • 'array': Only re-renders when array length changes (useful for array fields)
opts.defaultValue
TData
Default value for this field
opts.asyncDebounceMs
number
Default debounce time in milliseconds for async validation
opts.asyncAlways
boolean
If true, always run async validation even when sync validation fails
opts.validators
FieldValidators<TParentData, TName, TData>
Field-level validators
opts.validators.onMount
FieldValidateOrFn<TParentData, TName, TData>
Validator that runs when the field mounts
opts.validators.onChange
FieldValidateOrFn<TParentData, TName, TData>
Validator that runs when the field value changes
onChange: ({ value }) => 
  value.length < 3 ? 'Must be at least 3 characters' : undefined
opts.validators.onChangeAsync
FieldAsyncValidateOrFn<TParentData, TName, TData>
Async validator that runs when the field value changes
onChangeAsync: async ({ value }) => {
  const exists = await checkUsername(value)
  return exists ? 'Username taken' : undefined
}
opts.validators.onChangeAsyncDebounceMs
number
Debounce time for onChange async validation
opts.validators.onChangeListenTo
DeepKeys<TParentData>[]
Array of field names that should trigger this field’s onChange validation
opts.validators.onBlur
FieldValidateOrFn<TParentData, TName, TData>
Validator that runs when the field loses focus
opts.validators.onBlurAsync
FieldAsyncValidateOrFn<TParentData, TName, TData>
Async validator that runs when the field loses focus
opts.validators.onBlurAsyncDebounceMs
number
Debounce time for onBlur async validation
opts.validators.onBlurListenTo
DeepKeys<TParentData>[]
Array of field names that should trigger this field’s onBlur validation
opts.validators.onSubmit
FieldValidateOrFn<TParentData, TName, TData>
Validator that runs on form submission
opts.validators.onSubmitAsync
FieldAsyncValidateOrFn<TParentData, TName, TData>
Async validator that runs on form submission
opts.listeners
FieldListeners<TParentData, TName, TData>
Field-level event listeners
opts.listeners.onChange
(props: { value, fieldApi }) => void
Called when field value changes
opts.listeners.onBlur
(props: { value, fieldApi }) => void
Called when field loses focus
opts.listeners.onMount
(props: { value, fieldApi }) => void
Called when field mounts

Return Value

field
FieldApi<TParentData, TName, TData>
The field API instance
name
TName
The field name
form
FormApi<TParentData>
Reference to the parent form API
state
FieldState<TParentData, TName, TData>
Current field state
value
TData
Current field value
meta
FieldMeta
Field metadata
isTouched
boolean
True if field has been touched
isBlurred
boolean
True if field has been blurred
isDirty
boolean
True if field value has been modified
isPristine
boolean
True if field value has not been modified
isValidating
boolean
True if field is currently validating
isValid
boolean
True if field has no validation errors
errors
ValidationError[]
Array of validation errors
errorMap
ValidationErrorMap
Map of errors by validation event
handleChange
(updater: Updater<TData>) => void
Update the field value. Accepts a new value or updater function.
field.handleChange('new value')
field.handleChange((prev) => prev + 1)
handleBlur
() => void
Mark the field as blurred and trigger onBlur validation
getValue
() => TData
Get the current field value
setValue
(updater: Updater<TData>, opts?: UpdateMetaOptions) => void
Set the field value programmatically
getMeta
() => FieldMeta
Get the current field metadata
setMeta
(updater: Updater<FieldMeta>) => void
Set the field metadata programmatically
pushValue
(value: TData extends any[] ? TData[number] : never, opts?: UpdateMetaOptions) => void
Push a value to the end of an array field
insertValue
(index: number, value: TData extends any[] ? TData[number] : never, opts?: UpdateMetaOptions) => void
Insert a value into an array field at a specific index
removeValue
(index: number, opts?: UpdateMetaOptions) => void
Remove a value from an array field
swapValues
(index1: number, index2: number) => void
Swap two values in an array field
replaceValue
(index: number, value: TData extends any[] ? TData[number] : never, opts?: UpdateMetaOptions) => void
Replace a value in an array field
validate
(cause: ValidationCause) => ValidationError[]
Manually trigger validation for this field

Usage

Basic Field

import { useForm, useField } from '@tanstack/react-form'

function MyComponent() {
  const form = useForm({
    defaultValues: {
      firstName: '',
    },
  })

  const firstNameField = useField({
    form,
    name: 'firstName',
  })

  return (
    <input
      value={firstNameField.state.value}
      onChange={(e) => firstNameField.handleChange(e.target.value)}
      onBlur={firstNameField.handleBlur}
    />
  )
}

Field with Validation

import { useForm, useField } from '@tanstack/react-form'

function EmailField() {
  const form = useForm({
    defaultValues: {
      email: '',
    },
  })

  const emailField = useField({
    form,
    name: 'email',
    validators: {
      onChange: ({ value }) => {
        const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
        return !emailRegex.test(value) ? 'Invalid email address' : undefined
      },
      onChangeAsyncDebounceMs: 500,
      onChangeAsync: async ({ value }) => {
        const exists = await checkEmailExists(value)
        return exists ? 'Email already registered' : undefined
      },
    },
  })

  return (
    <div>
      <input
        type="email"
        value={emailField.state.value}
        onChange={(e) => emailField.handleChange(e.target.value)}
        onBlur={emailField.handleBlur}
      />
      {emailField.state.meta.errors && (
        <span>{emailField.state.meta.errors.join(', ')}</span>
      )}
      {emailField.state.meta.isValidating && <span>Validating...</span>}
    </div>
  )
}

Array Field

import { useForm, useField } from '@tanstack/react-form'

function TodoList() {
  const form = useForm({
    defaultValues: {
      todos: [] as string[],
    },
  })

  const todosField = useField({
    form,
    name: 'todos',
    mode: 'array', // Only re-render when array length changes
  })

  return (
    <div>
      {todosField.state.value.map((_, i) => (
        <div key={i}>
          <form.Field
            name={`todos[${i}]`}
            children={(field) => (
              <input
                value={field.state.value}
                onChange={(e) => field.handleChange(e.target.value)}
              />
            )}
          />
          <button onClick={() => todosField.removeValue(i)}>Remove</button>
        </div>
      ))}
      <button onClick={() => todosField.pushValue('')}>Add Todo</button>
    </div>
  )
}

Nested Fields

import { useForm, useField } from '@tanstack/react-form'

interface User {
  profile: {
    firstName: string
    lastName: string
  }
}

function UserForm() {
  const form = useForm<User>({
    defaultValues: {
      profile: {
        firstName: '',
        lastName: '',
      },
    },
  })

  const firstNameField = useField({
    form,
    name: 'profile.firstName',
  })

  return (
    <input
      value={firstNameField.state.value}
      onChange={(e) => firstNameField.handleChange(e.target.value)}
    />
  )
}

TypeScript

import { useForm, useField } from '@tanstack/react-form'

interface FormData {
  email: string
  age: number
}

function MyComponent() {
  const form = useForm<FormData>({
    defaultValues: {
      email: '',
      age: 0,
    },
  })

  // Type-safe field names
  const emailField = useField({
    form,
    name: 'email', // Type checked!
    validators: {
      onChange: ({ value }) => {
        // value is typed as string
        return value.includes('@') ? undefined : 'Invalid email'
      },
    },
  })

  return (
    <input
      type="email"
      value={emailField.state.value} // Typed as string
      onChange={(e) => emailField.handleChange(e.target.value)}
    />
  )
}

Build docs developers (and LLMs) love