Documentation Index
Fetch the complete documentation index at: https://mintlify.com/tanstack/form/llms.txt
Use this file to discover all available pages before exploring further.
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 fieldopts.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")
Controls reactivity behavior:
'value': Re-renders on any value change (default)
'array': Only re-renders when array length changes (useful for array fields)
Default value for this field
Default debounce time in milliseconds for async validation
If true, always run async validation even when sync validation fails
opts.validators
FieldValidators<TParentData, TName, TData>
Field-level validatorsopts.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 changesonChange: ({ 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 changesonChangeAsync: async ({ value }) => {
const exists = await checkUsername(value)
return exists ? 'Username taken' : undefined
}
opts.validators.onChangeAsyncDebounceMs
Debounce time for onChange async validation
opts.validators.onChangeListenTo
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
Debounce time for onBlur async validation
opts.validators.onBlurListenTo
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 listenersopts.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 instanceReference to the parent form API
state
FieldState<TParentData, TName, TData>
Current field stateField metadataTrue if field has been touched
True if field has been blurred
True if field value has been modified
True if field value has not been modified
True if field is currently validating
True if field has no validation errors
Array of validation errors
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)
Mark the field as blurred and trigger onBlur validation
Get the current field value
setValue
(updater: Updater<TData>, opts?: UpdateMetaOptions) => void
Set the field value programmatically
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)}
/>
)
}