Nested Forms
Spread form fields across any number of components using context and useFormSelector.
A GInput doesn't have to be a direct child of GForm. Because gform-react uses React context, a
GInput works anywhere inside the GForm subtree - even several components deep. This lets you
break large forms into focused, reusable section components.
Fields in child components
Move groups of fields into their own components. As long as they render inside the GForm, they
join the same form state and validation.
import { GForm, GInput, GValidator, type GValidators } from "gform-react";
interface ProfileForm {
firstName: string;
lastName: string;
street: string;
city: string;
}
const validators: GValidators<ProfileForm> = {
"*": new GValidator().withRequiredMessage("Required"),
};
// A reusable text field.
function Field({ formKey, label }: { formKey: keyof ProfileForm; label: string }) {
return (
<GInput
formKey={formKey}
required
element={(input, props) => (
<label>
<span>{label}</span>
<input {...props} />
{input.error && <small>{input.errorText}</small>}
</label>
)}
/>
);
}
// A whole section, several components deep - still part of the same form.
function AddressSection() {
return (
<fieldset>
<legend>Address</legend>
<Field formKey="street" label="Street" />
<Field formKey="city" label="City" />
</fieldset>
);
}
export default function ProfileForm() {
return (
<GForm<ProfileForm>
validators={validators}
onSubmit={(state, e) => {
e.preventDefault();
console.log(state.toRawData());
}}
>
<Field formKey="firstName" label="First name" />
<Field formKey="lastName" label="Last name" />
<AddressSection />
<button>Save</button>
</GForm>
);
}Reading fields with useFormSelector
Any component inside the form can subscribe to a slice of form state with useFormSelector -
without prop-drilling. It re-renders only when the selected slice changes, preserving gform's minimal
re-render guarantee.
import { useFormSelector } from "gform-react";
function CityBadge() {
const city = useFormSelector((state) => state.fields.city);
return <span>Selected city: {String(city?.value ?? "-")}</span>;
}useFormSelector subscribes to just the slice you return. A component reading state.fields.city
won't re-render when an unrelated field changes - which is how nested forms stay performant even
when they're large.