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) => {
required(path.name);
minLength(path.name, 2);
required(path.email);
email(path.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>) => {
required(path.name); // β
Valid
required(path.email); // β
Valid
required(path.address.city); // β
Valid - nested access
required(path.phone); // β TypeScript error!
};
Benefitsβ
- Autocomplete β IDE shows available fields
- Compile-time checks β Catch typos early
- Refactoring support β Rename fields safely
Built-in Validatorsβ
| Validator | Description |
|---|---|
required(path.field) | Field must have value |
email(path.field) | Valid email format |
minLength(path.field, n) | Minimum string length |
maxLength(path.field, n) | Maximum string length |
min(path.field, n) | Minimum number |
max(path.field, n) | Maximum number |
pattern(path.field, regex) | Match regex |
See Built-in Validators for full list.
Conditional Validationβ
Apply validators based on conditions:
import { when } from '@reformer/core/validators';
validation: (path) => {
required(path.email);
// Only validate phone if user wants SMS
when(
() => form.controls.wantsSms.value === true,
() => {
required(path.phone);
pattern(path.phone, /^\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
required(path.customer.name);
email(path.customer.email);
// Array items (validates each item's template)
required(path.items.product);
min(path.items.quantity, 1);
};
Cross-Field Validationβ
Validate fields against each other:
import { custom } from '@reformer/core/validators';
validation: (path) => {
required(path.password);
required(path.confirmPassword);
custom(path.confirmPassword, (value, ctx) => {
const password = ctx.form.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) => {
required(path.username);
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>) {
required(path.firstName);
minLength(path.firstName, 2);
required(path.lastName);
required(path.email);
email(path.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