gform-react
Back to Hooks

useFormSelector

Subscribe any component inside a GForm to a slice of form state - it re-renders only when that slice changes.

useFormSelector subscribes a component to gform's external form store - the same store GInput uses internally for its per-field re-render isolation. You pass a selector; the component re-renders only when the selected value changes (compared with Object.is):

import { useFormSelector } from "gform-react";
 
function TitlePreview() {
  const title = useFormSelector((state) => state.fields.title?.value);
  return <h2>{title || "Untitled"}</h2>;
}

The selector receives the store state: { fields: { [formKey]: GInputState } }. Note this is the raw field map, not the GFormState the GForm render prop receives - there's no toRawData() or isValid on it (you can derive validity yourself).

The hook must be called from a component rendered inside a <GForm>; outside one it throws.

When to reach for it

You needReach for
Live form state right next to your fields (e.g. disabled={state.isInvalid})GForm's render prop
One field's state, deep in the tree, without re-rendering everything betweenuseFormSelector
Reading current state in callbacks/effects without subscribing at allGForm's stateRef

The render prop re-runs on every field change - fine for a submit button, wasteful for a deep component that mirrors a single field. useFormSelector lets that component subscribe narrowly and lets you keep it as a static child of GForm, so nothing else re-renders on its behalf.

Selecting state

Select a single value:

const title = useFormSelector((s) => s.fields.title?.value);

Or a whole field - field-state objects are replaced immutably, so this re-renders exactly when that field changes:

const email = useFormSelector((s) => s.fields.email);
// email?.value, email?.error, email?.errorText, …

Selectors can also derive values, as long as the result is a primitive. For example, whole-form validity (the same rule state.isValid uses - no field currently has an error):

const isInvalid = useFormSelector((s) =>
  Object.values(s.fields).some((f) => f.error)
);

Rules

  • Guard with ?. - a field appears in state.fields when its GInput mounts and leaves when it unmounts, so the slice may be undefined (especially with dynamic fields).

  • Don't return a fresh object from the selector. A new object fails the Object.is check on every store change, defeating the isolation:

    // ❌ new object on every check - re-renders on every form change
    const slice = useFormSelector((s) => ({ title: s.fields.title?.value }));
     
    // ✅ two narrow selectors
    const title = useFormSelector((s) => s.fields.title?.value);
    const city = useFormSelector((s) => s.fields.city?.value);
This is GInput's own primitive

Internally, GInput is memo + useFormSelector((s) => s.fields[formKey]) - that's why a keystroke re-renders only the field being typed in. useFormSelector hands the same primitive to your components.

Try it live

NameBadge subscribes to the name field only. Type in Notes and its render counter stays frozen; type in Name and it updates. (The sandbox entry renders without StrictMode so the counter counts each render once.)

Loading playground…