@matthew.ngo/reform
Version:
A flexible and powerful React form management library with advanced validation, state observation, and multi-group support
397 lines (321 loc) • 10.5 kB
Markdown
# Reform Validation
Reform provides a powerful validation system that helps you validate form data with multiple strategies. This document covers the validation capabilities of Reform.
## Table of Contents
- [Overview](#overview)
- [Yup Integration](#yup-integration)
- [Dynamic Validation](#dynamic-validation)
- [Context-Aware Validation](#context-aware-validation)
- [Field-Level Validation](#field-level-validation)
- [Group Validation](#group-validation)
- [Form Submission Validation](#form-submission-validation)
- [API Reference](#api-reference)
## Overview
Reform's validation system offers several validation strategies:
1. **Schema-based validation** with Yup
2. **Dynamic validation rules** based on field values
3. **Context-aware validation** that can access other form data
4. **Field-level validation** for individual fields
5. **Group validation** for validating entire form groups
6. **Form submission validation** for pre-submission checks
## Yup Integration
Reform provides seamless integration with Yup for schema-based validation.
### Basic Usage
```tsx
import { useReform } from '@reform/core';
import * as yup from 'yup';
// Define your validation schema
const userSchema = yup.object({
name: yup.string().required('Name is required'),
email: yup.string().email('Invalid email').required('Email is required'),
age: yup.number().min(18, 'Must be at least 18 years old'),
});
const MyForm = () => {
const form = useReform<UserForm>({
defaultData: { name: '', email: '', age: 0 },
minGroups: 1,
groupSchema: userSchema,
});
return (
<form onSubmit={form.formMethods.handleSubmit(onSubmit)}>
{/* Form fields */}
</form>
);
};
```
### Enhanced Yup Schema
Reform extends Yup's functionality with transformers and context:
```tsx
import { useReform } from '@reform/core';
import * as yup from 'yup';
const MyForm = () => {
const form = useReform<UserForm>(config);
// Create an enhanced schema with transformers and context
const enhancedSchema = form.yup.createEnhancedSchema(
yup.object({
birthDate: yup.date().required('Birth date is required'),
registrationDate: yup.date().min(
yup.ref('birthDate'),
'Registration date must be after birth date'
),
})
);
// Register a transformer for date fields
useEffect(() => {
const unregister = form.yup.registerTransformer({
field: 'birthDate',
transformer: (value) => value instanceof Date ? value : new Date(value),
transformOn: 'input'
});
return unregister;
}, []);
return (
<form>
{/* Form fields */}
</form>
);
};
```
## Dynamic Validation
Reform allows you to create dynamic validation rules that change based on form values.
```tsx
import { useReform, useDynamicValidation } from '@reform/core';
const MyForm = () => {
const form = useReform<UserForm>(config);
const dynamicValidation = useDynamicValidation(form, {
rules: [
{
field: 'password',
validate: (value, data) => {
if (!value) return 'Password is required';
if (value.length < 8) return 'Password must be at least 8 characters';
return null;
},
},
{
field: 'confirmPassword',
validate: (value, data, context) => {
if (value !== data.password) {
return 'Passwords do not match';
}
return null;
},
dependencies: ['password'],
},
],
});
return (
<form>
{/* Form fields */}
</form>
);
};
```
## Context-Aware Validation
Reform's validation system can access context data from other parts of the form:
```tsx
import { useReform } from '@reform/core';
import * as yup from 'yup';
const MyForm = () => {
const form = useReform<UserForm>(config);
// Update context data
useEffect(() => {
form.yup.updateContextData({
minAge: 18,
maxAge: 65,
currentDate: new Date(),
});
}, []);
// Create a schema that uses context
const schema = form.yup.createSchemaWithContext(
yup.object({
age: yup.number().test(
'age-range',
'Age must be between ${context.minAge} and ${context.maxAge}',
function(value) {
const { minAge, maxAge } = this.options.context;
return value >= minAge && value <= maxAge;
}
),
})
);
return (
<form>
{/* Form fields */}
</form>
);
};
```
## Field-Level Validation
Reform supports field-level validation through React Hook Form's validation rules:
```tsx
import { useReform } from '@reform/core';
const MyForm = () => {
const form = useReform<UserForm>(config);
return (
<form>
<input
{...form.register(0, 'email', {
required: 'Email is required',
pattern: {
value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
message: 'Invalid email address',
},
})}
placeholder="Email"
/>
{form.getFieldError(0, 'email') && (
<p>{form.getFieldError(0, 'email')}</p>
)}
</form>
);
};
```
## Group Validation
Reform provides utilities to validate entire form groups and check if they meet specific requirements:
```tsx
import { useReform } from '@reform/core';
const MyForm = () => {
const form = useReform<UserForm>(config);
const { isGroupValid, areAllGroupsValid } = form.errors;
// Define required fields for validation
const requiredFields: (keyof UserForm)[] = ['name', 'email', 'age'];
// Check if a specific group is valid
const isFirstGroupValid = isGroupValid(0, requiredFields);
// Check if all groups are valid
const allGroupsValid = areAllGroupsValid(requiredFields);
const handleAddGroup = () => {
// Only add a new group if all existing groups are valid
if (allGroupsValid) {
form.addGroup();
} else {
alert('Please complete all required fields in existing groups first');
}
};
return (
<form>
{/* Form fields */}
<button
type="button"
onClick={handleAddGroup}
disabled={!allGroupsValid}
>
Add Group
</button>
</form>
);
};
```
## Form Submission Validation
Reform provides utilities to check if a form is ready for submission:
```tsx
import { useReform } from '@reform/core';
const MyForm = () => {
const form = useReform<UserForm>(config);
const { isFormSubmittable } = form.errors;
// Define required fields for submission
const requiredFields: (keyof UserForm)[] = ['name', 'email', 'age'];
// Check if the form can be submitted
const canSubmit = isFormSubmittable(requiredFields);
const handleSubmit = (data: any) => {
console.log('Form submitted:', data);
};
return (
<form onSubmit={form.formMethods.handleSubmit(handleSubmit)}>
{/* Form fields */}
<button
type="submit"
disabled={!canSubmit}
>
Submit
</button>
{!canSubmit && (
<p className="error">
Please complete all required fields and fix any errors before submitting
</p>
)}
</form>
);
};
```
## API Reference
### useReformYupIntegration
```typescript
function useReformYupIntegration<T extends Record<string, any> & AnyObject>(
reform: ReformReturn<T>
): ReformYupIntegration<T>
```
#### Returns
Returns a `ReformYupIntegration<T>` object with the following methods:
| Method | Description |
|--------|-------------|
| createEnhancedSchema | Create a schema with transformers and context |
| validateWithContext | Validate data with context for a specific group |
| updateContextData | Update context data for both context and transformers |
| validateAllGroups | Validate all groups with the current schema |
| createSchemaWithContext | Create a schema with context support |
| createSchemaWithTransformers | Create a schema with transformer support |
| registerTransformer | Register a transformer for a specific field |
| unregisterTransformer | Unregister a transformer |
### useDynamicValidation
```typescript
function useDynamicValidation<T extends Record<string, any>>(
reform: ReformReturn<T>,
config: DynamicSchemaConfig<T>
): {
validateField(groupIndex: number, field: FieldPath<T>, value: any): ValidationRuleResult;
validateGroup(groupIndex: number): boolean;
getFieldRules(groupIndex: number, field: FieldPath<T>): DynamicValidationRule<T>[];
updateContext(newContextData: Record<string, any>): void;
}
```
#### Parameters
| Parameter | Type | Description |
|-----------|------|-------------|
| reform | `ReformReturn<T>` | Reform hook return value |
| config | `DynamicSchemaConfig<T>` | Configuration for dynamic validation |
#### Configuration Options
| Option | Type | Description |
|--------|------|-------------|
| rules | `DynamicValidationRule<T>[]` | Array of validation rules |
| context | `Record<string, any>` | Context data for validation |
### FormErrorsState
```typescript
interface FormErrorsState<T extends Record<string, any>> {
errors: Record<string, FieldError>;
hasErrors: boolean;
getFieldError: (groupIndex: number, fieldName: string) => string | undefined;
getGroupErrors: (index: number) => Record<string, string>;
getFieldErrors: (index: number) => Record<keyof T, string | undefined>;
clearErrors: () => void;
clearFieldError: (groupIndex: number, fieldName: string) => void;
clearGroupError: (groupIndex: number) => void;
clearGlobalError: () => void;
getError: (groupIndex: number, field: string) => string | undefined;
hasGroupErrors: (groupIndex: number) => boolean;
isGroupValid: (groupIndex: number, requiredFields: (keyof T)[]) => boolean;
areAllGroupsValid: (requiredFields: (keyof T)[]) => boolean;
isFormSubmittable: (requiredFields: (keyof T)[]) => boolean;
canAddNewGroup: (requiredFields: (keyof T)[]) => boolean;
}
```
### DynamicValidationRule
```typescript
interface DynamicValidationRule<T> {
field: FieldPath<T>;
validate: (
value: any,
data: T,
context: ValidationContext
) => string | null | Promise<string | null>;
dependencies?: Array<FieldPath<T>>;
runCondition?: (data: T, context: ValidationContext) => boolean;
}
```
### YupTransformer
```typescript
interface YupTransformer<T> {
field: FieldPath<T>;
transformer: (value: any) => any;
transformOn: 'input' | 'output' | 'both';
}
```