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 need | Reach 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 between | useFormSelector |
| Reading current state in callbacks/effects without subscribing at all | GForm'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 instate.fieldswhen itsGInputmounts and leaves when it unmounts, so the slice may beundefined(especially with dynamic fields). -
Don't return a fresh object from the selector. A new object fails the
Object.ischeck 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);
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.)