GValidator
The validator class - constructor inheritance, chainable (mutating) methods, constraint messages, custom and async rules, and dev-mode diagnostics.
GValidator builds the validation rules for one field. Instances live in the validators object
you pass to GForm, keyed by formKey:
import { GValidator, type GValidators } from "gform-react";
const base = new GValidator().withRequiredMessage("Required");
const validators: GValidators<SignUpForm> = {
"*": base, // default for every field without its own entry
password: new GValidator(base).withMinLengthMessage("At least 8 characters"),
};This page is the class reference. For validation recipes - cross-field rules, async UX, Yup/Zod integration - see Schema Libraries.
The validators object
GValidators<T> maps each formKey to a GValidator. Two special lookups:
"*"- the default validator, used by every field that has no entry of its own.- A field resolves its validator as
validators[validatorKey || formKey], falling back to"*"- sovalidatorKeyon aGInputcan point several dynamic fields at one shared entry.
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.
Constructor and inheritance
new GValidator(base) copies the base validator's rules, so per-field validators can extend
a default instead of redefining it:
const base = new GValidator().withRequiredMessage("Required");
const validators: GValidators<SignUpForm> = {
"*": base,
password: new GValidator(base) // inherits the required rule…
.withMinLengthMessage("At least 8 characters"), // …and adds one
};The copy is a snapshot taken at construction: rules added to base after
new GValidator(base) do not propagate to the derived validator. Build base validators
completely before deriving from them.
Chainable methods mutate the instance
Every with… method adds the rule to the instance it's called on and returns that same
instance - chaining is mutation, not copying. Calling a with… method on a shared validator
silently adds the rule to every field using it:
const base = new GValidator().withRequiredMessage("Required");
// ❌ mutates `base` - now EVERY "*" field has the minLength message
const validators: GValidators<SignUpForm> = {
"*": base,
password: base.withMinLengthMessage("At least 8 characters"),
};
// ✅ extend a copy
const validators: GValidators<SignUpForm> = {
"*": base,
password: new GValidator(base).withMinLengthMessage("At least 8 characters"),
};Constraint message methods
Each method registers the error message for one native
constraint-validation
violation. The constraint itself goes on the GInput; the validator supplies the message when
the browser reports that violation:
| Method | Pairs with GInput prop | Validity violation |
|---|---|---|
withRequiredMessage | required | valueMissing |
withMinLengthMessage | minLength | tooShort |
withMaxLengthMessage | maxLength | tooLong |
withPatternMismatchMessage | pattern | patternMismatch |
withRangeUnderflowMessage | min | rangeUnderflow |
withRangeOverflowMessage | max | rangeOverflow |
withStepMismatchMessage | step | stepMismatch |
withTypeMismatchMessage | type (email/url/…) | typeMismatch |
withBadInputMessage | - | badInput |
Every method accepts a string or a function (input: GInputState) => string, which
receives the full field state - useful for interpolating the constraint value or the key:
new GValidator()
.withRequiredMessage((input) => `${input.formKey} is required`)
.withMinLengthMessage((input) => `At least ${input.minLength} characters`);Custom rules: withCustomValidation
For anything the native API can't express. The handler receives the current input and all
fields, and follows an inverted contract:
new GValidator().withCustomValidation((input, fields) => {
if (input.value !== fields.password.value) {
input.errorText = "Passwords do not match"; // you set the message
return true; // ⚠️ true = ERROR
}
return false; // valid
});- Return
trueto signal an error - and setinput.errorTextyourself. - Return
false(or nothing) when the field is valid. - Returning a
RegExpor pattern string means "valid only if the value matches it".
Async rules: withCustomValidationAsync
Same contract, but the handler returns a Promise - for server-side checks like "is this
username taken?". Async runs are debounced per field (default 300 ms, configurable via
the debounce prop on GInput).
new GValidator().withCustomValidationAsync(async (input) => {
const taken = await checkUsernameTaken(input.value as string);
if (taken) {
input.errorText = "That username is taken";
return true;
}
return false;
});While an async check is in flight, the field is held in the error state (with an empty
errorText), so state.isInvalid stays true and a submit can't slip through mid-check. The
real result replaces it when the Promise resolves.
Execution order
When a field validates, its rules run in this order, stopping at the first failure - a field reports one error at a time:
- Constraint handlers, in registration order.
- Custom sync handlers, in registration order.
- Async handlers (debounced), sequentially - and only if everything synchronous passed.
Development warnings
In development builds, gform cross-checks validators against inputs and warns about mismatches (all of this is stripped from production builds):
- Duplicate Handlers - the same violation registered twice on one validator. This includes re-registering a violation the base validator already handles, since inherited rules count.
- Missing Prop - the validator has a message for a violation, but the input never declared
the matching constraint (e.g.
withMinLengthMessagewithoutminLengthon theGInput). - Missing Validator - the input declared a constraint, but no matching message method was registered, so a violation would block submission with no visible message.
Typing
GValidator<T> is generic over the form interface, typing the input/fields your handlers
receive. The map type ties it together:
type GValidators<T> = { [key in keyof T]?: GValidator<T> } & {
[key: string]: GValidator<T> | undefined; // "*" default + dynamic keys
};Try it live
A "*" base validator with a dynamic message, extended (correctly - via new GValidator(base))
for the password field. Submit empty to see the interpolated messages: