Custom Components and Composition
As your forms grow, you will notice yourself repeating the same pattern: form.Field, an input, and an error display block. It gets verbose fast. The solution is extracting that pattern into reusable components.
Basic Reusable Field Component
The straightforward approach is to pull the repeated structure into its own component and pass the form instance as a prop.
function TextField({
form,
name,
label,
}: {
form: ReturnType<typeof useForm>
name: string
label: string
}) {
return (
<form.Field
name={name}
children={(field) => (
<div>
<label htmlFor={name}>{label}</label>
<input
id={name}
value={field.state.value}
onChange={(e) => field.handleChange(e.target.value)}
onBlur={field.handleBlur}
/>
{field.state.meta.isTouched && field.state.meta.errors.length > 0 && (
<p style={{ color: 'red' }}>{field.state.meta.errors.join(', ')}</p>
)}
</div>
)}
/>
)
}
This works, but the name prop is typed as string, which means you lose autocomplete and type checking on field names. That matters as forms get more complex.
form.AppField for Type-Safe Composition
TanStack Form's recommended pattern for reusable components is form.AppField. It wraps your field component and preserves type safety on field names — so if you rename a field in your schema, TypeScript will tell you everywhere you need to update.
<form.AppField name="firstName">
{(field) => <TextInput field={field} label="First Name" />}
</form.AppField>
Your TextInput component just accepts a field prop and renders the input and errors. The AppField wrapper handles the binding to the form and ensures name is a valid key in your form's shape. This is the pattern to reach for once you start building a library of field components.
createFormHook for App-Wide Configuration
When you have a design system with standard field types — text inputs, selects, checkboxes — you can share that configuration across your entire app using createFormHook.
import { createFormHook, createFormHookContexts } from '@tanstack/react-form'
const { fieldContext, formContext } = createFormHookContexts()
const { useAppForm } = createFormHook({
fieldContext,
formContext,
fieldComponents: {
TextField: TextInput,
},
formComponents: {
SubmitButton,
},
})
Now anywhere you call useAppForm, your custom field and form components are available directly on the form instance. Your team gets a consistent API and you write the wiring code once.
Breaking Forms into Sub-Components
For large forms, you will want to split rendering across multiple components. The two main approaches are:
- Pass the form instance as a prop — works fine for simple cases and keeps things explicit.
- Use the context from
createFormHook— thefieldContextandformContextlet child components access the form without prop drilling.
The context approach pairs well with createFormHook since you are already setting it up.
Where to Start
Start with the basic extraction when you find yourself copying the same field structure more than twice. Move to AppField when type safety on field names matters — which is most real projects. Reach for createFormHook when you have a shared design system and want a consistent form API across your app.
The patterns compose well. You do not have to choose just one.