TanStack Form is unlike most form libraries you've used before. It's designed for large-scale production usage, with a focus on type safety, performance and composition for an unmatched developer experience.
As a result, we've developed a philosophy around the library's usage that values scalability and long-term developer experience over short and sharable code snippets.
Here's an example of a form following many of our best practices, which will allow you to rapidly develop even high-complexity forms after a short onboarding experience:
import React from 'react'
import ReactDOM from 'react-dom/client'
import { createFormHook, createFormHookContexts } from '@tanstack/react-form'
// Form components that pre-bind events from the form hook; check our "Form Composition" guide for more
import { TextField, NumberField, SubmitButton } from '~our-app/ui-library'
// We also support Valibot, ArkType, and any other standard schema library
import { z } from 'zod'
const { fieldContext, formContext } = createFormHookContexts()
// Allow us to bind components to the form to keep type safety but reduce production boilerplate
// Define this once to have a generator of consistent form instances throughout your app
const { useAppForm } = createFormHook({
fieldComponents: {
TextField,
NumberField,
},
formComponents: {
SubmitButton,
},
fieldContext,
formContext,
})
const PeoplePage = () => {
const form = useAppForm({
defaultValues: {
username: '',
age: 0,
},
validators: {
// Pass a schema or function to validate
onChange: z.object({
username: z.string(),
age: z.number().min(13),
}),
},
onSubmit: ({ value }) => {
// Do something with form data
alert(JSON.stringify(value, null, 2))
},
})
return (
<form
onSubmit={(e) => {
e.preventDefault()
form.handleSubmit()
}}
>
<h1>Personal Information</h1>
{/* Components are bound to `form` and `field` to ensure extreme type safety */}
{/* Use `form.AppField` to render a component bound to a single field */}
<form.AppField
name="username"
children={(field) => <field.TextField label="Full Name" />}
/>
{/* The "name" property will throw a TypeScript error if typo'd */}
<form.AppField
name="age"
children={(field) => <field.NumberField label="Age" />}
/>
{/* Components in `form.AppForm` have access to the form context */}
<form.AppForm>
<form.SubmitButton />
</form.AppForm>
</form>
)
}
const rootElement = document.getElementById('root')!
ReactDOM.createRoot(rootElement).render(<PeoplePage />)
import React from 'react'
import ReactDOM from 'react-dom/client'
import { createFormHook, createFormHookContexts } from '@tanstack/react-form'
// Form components that pre-bind events from the form hook; check our "Form Composition" guide for more
import { TextField, NumberField, SubmitButton } from '~our-app/ui-library'
// We also support Valibot, ArkType, and any other standard schema library
import { z } from 'zod'
const { fieldContext, formContext } = createFormHookContexts()
// Allow us to bind components to the form to keep type safety but reduce production boilerplate
// Define this once to have a generator of consistent form instances throughout your app
const { useAppForm } = createFormHook({
fieldComponents: {
TextField,
NumberField,
},
formComponents: {
SubmitButton,
},
fieldContext,
formContext,
})
const PeoplePage = () => {
const form = useAppForm({
defaultValues: {
username: '',
age: 0,
},
validators: {
// Pass a schema or function to validate
onChange: z.object({
username: z.string(),
age: z.number().min(13),
}),
},
onSubmit: ({ value }) => {
// Do something with form data
alert(JSON.stringify(value, null, 2))
},
})
return (
<form
onSubmit={(e) => {
e.preventDefault()
form.handleSubmit()
}}
>
<h1>Personal Information</h1>
{/* Components are bound to `form` and `field` to ensure extreme type safety */}
{/* Use `form.AppField` to render a component bound to a single field */}
<form.AppField
name="username"
children={(field) => <field.TextField label="Full Name" />}
/>
{/* The "name" property will throw a TypeScript error if typo'd */}
<form.AppField
name="age"
children={(field) => <field.NumberField label="Age" />}
/>
{/* Components in `form.AppForm` have access to the form context */}
<form.AppForm>
<form.SubmitButton />
</form.AppForm>
</form>
)
}
const rootElement = document.getElementById('root')!
ReactDOM.createRoot(rootElement).render(<PeoplePage />)
While we generally suggest using createFormHook for reduced boilerplate in the long-run, we also support one-off components and other behaviors using useForm and form.Field:
import React from 'react'
import ReactDOM from 'react-dom/client'
import { useForm } from '@tanstack/react-form'
const PeoplePage = () => {
const form = useForm({
defaultValues: {
username: '',
age: 0,
},
onSubmit: ({ value }) => {
// Do something with form data
alert(JSON.stringify(value, null, 2))
},
})
return (
<form.Field
name="age"
validators={{
// We can choose between form-wide and field-specific validators
onChange: ({ value }) =>
value > 13 ? undefined : 'Must be 13 or older',
}}
children={(field) => (
<>
<input
name={field.name}
value={field.state.value}
onBlur={field.handleBlur}
type="number"
onChange={(e) => field.handleChange(e.target.valueAsNumber)}
/>
{field.state.meta.errors.length ? (
<em>{field.state.meta.errors.join(',')}</em>
) : null}
</>
)}
/>
)
}
const rootElement = document.getElementById('root')!
ReactDOM.createRoot(rootElement).render(<PeoplePage />)
import React from 'react'
import ReactDOM from 'react-dom/client'
import { useForm } from '@tanstack/react-form'
const PeoplePage = () => {
const form = useForm({
defaultValues: {
username: '',
age: 0,
},
onSubmit: ({ value }) => {
// Do something with form data
alert(JSON.stringify(value, null, 2))
},
})
return (
<form.Field
name="age"
validators={{
// We can choose between form-wide and field-specific validators
onChange: ({ value }) =>
value > 13 ? undefined : 'Must be 13 or older',
}}
children={(field) => (
<>
<input
name={field.name}
value={field.state.value}
onBlur={field.handleBlur}
type="number"
onChange={(e) => field.handleChange(e.target.valueAsNumber)}
/>
{field.state.meta.errors.length ? (
<em>{field.state.meta.errors.join(',')}</em>
) : null}
</>
)}
/>
)
}
const rootElement = document.getElementById('root')!
ReactDOM.createRoot(rootElement).render(<PeoplePage />)
All properties from useForm can be used in useAppForm and all properties from form.Field can be used in form.AppField.
Your weekly dose of JavaScript news. Delivered every Monday to over 100,000 devs, for free.