Core Concepts
GForm, formKey, the GInput element render prop, and how form state works.
A gform-react form is made of three pieces:
- A typed interface describing your form - keys are field names, values are field value types.
- A
validatorsobject (kept at module level for a stable reference). GFormcomponent wrapping one or moreGInputfields.
The form interface
You describe your form as a TypeScript interface - keys are field names (formKeys), values are the
field value types. This single type drives the typing of state, validators, toRawData(), toFormData(), and toURLSearchParams().
interface IRegisterForm {
name: string;
age: number;
dateOfBirth: string;
gender: "male" | "female";
terms: boolean;
}The validators object
validators maps each formKey to a GValidator. The special
"*" entry is the default applied to every field that has no entry of its own, so a single
required rule can cover the whole form. Declare it at module level (or wrap it in useMemo) -
a new object on every render re-registers the fields.
import { GValidator, type GValidators } from "gform-react";
// Module-level: a stable reference, so fields don't re-register on every render.
const base = new GValidator().withRequiredMessage("This field is required");
const validators: GValidators<IRegisterForm> = {
"*": base, // default rule for every field
name: new GValidator(base).withMinLengthMessage("At least 2 characters"), // per-field override
};Pass validators by a stable reference - declare it at module level, or wrap it in useMemo.
A new object on every render re-registers the fields and defeats the optimization.
Validators carry native-constraint messages (withRequiredMessage, withMinLengthMessage, …),
custom rules, async rules, and whole-form schema rules. See
Constraint Validation and
Schema Validation for the patterns, and
GValidator for the full class reference.
The GForm component
GForm<T> is the container. It renders a real <form>, owns the form's state store, and registers
every GInput rendered beneath it. Each GInput declares a formKey that must be a key of
T; the formKey is also used as the input's name, which is how
Server Actions read values from FormData.
By default a GInput renders a fully-wired native <input>. To render anything else - custom
markup, a <select>, <textarea>, or a UI-library component - pass an element render prop;
it receives the field's live input state plus the props you spread onto your control. The
GInput page covers the rendering model and per-type value typing in
full.
GForm can take a function child that receives the live state - use it to read validity,
read values, or disable the submit button straight from form state:
<GForm<IRegisterForm> validators={validators} onSubmit={handleSubmit}>
{(state) => (
<>
<GInput formKey="name" required placeholder="Name" />
<GInput formKey="age" type="number" required min={18} />
{/* disable from form state - not manual value checks */}
<button disabled={state.isInvalid}>Create account</button>
</>
)}
</GForm>The state object is the single source of truth for the form: state.<formKey>.value,
state.isValid / state.isInvalid, and serializers like state.toRawData(). See
The Form State for turning state into a payload, and the
GForm page for every prop.
Two important guarantees
onSubmitonly fires when the form is valid. An invalid form never reaches your handler.- gform does not call
e.preventDefault()for you. This is deliberate - it's what enables native<form>submission and Next.js Server Actions. CallpreventDefault()yourself when you handle submission in JavaScript. (For Server Actions you useactioninstead and must notpreventDefault- see Server Actions.)
Next
- Your First Form - see all three pieces working together in a runnable form.
- Validation - native constraints, custom messages, and async rules.
- Disabling Submit - the
state.isInvalidpattern in detail.