gform-react
Back to Components

GInput

The field component - default rendering, the element render prop, per-type values, fetch, and field lifecycle.

GInput declares one field of the form. Its formKey identifies the field everywhere: it names the underlying control (name={formKey}), keys the field's slice of form state (state.<formKey>), and selects its validator. GInput is memoized and subscribes only to its own slice, so typing in one field re-renders that field alone - see Core Concepts for the full model.

import { GInput } from "gform-react";
 
<GInput formKey="email" type="email" required placeholder="you@example.com" />;

Rendering

The default: a native input

When you don't pass element, GInput renders a native <input>, fully wired - value, handlers, name, and aria attributes are all applied for you. For plain text-ish fields that's all you need:

<GInput formKey="name" required placeholder="Name" />

The element render prop

To render anything else - custom markup, a <select>, a <textarea>, or a component from MUI, PrimeReact, etc. - pass an element function. It receives:

  • input - the field's live GInputState (value, error, errorText, formKey, …).
  • props - the props to spread onto your control: gform's wiring plus every pass-through prop you set on GInput.
<GInput
  formKey="email"
  type="email"
  required
  element={(input, props) => (
    <label>
      <span>Email</span>
      <input {...props} />
      {input.error && <small>{input.errorText}</small>}
    </label>
  )}
/>

props is typed as GElementProps - the intersection of <input>, <select>, and <textarea> attributes - so it spreads onto any of them without a cast. The element render prop section of Core Concepts walks through it in depth, including which props pass through.

Props

PropTypeDescription
formKeystringRequired. Field key; also the control's name.
element(input: GInputState, props: GElementProps) => ReactNodeRender prop. Omit it to render a native <input>.
typeHTMLInputTypeDrives the value type (below). Default "text".
valuedepends on typeInitial value - seeds form state and is validated on mount.
defaultValuestring | numberUncontrolled initial value.
requiredbooleanNative required constraint.
validatorKeystringLook up another key's validator instead of formKey.
minLength / maxLengthnumberLength constraints (text types).
patternstringRegex constraint (text types).
min / max / stepnumberRange constraints (number/date types).
multiplebooleantype="file" only - stores File[].
debouncenumberDebounce (ms) for async validation and fetch. Default 300.
fetch(input, fields) => void | Partial | Promise<>Populate the field asynchronously (below).
fetchDepsstring[]Field keys whose value changes re-trigger fetch.

Everything else you put on GInput - placeholder, autoComplete, className, id, event handlers, … - passes through to the rendered control (directly onto the default <input>, or via the props object handed to element). Keep a field's whole configuration on GInput.

type selects the value type

GInputProps is a discriminated union on type: it constrains value, the type-specific constraint props, and the input/props your element receives.

typevalue typeType-specific props
text, password, email, tel, url, searchstringminLength, maxLength, pattern
checkboxbooleanchecked, defaultChecked
number, rangenumbermin, max, step
date, time, week, month, datetime-localstringmin, max, step
colorstring-
fileFile | File[] | nullmultiple

So with type="number", input.value is a number; with type="checkbox" the checked state lives in input.value as a boolean; and so on.

What gform wires for you

A few props on the rendered control are managed by gform and can't be overridden:

  • name is always the formKey - this is how FormData and Server Actions find the field.
  • value / checked are controlled by form state (type="file" is the exception - file inputs stay uncontrolled).
  • ref, aria-invalid, and aria-required are injected.
  • title falls back to the current errorText, so hovering an invalid control shows its error message - pass your own title to override.

Handlers are chained, not replaced: an onChange, onBlur, or onInvalid you pass to GInput runs after gform's own handler. gform also calls preventDefault() on invalid events to suppress the browser's default validation tooltip in favor of input.errorText.

Initial values

value seeds the field's state. A field that mounts with a value is validated immediately - constraint errors render on the first paint and an invalid initial value blocks submission, with custom/async validators running right after mount:

<GInput
  formKey="status"
  value="on-track"
  element={(input, props) => (
    <select {...props}>
      <option value="on-track">On track</option>
      <option value="ahead">Ahead</option>
    </select>
  )}
/>

Loading values asynchronously: fetch and fetchDeps

fetch(input, fields) runs once after the field mounts, and again - debounced by debounce - whenever the value of any key listed in fetchDeps changes. Return a partial field state (or a Promise of one) and gform dispatches it:

<GInput
  formKey="city"
  fetchDeps={["zip"]}
  fetch={async (input, fields) => {
    const zip = fields.zip?.value;
    if (!zip) return;
    return { value: await lookupCity(zip) };
  }}
  element={(input, props) => <input {...props} />}
/>

To seed the whole form in one go, prefer GForm's onInit.

Reusing validators: validatorKey

A field resolves its validator as validators[validatorKey || formKey], falling back to the "*" entry. validatorKey is handy when formKeys are dynamic but share one rule set:

const validators: GValidators = {
  option: new GValidator().withRequiredMessage("Required"),
};
 
{ids.map((id) => (
  <GInput key={id} formKey={`option-${id}`} validatorKey="option" required />
))}

See Constraint Validation for validation patterns and GValidator for the full class reference.

Lifecycle

  • A field registers on its first render. Two mounted GInputs with the same formKey are not allowed - gform warns in development and ignores the duplicate.
  • A field unregisters on unmount: its value leaves form state and overall validity is recalculated. Remounting registers it fresh. This is what makes dynamic fields work without bookkeeping.

File inputs

type="file" stores the real File (or File[] with multiple) in form state - not the C:\fakepath\… string - and toFormData() includes the files. File inputs stay uncontrolled at the DOM level; gform syncs the selection for you. See the File Inputs guide.

React Native

On React Native use RNGInput from gform-react/native: same formKey model, but element receives TextInputProps and the default render is a TextInput. See React Native.

Try it live

Three rendering styles side by side: the default native input, a custom element with inline errors, and a <select>:

Loading playground…