Patterns

Patterns

Loading-gated async defaults

When to use: initial form values come from a server (edit-a-record flows). How: fetch first (e.g. useQuery), branch on isLoading, only call useForm once real data is available; fall back to sensible empty defaults (data?.field ?? ''). Trade-offs: simple and type-safe, but the form literally doesn't exist until the fetch resolves — no form to show a skeleton inside; the loading branch replaces the whole form. (Ch 10)

Timing-scoped validation via revalidateLogic

When to use: don't want to nag users with errors before they've tried to submit once, but do want immediate feedback after. How: validationLogic: revalidateLogic({ mode: 'submit', modeAfterSubmission: 'blur' }) + an onDynamic validator. Trade-offs: adds a second validator "track" (onDynamic) alongside any onChange/onBlur validators already on the same field — easy to end up validating the same rule twice if not careful. (Ch 9)

Cross-field validation via listen-to

When to use: one field's validity depends on another's current value (confirm-password, min/max range pairs). How: onChangeListenTo: ['otherField'] + read fieldApi.form.getFieldValue('otherField') inside the validator. Trade-offs: only the listening field re-validates automatically; if both fields should show an error when they conflict, wire the listener on both, or use a form-level validator instead. (Ch 13)

Array field CRUD

When to use: dynamic lists of sub-objects (line items, team members). How: mode="array" on the parent field, field.pushValue/removeValue/insertValue/swapValues/moveValue for mutation, `arr[${i}].key` naming for nested sub-fields. Trade-offs: index-based naming means reordering (moveValue/swapValues) shifts every subsequent field's effective name — don't rely on a field's name staying stable across reorders for anything besides React's key. (Ch 11)

Pre-bound field components via createFormHook

When to use: more than a couple of forms in the app, or a design system to keep consistent. How: createFormHookContexts() → custom components reading useFieldContext/useFormContextcreateFormHook({ fieldComponents, formComponents }) → use useAppForm/form.AppField everywhere instead of raw useForm/form.Field. Trade-offs: upfront setup cost for the first form; pays off from the second form onward. Skipping this and wiring form.Field render props by hand in every component is the most common source of duplicated UI-library glue code (ch018). (Ch 20)

Multi-step wizard via FormGroup

When to use: a form is naturally split into sequential steps, each with its own validation gate. How: structure defaultValues around step keys (step1: {...}, step2: {...}), wrap each step's fields in <form.FormGroup name="stepN">, advance via onGroupSubmit, call the parent form.handleSubmit() only on the final step. Trade-offs: defaultValues shape is now coupled to the wizard's step structure — restructuring steps later means restructuring the data shape too. For a wizard where steps aren't fixed in advance, plain field-level validation with manual step-gating logic may be more flexible. (Ch 12)

Server-validated SSR forms

When to use: TanStack Start / Next.js App Router / Remix apps that need forms to work correctly before client JS hydrates. How: createServerValidate in the framework's server entry point (server function / server action / route action), catch ServerValidateError, read the result back on the client (loader / useActionState / useActionData), useTransform + mergeForm into the client useForm instance. Trade-offs: three different read-back mechanisms per framework (ch022's reference table) — the pattern is consistent but not copy-paste identical across TanStack Start/Next.js/Remix. (Ch 22)

Structured (non-string) validation errors

When to use: error UI needs severity levels, i18n error codes, or multiple messages per field. How: return an object/array from a validator instead of a string; anything truthy counts as an error. Trade-offs: consuming code must know to read err.message/err.code instead of treating err as a display string directly — a plain-string validator elsewhere in the same form won't break this, but mixing shapes on the same field's different validators can. (Ch 16)