Technology handbook
TanStack Form
Type-safe fields, validation, async checks, arrays, submission and framework adapters.
- Topics
- 32
- Sections
- 5
Hooks
useField
Read and update one field, with its own validators and meta, from a component.
- The hook gives one field its value, its handlers and its own meta.
- Only the component holding that field re-renders when the value changes.
- Field validators declared here travel with the field wherever it is used.
form.Field
Bind a field through a render prop when the field's state should not lift a whole form render.
- The render prop keeps field state out of the surrounding component.
- A parent that never reads the value is never re-rendered by typing in it.
- It is the right shape when fields are generated from a configuration list.
form.Subscribe
Re-render a small part of the form when a selected piece of form state changes.
- Subscribing renders only the part that depends on the selected state.
- A submit button can track validity without the whole form re-rendering.
- The selector should return a primitive, or the comparison never matches.
useStore with a form selector
Subscribe to a slice of form state - canSubmit, isDirty, errors - instead of the whole store.
- Reading a slice keeps unrelated keystrokes from touching the subscriber.
- Submittability, dirtiness and the error map are the usual slices.
- Selecting the whole store subscribes to everything and defeats the purpose.
formOptions
Declare shared form configuration once and reuse it across client and server.
- Shared options are declared once and reused by every form that needs them.
- The same object can be used on the server, which keeps validation identical.
- Default values declared here are what the field types are inferred from.
useTransform and mergeForm
Merge server-returned state into the client form after a submission.
- A server response can be merged back into the live form state.
- This is how server-side errors reach the fields that caused them.
- The merge keeps what the user typed instead of resetting the form.
Field-level validators
Attach sync and async validation to the field that owns it, with its own debounce.
- A field validates itself, so moving it does not move its rules.
- Each field can have its own timing and its own debounce interval.
- Errors returned here are already associated with the field that owns them.
Array fields
Add, remove and reorder rows while keeping each row's validation and meta attached.
- Rows are addressed by index, and the API keeps their state aligned on change.
- Each row keeps its own validation and touched state across a reorder.
- Push, insert and remove through the form rather than mutating the array.
Validation
Field validators
Validate one value in isolation and return a message the field can render.
- A field rule sees one value and returns a message or nothing.
- Keep the message about what the user must do, not about the internal rule.
- Pure functions here stay testable without rendering the form.
Form-level validators
Express rules that need more than one field, such as matching passwords.
- Some rules need two values, so they cannot live on either field.
- Attach the resulting issue to the field the user should correct.
- Form rules run after the fields pass, so they see validated values.
Validation timing
Choose change, blur or submit per rule so the form corrects rather than nags.
- Validating on every keystroke reports an error before the entry is finished.
- Blur is the usual first pass; submit is the guaranteed one.
- Once a field has failed, revalidating on change lets it recover immediately.
Schema validation
Drive validation from a Zod or Valibot schema and keep one definition of the shape.
- One schema can validate the form and type the submitted value.
- The same schema runs on the server, so the two cannot drift apart.
- Schema issues map onto fields by path with no manual translation.
Async validators
Check availability against a server with debouncing and a pending state that blocks submit.
- A server check needs a debounce or it fires on every keystroke.
- Submission must wait for a pending check, or a stale pass slips through.
- Show the pending state, so the reader knows why submit is disabled.
Error mapping
Return server-side failures onto the specific fields that produced them.
- A rejected submission returns errors keyed by field path.
- Mapping them back puts each message next to the input that caused it.
- A single banner saying the save failed leaves the reader hunting.
Arrays & dynamic fields
Field arrays
Render a repeating group and keep each row's identity stable across reorders.
- A stable key per row keeps focus and errors attached through a reorder.
- Using the array index as the key loses that state the moment rows move.
- The row component reads its own path rather than the whole array.
Adding and removing rows
Mutate the array through the form API so validation and dirty state stay correct.
- Mutating through the form keeps validation and dirty state consistent.
- Removing a row must drop its errors too, or the form stays invalid invisibly.
- Inserting in the middle shifts every later path, which the API handles.
Nested objects
Model grouped fields as an object and validate the group as a unit.
- Grouped fields validate as a unit when the group has its own rule.
- Paths mirror the shape, so an address block reads as one object.
- The submitted value arrives already nested, needing no reassembly.
Conditional fields
Show and validate a field only while another value makes it relevant.
- A hidden field should stop validating, or submit fails on something invisible.
- Decide whether hiding clears the value or preserves it for when it returns.
- Drive visibility from form state rather than from separate component state.
Reordering
Move rows without losing per-row errors or focus.
- Moving a row keeps its errors, its touched flag and its focus.
- Reordering is a form operation, not an array mutation done outside it.
- Announce the move, or a keyboard user has no idea the list changed.
Array-level validation
Enforce minimum and maximum counts separately from each row's own rules.
- Minimum and maximum counts belong to the array, not to any row.
- Uniqueness across rows is an array rule with an issue pointing at a row.
- Row rules still run, so both levels report independently.
Submission & integration
onSubmit
Receive validated values once and perform the write from a single place.
- The handler receives one validated value, so the write happens in a single place.
- Validation runs first, so an invalid form never reaches the handler.
- Throwing here surfaces as a submission error rather than an unhandled rejection.
canSubmit and isSubmitting
Derive button state from the form instead of tracking a separate flag.
- Derived state removes the separate flag that always drifts out of sync.
- A submit button disabled on pending async validation prevents a double write.
- Both come from a subscription, so the button alone re-renders.
Reset and clear
Return to initial values or to empty, and know which one the user expects.
- Reset returns to the values the form started with, not to empty.
- Clearing to empty is a different intent and needs explicit values.
- Resetting after a successful save also drops the unsaved-change warning.
Server actions
Submit to a server function and surface its result without losing entered values.
- A form can submit to a server function and receive typed results back.
- Keep the entered values on failure, so the reader does not retype the form.
- Validate on the server too: the client schema is a convenience, not a guard.
Progressive enhancement
Keep a form usable before hydration and enrich it once the client takes over.
- A real form element and a method mean the page works before hydration.
- Client validation then enriches a form that already submits without it.
- Anything that only exists in JavaScript must not be required to submit.
Framework adapters
Use the binding for React, Vue, Solid or Angular without changing the core model.
- The core is framework-agnostic; the adapter supplies reactivity.
- Validation, arrays and submission behave the same across React, Vue and Solid.
- Only the binding layer changes when a team moves between frameworks.
Sources
- TanStack Form official documentation