| id | getting-and-setting-values |
|---|---|
| title | Getting and Setting Form Values |
This guide covers the different ways to read and write form values in TanStack Form—both at initialization time and at runtime.
The simplest way to populate a form is by passing defaultValues to useForm:
const form = useForm({
defaultValues: {
firstName: 'John',
lastName: 'Doe',
age: 30,
},
onSubmit: async ({ value }) => {
console.log(value)
},
})You can also share defaults across multiple forms using formOptions:
import { formOptions, useForm } from '@tanstack/react-form'
const sharedOptions = formOptions({
defaultValues: {
firstName: '',
lastName: '',
},
})
// Later, in a component:
const form = useForm({
...sharedOptions,
onSubmit: async ({ value }) => console.log(value),
})When your default values come from a server, you have two main options.
You can fetch the data yourself and pass it to defaultValues once it is ready. Until then, render a loading state:
export default function App() {
const [initialData, setInitialData] = React.useState<{
firstName: string
lastName: string
} | null>(null)
React.useEffect(() => {
fetch('/api/user')
.then((r) => r.json())
.then((data) => setInitialData(data))
}, [])
if (!initialData) return <p>Loading…</p>
return <UserForm initialData={initialData} />
}
function UserForm({
initialData,
}: {
initialData: { firstName: string; lastName: string }
}) {
const form = useForm({
defaultValues: initialData,
onSubmit: async ({ value }) => console.log(value),
})
// …render the form
}Tip: Always render the form after the data is ready so
defaultValuesis only evaluated once. ChangingdefaultValuesafter the form mounts has no effect.
TanStack Query pairs perfectly with TanStack Form for async defaults. See the dedicated Async Initial Values guide for a complete example.
import { useForm } from '@tanstack/react-form'
import { useQuery } from '@tanstack/react-query'
export default function App() {
const { data, isLoading } = useQuery({
queryKey: ['user'],
queryFn: () => fetch('/api/user').then((r) => r.json()),
})
if (isLoading) return <p>Loading…</p>
return <UserForm defaultValues={data} />
}Within a form.Field render, the current value is available on field.state.value:
<form.Field name="firstName">
{(field) => (
<input
value={field.state.value}
onChange={(e) => field.handleChange(e.target.value)}
/>
)}
</form.Field>Call form.getFieldValue(fieldName) anywhere outside a render prop to read the current snapshot of a field value. This does not subscribe to future updates:
const currentName = form.getFieldValue('firstName')To read the entire form state at once, access form.state.values:
const allValues = form.state.valuesIf you need a value that re-renders your component when it changes, use useStore from the store package:
import { useStore } from '@tanstack/react-store'
const firstName = useStore(form.store, (state) => state.values.firstName)To keep re-renders scoped to a sub-tree of your UI rather than the full component, use the form.Subscribe component:
<form.Subscribe selector={(state) => state.values.age}>
{(age) => <p>You are {age} years old.</p>}
</form.Subscribe>See the Reactivity guide for a deeper dive into
useStorevsform.Subscribe.
Use form.setFieldValue to update one field from outside a render prop—for example in response to a button click or an external event:
// Set a scalar value
form.setFieldValue('firstName', 'Jane')
// Use an updater function for derived updates
form.setFieldValue('age', (prev) => prev + 1)Inside a field's render prop, prefer field.setValue (or field.handleChange) instead:
<form.Field name="firstName">
{(field) => (
<button onClick={() => field.setValue('Jane')}>Set name to Jane</button>
)}
</form.Field>To replace all field values at once (and optionally update the stored default values), call form.reset:
// Reset to completely new values
form.reset({
firstName: 'Jane',
lastName: 'Smith',
age: 25,
})form.reset also re-initialises the form's "dirty" and "touched" tracking, making it ideal for switching between records in a master/detail view.
If you need to set several fields without resetting the form state, call setFieldValue multiple times:
form.setFieldValue('firstName', 'Jane')
form.setFieldValue('lastName', 'Smith')By default, calling form.setFieldValue or field.setValue does run the field's change-event validators (the same validators that run when the user types). This is equivalent to how the value changes when a user interacts with the input.
To update a value without triggering validation, pass { dontValidate: true } in the options:
form.setFieldValue('firstName', 'Jane', { dontValidate: true })To run validation on a field without changing its value, call form.validateField:
// Validate as if the field just changed
await form.validateField('firstName', 'change')
// Validate as if the field was blurred
await form.validateField('firstName', 'blur')Validators often only run after a field has been touched. If you set a value programmatically and also want to mark the field as touched (so error messages appear), call field.setMeta alongside the value update:
<form.Field name="firstName">
{(field) => (
<button
onClick={() => {
field.setValue('Jane')
field.setMeta((prev) => ({ ...prev, isTouched: true }))
}}
>
Set and touch
</button>
)}
</form.Field>For side effects that should fire when a field changes (e.g. resetting a dependent field), use the listeners API:
<form.Field
name="country"
listeners={{
onChange: ({ value }) => {
form.setFieldValue('province', '')
},
}}
>
{(field) => <input value={field.state.value} onChange={(e) => field.handleChange(e.target.value)} />}
</form.Field>See the Side effects for event triggers guide for more detail, including built-in debouncing and form-level listeners.