Capítulo 20 de 29
createFormHook lets you pre-bind your own field/form components (a TextField, a SubmitButton, etc.) to a shared context once, so every form in the app gets useAppForm/form.AppField with those components already wired — and withForm/withFieldGroup let you split a large form into sub-components without prop-drilling the form instance through every level.
createFormHookContexts() produces a fieldContext/formContext pair used to build components that can read the current field/form without receiving it as an explicit prop.createFormHook({ fieldContext, formContext, fieldComponents, formComponents }) returns useAppForm (a useForm replacement) and withForm (a HOC for sub-forms), with your custom components (e.g. TextField) pre-registered.useFieldContext<T>() inside a custom component (like TextField) pulls the current field from context — this is what lets TextField be written once and reused across every form built with this hook.withForm wraps a piece of a form (e.g. an AddressSection) so it can be composed into a parent without threading the form instance through props manually — the parent passes form once, sub-components read it via context.withFieldGroup is the analogous tool for reusing a cluster of related fields (not a whole form) across multiple different forms.const { fieldContext, formContext } = createFormHookContexts()
function TextField({ label }) {
const field = useFieldContext<string>()
return (
<label>
<span>{label}</span>
<input value={field.state.value} onChange={(e) => field.handleChange(e.target.value)} />
</label>
)
}
const { useAppForm, withForm } = createFormHook({
fieldContext, formContext,
fieldComponents: { TextField },
formComponents: { SubmitButton },
})
const AddressSection = withForm({
defaultValues: { street: '', city: '' },
render: ({ form }) => (
<form.AppField name="street">{(field) => <field.TextField label="Street" />}</form.AppField>
),
})
TextField to a composable AddressSection sub-form — this is the concrete expression of the "built for composition" philosophy from ch003.A team building a multi-page checkout flow would set up createFormHookContexts/createFormHook once at the app level, registering TextField, SelectField, and SubmitButton as fieldComponents/formComponents. Every page's form then calls useAppForm instead of useForm, immediately getting typed, pre-styled fields via form.AppField — no per-page UI-library wiring (ch018) repeated. A shared AddressSection (built with withForm, as above) can then be dropped into both the "shipping address" and "billing address" steps of the checkout without duplicating its field markup or its validators.
createFormHook once per app/design-system, not once per form — this is infrastructure, analogous to radix-primitives-docs/base-ui-docs's "wrap the primitive once" pattern in this same skill library.withForm solves prop-drilling for splitting one large form into pieces; withFieldGroup solves reusing a field cluster across different forms — pick based on which problem you actually have.createFormHook is set up, prefer useAppForm/form.AppField over raw useForm/form.Field everywhere in the app — reaching for the raw hooks bypasses the whole point of this setup.fieldComponents centralizes.withFieldGroup reuses field definitions, FormGroup scopes validation within one form instance.