How Formily's Validation System Works: Trigger Types and Implementation
Formily's validation system executes validator rules based on trigger types—onInput, onFocus, onBlur, or custom strings—by matching the trigger parameter in validateSelf against each rule's triggerType property.
The Formily validation system, as implemented in the alibaba/formily repository, provides a flexible architecture for executing field-level and form-level validation based on user interaction patterns. Understanding how trigger types control when validation runs is essential for building responsive forms that provide timely feedback without overwhelming users.
Core Architecture of Formily's Validation System
Formily's validation pipeline follows a three-stage process: descriptor parsing, rule normalization, and trigger-based execution. This architecture separates the definition of validation rules from their execution context.
Validator Descriptor Parsing
Validation begins with validator descriptors that are normalized by the parser utilities in packages/validator/src/parser.ts. The parseValidatorDescription and parseValidatorDescriptions functions (lines 21-44) transform string, function, or object descriptors into standardized IValidatorRules objects. Each rule object may optionally include a triggerType field that specifies when the validation should execute.
Rule-to-Function Conversion
Once parsed, parseValidatorRules (lines 46-70) walks through the rule keys and retrieves built-in validator functions from the internal registry. Each validator is wrapped with a consistent asynchronous wrapper (createValidate) that normalizes return values into IValidateResult objects containing error messages, warnings, or success states. This wrapping ensures that all validators—whether synchronous or asynchronous—return a consistent Promise-based interface.
How Trigger Types Control Validation Execution
The execution of validation rules is orchestrated by the validateSelf function in packages/core/src/shared/internals.ts. This function implements the trigger-matching logic that determines which validators run during any given validation cycle.
The Trigger Type Definition
Trigger types are defined as a TypeScript union type in packages/validator/src/types.ts (lines 49-53). The system recognizes the following standard trigger types:
onInput– Validates whenever the field value changes (default behavior)onFocus– Validates when the field receives focusonBlur– Validates when the field loses focus- Custom strings – Any user-defined string value (e.g.,
onSubmit,onChange) for application-specific validation timing
validateSelf Implementation Details
The validateSelf function implements two distinct execution modes based on the presence of a triggerType argument:
-
Auto-collection mode (lines 62-70): When called without an explicit trigger, the function gathers all unique trigger types present in the field's validator list and executes validation for each type sequentially.
-
Specific trigger mode (lines 82-84): When a specific
triggerTypeis supplied (e.g.,'onBlur'), only validators whosetriggerTypeproperty matches the supplied value execute.
Field Lifecycle Integration
Field components in packages/core/src/models/Field.ts automatically invoke validateSelf through lifecycle hooks that map to DOM events:
onInput(lines 84-85): Called on every value changeonFocus(lines 88-94): Called when the input receives focusonBlur(lines 97-102): Called when the input loses focus
Each method calls validateSelf(this, '<event>'), passing the field instance and the trigger type string that corresponds to the interaction.
Supported Trigger Types in Formily
Formily supports four categories of validation triggers that provide granular control over when feedback appears:
onInput (Default)
Validation runs continuously as the user types. This is the default behavior when no triggerType is specified in a validator rule, providing immediate feedback but potentially showing errors while the user is still entering data.
onFocus
Validation executes when the user clicks or tabs into a field. This trigger is useful for displaying contextual help or pre-validation warnings before the user begins typing.
onBlur
Validation runs when the user leaves the field. This pattern reduces noise during typing while ensuring data quality before the user moves to the next field.
Custom Triggers
Any string value can be used as a trigger type. Developers define custom triggers like onSubmit or onStepChange and manually invoke form.validate({ triggerType: 'customName' }) to execute specific validation sets on demand.
Practical Implementation Examples
Basic Field with Default onInput Validation
The following example demonstrates the default behavior where validation runs on every keystroke:
import { createForm, FormProvider, Field } from '@formily/react';
const form = createForm();
<FormProvider form={form}>
<Field
name="email"
validator={{
format: 'email',
required: true,
// triggerType omitted → defaults to 'onInput'
}}
>
{(field) => (
<input
value={field.value}
onChange={field.onInput}
/>
)}
</Field>
</FormProvider>
Delayed Validation with onBlur
To validate only when the user finishes editing, specify triggerType: 'onBlur':
<Field
name="username"
validator={{
validator: (value) => value?.length >= 4 ? '' : 'Username must be at least 4 characters',
triggerType: 'onBlur',
}}
>
{(field) => (
<>
<input
value={field.value}
onChange={field.onInput}
onBlur={field.onBlur}
/>
{field.errors?.map(err => <span key={err} className="error">{err}</span>)}
</>
)}
</Field>
Form-Level Validation with Specific Triggers
The batchValidate function in packages/core/src/shared/internals.ts supports trigger propagation from form-level calls:
// Validate only onBlur rules across all fields
await form.validate({ triggerType: 'onBlur' });
Implementing Custom Trigger Types
Define application-specific validation timing using custom strings:
const secretRule = {
validator: (value) => value === 'secret-token' ? '' : 'Invalid access token',
triggerType: 'onVerify',
};
// Later in your component
await form.validate({ triggerType: 'onVerify' });
Summary
- Formily's validation system uses
parseValidatorDescriptionsinpackages/validator/src/parser.tsto normalize validator descriptors into executable rules. - Trigger types are defined in
packages/validator/src/types.tsasonInput,onFocus,onBlur, or custom string values. validateSelfinpackages/core/src/shared/internals.tsfilters validators by matching the supplied trigger against each rule'striggerTypeproperty.- Field lifecycle methods (
onInput,onFocus,onBlur) inpackages/core/src/models/Field.tsautomatically invoke validation with their respective trigger types. - Form-level validation via
form.validate()supports trigger filtering, allowing batch execution of specific validation sets.
Frequently Asked Questions
What is the default trigger type in Formily?
The default trigger type is onInput. When you define a validator without explicitly specifying a triggerType property, Formily executes that validator whenever the field value changes, corresponding to the onInput event.
How do I run validation only on specific events?
Pass a triggerType option to form.validate() or define the triggerType property in your validator rules. For example, await form.validate({ triggerType: 'onBlur' }) executes only validators configured with triggerType: 'onBlur', while setting triggerType: 'onBlur' in a field validator ensures that rule only runs when the field loses focus.
Can I define custom trigger types in Formily?
Yes, Formily accepts any string value as a triggerType. The ValidatorTriggerType in packages/validator/src/types.ts is an open union type that includes onInput, onFocus, and onBlur, but you can pass custom strings like 'onSubmit' or 'onSave'. You must manually invoke form.validate({ triggerType: 'yourCustomTrigger' }) to execute validators using custom triggers.
Where does Formily store the validation logic for fields?
Validation logic is stored in the field's validator property as normalized rules. The actual execution logic resides in packages/core/src/shared/internals.ts (validateSelf and batchValidate functions), while the parsing and rule creation utilities are located in packages/validator/src/parser.ts. The field models in packages/core/src/models/Field.ts provide the lifecycle hooks that initiate validation based on user interactions.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →