gform-react
Back to Introduction

Core Concepts

GForm, formKey, the GInput element render prop, and how form state works.

A gform-react form is made of three pieces:

  1. A typed interface describing your form - keys are field names, values are field value types.
  2. A validators object (kept at module level for a stable reference).
  3. GForm component wrapping one or more GInput fields.

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
};
Keep validators stable

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

  • onSubmit only 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. Call preventDefault() yourself when you handle submission in JavaScript. (For Server Actions you use action instead and must not preventDefault - see Server Actions.)

Next