Validation Schema
Validation Schema defines rules for validating form data.
ValidationSchemaFn Typeβ
type ValidationSchemaFn<T> = (path: FieldPath<T>) => void;
The validation function receives a type-safe path object for declaring validation rules:
import { GroupNode } from '@reformer/core';
import { required, email, minLength } from '@reformer/core/validators';
const form = new GroupNode({
form: {
name: { value: '' },
email: { value: '' },
},
validation: (path) => {
validate(path.name, required());
validate(path.name, minLength(2));
validate(path.email, required());
validate(path.email, email());
},
});
FieldPath β Type-Safe Pathsβ
FieldPath<T> provides type-safe access to form fields:
interface User {
name: string;
email: string;
address: {
city: string;
zip: string;
};
}
validation: (path: FieldPath<User>) => {
validate(path.name, required()); // β
Valid
validate(path.email, required()); // β
Valid
validate(path.address.city, required()); // β
Valid - nested access
validate(path.phone, required()); // β TypeScript error!
};
Benefitsβ
- Autocomplete β IDE shows available fields
- Compile-time checks β Catch typos early
- Refactoring support β Rename fields safely
Built-in Validatorsβ
| Validator | Description |
|---|---|
validate(path.field, required()) | Field must have value |
validate(path.field, email()) | Valid email format |
validate(path.field, minLength(n)) | Minimum string length |
validate(path.field, maxLength(n)) | Maximum string length |
validate(path.field, min(n)) | Minimum number |
validate(path.field, max(n)) | Maximum number |
validate(path.field, pattern(regex)) | Match regex |
See Built-in Validators for full list.
Conditional Validationβ
Apply validators based on conditions:
import { applyWhen, validate, required, pattern } from '@reformer/core/validators';
validation: (path) => {
validate(path.email, required());
// Only validate phone if user wants SMS
applyWhen(
path.wantsSms,
(wantsSms) => wantsSms === true,
(path) => {
validate(path.phone, required());
validate(path.phone, pattern(/^\d{10}$/));
}
);
};
Nested Validationβ
Validate nested objects and arrays:
interface Order {
customer: {
name: string;
email: string;
};
items: Array<{
product: string;
quantity: number;
}>;
}
validation: (path) => {
// Nested object
validate(path.customer.name, required());
validate(path.customer.email, email());
// Array items (validates each item's template)
validate(path.items.product, required());
validate(path.items.quantity, min(1));
};
Cross-Field Validationβ
Validate fields against each other:
import { custom } from '@reformer/core/validators';
validation: (path) => {
validate(path.password, required());
validate(path.confirmPassword, required());
custom(path.confirmPassword, (value, _control, root) => {
const password = root.password.value;
if (value !== password) {
return { match: 'Passwords must match' };
}
return null;
});
};
Async Validationβ
Server-side validation:
import { asyncValidator } from '@reformer/core/validators';
validation: (path) => {
validate(path.username, required());
asyncValidator(path.username, async (value) => {
const exists = await checkUsername(value);
if (exists) {
return { taken: 'Username already taken' };
}
return null;
});
};
See Async Validation for details.
Extracting Validation Setsβ
Create reusable validation functions:
import { FieldPath } from '@reformer/core';
import { required, email, minLength } from '@reformer/core/validators';
// Reusable validation set
export function validatePerson(path: FieldPath<Person>) {
validate(path.firstName, required());
validate(path.firstName, minLength(2));
validate(path.lastName, required());
validate(path.email, required());
validate(path.email, email());
}
// Usage
const form = new GroupNode({
form: {
user: personSchema(),
admin: personSchema(),
},
validation: (path) => {
validatePerson(path.user);
validatePerson(path.admin);
},
});
Next Stepsβ
- Validation Overview β Detailed validation guide
- Built-in Validators β All validators
- Custom Validators β Create your own
- Composition β Reuse validation sets