Framework-agnostic, high-performance form state management library with subscription-based updates.
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Comprehensive validation system supporting both synchronous and asynchronous validation at form and field levels with performance optimization and cross-field validation capabilities.
Form-wide validation that validates all form values together.
interface Config<FormValues, InitialFormValues> {
/** Form-level validation function */
validate?: (values: FormValues) => ValidationErrors | Promise<ValidationErrors>;
/** Whether to validate fields on blur event (default: false) */
validateOnBlur?: boolean;
}
type ValidationErrors = AnyObject | undefined;
type AnyObject = { [key: string]: any };Usage Examples:
import { createForm, FORM_ERROR } from "final-form";
// Synchronous form validation
const form = createForm({
onSubmit: (values) => console.log(values),
validate: (values) => {
const errors = {};
// Required field validation
if (!values.email) {
errors.email = 'Email is required';
}
// Email format validation
if (values.email && !/\S+@\S+\.\S+/.test(values.email)) {
errors.email = 'Invalid email format';
}
// Cross-field validation
if (values.password !== values.confirmPassword) {
errors.confirmPassword = 'Passwords must match';
}
// Form-level error
if (values.username === 'admin' && values.password === 'admin') {
errors[FORM_ERROR] = 'Invalid credentials combination';
}
return errors;
}
});
// Asynchronous form validation
const asyncForm = createForm({
onSubmit: async (values) => await submitUser(values),
validate: async (values) => {
const errors = {};
// Async username availability check
if (values.username) {
try {
const isAvailable = await checkUsernameAvailability(values.username);
if (!isAvailable) {
errors.username = 'Username is already taken';
}
} catch (error) {
errors.username = 'Unable to verify username availability';
}
}
// Async email validation
if (values.email) {
const isValidDomain = await validateEmailDomain(values.email);
if (!isValidDomain) {
errors.email = 'Email domain is not allowed';
}
}
return errors;
}
});
// Validation on blur
const blurValidationForm = createForm({
onSubmit: (values) => console.log(values),
validateOnBlur: true, // Validate fields when they lose focus
validate: (values) => {
const errors = {};
if (values.name && values.name.length < 2) {
errors.name = 'Name must be at least 2 characters';
}
return errors;
}
});Individual field validation with support for cross-field dependencies.
interface FieldConfig<FieldValue = any> {
/** Function that returns a validator for this field */
getValidator?: GetFieldValidator<FieldValue>;
/** Names of other fields to validate when this field changes */
validateFields?: string[];
/** Whether field validation is asynchronous */
async?: boolean;
}
type GetFieldValidator<FieldValue = any> = () => FieldValidator<FieldValue> | undefined;
type FieldValidator<FieldValue = any> = (
value: FieldValue,
allValues: object,
meta?: FieldState<FieldValue>
) => any | Promise<any>;Usage Examples:
// Basic field validation
form.registerField(
'email',
(fieldState) => updateEmailField(fieldState),
{ value: true, error: true },
{
getValidator: () => (value, allValues) => {
if (!value) return 'Email is required';
if (!/\S+@\S+\.\S+/.test(value)) return 'Invalid email format';
// Access other form values for cross-field validation
if (allValues.confirmEmail && value !== allValues.confirmEmail) {
return 'Email addresses must match';
}
}
}
);
// Async field validation
form.registerField(
'username',
(fieldState) => updateUsernameField(fieldState),
{ value: true, error: true, validating: true },
{
async: true,
getValidator: () => async (value) => {
if (!value) return 'Username is required';
if (value.length < 3) return 'Username must be at least 3 characters';
// Async validation
try {
const isAvailable = await checkUsernameAvailability(value);
if (!isAvailable) {
return 'Username is already taken';
}
} catch (error) {
return 'Unable to validate username';
}
}
}
);
// Cross-field validation with validateFields
form.registerField(
'password',
(fieldState) => updatePasswordField(fieldState),
{ value: true, error: true },
{
validateFields: ['confirmPassword'], // Re-validate confirmPassword when password changes
getValidator: () => (value) => {
if (!value) return 'Password is required';
if (value.length < 8) return 'Password must be at least 8 characters';
if (!/(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/.test(value)) {
return 'Password must contain uppercase, lowercase, and number';
}
}
}
);
form.registerField(
'confirmPassword',
(fieldState) => updateConfirmPasswordField(fieldState),
{ value: true, error: true },
{
getValidator: () => (value, allValues) => {
if (!value) return 'Please confirm your password';
if (value !== allValues.password) {
return 'Passwords must match';
}
}
}
);Methods for controlling validation behavior programmatically.
interface FormApi<FormValues, InitialFormValues> {
/** Check if validation is currently paused */
isValidationPaused(): boolean;
/** Pause all validation */
pauseValidation(): void;
/** Resume validation */
resumeValidation(): void;
}Usage Examples:
// Pause validation during bulk updates
form.pauseValidation();
form.batch(() => {
form.change('field1', 'value1');
form.change('field2', 'value2');
form.change('field3', 'value3');
});
form.resumeValidation(); // Validation will run once when resumed
// Conditional validation pausing
if (isImportingData) {
form.pauseValidation();
await importFormData();
form.resumeValidation();
}
// Check validation state
if (form.isValidationPaused()) {
console.log('Validation is currently paused');
}Common patterns for handling validation errors.
// Special error constants
const FORM_ERROR: string;
const ARRAY_ERROR: string;Error Handling Examples:
import { createForm, FORM_ERROR, ARRAY_ERROR } from "final-form";
// Form-level errors
const form = createForm({
onSubmit: async (values) => {
try {
await submitData(values);
} catch (error) {
// Return form-level error
return { [FORM_ERROR]: 'Submission failed. Please try again.' };
}
},
validate: (values) => {
const errors = {};
// Field-specific errors
if (!values.name) {
errors.name = 'Name is required';
}
// Form-level error for business logic
if (values.age < 18 && values.requiresParentalConsent !== true) {
errors[FORM_ERROR] = 'Parental consent required for users under 18';
}
return errors;
}
});
// Array field errors
const arrayForm = createForm({
onSubmit: (values) => console.log(values),
validate: (values) => {
const errors = {};
if (values.items && Array.isArray(values.items)) {
const itemErrors = values.items.map((item, index) => {
const itemError = {};
if (!item.name) {
itemError.name = 'Item name is required';
}
if (!item.price || item.price <= 0) {
itemError.price = 'Price must be greater than 0';
}
return Object.keys(itemError).length > 0 ? itemError : undefined;
});
// Check if array has any errors
if (itemErrors.some(error => error)) {
errors.items = itemErrors;
}
// Array-level error
if (values.items.length === 0) {
errors.items = { [ARRAY_ERROR]: 'At least one item is required' };
}
}
return errors;
}
});
// Error display patterns
form.subscribe(
(formState) => {
// Display form-level errors
if (formState.error) {
showFormError(formState.error);
}
// Display field-level errors
if (formState.errors) {
Object.keys(formState.errors).forEach(fieldName => {
if (fieldName !== FORM_ERROR) {
showFieldError(fieldName, formState.errors[fieldName]);
}
});
}
},
{ error: true, errors: true }
);Best practices for optimizing validation performance.
// ✅ Good: Memoized validators
const createEmailValidator = () => {
const emailRegex = /\S+@\S+\.\S+/;
return (value) => {
if (!value) return 'Email is required';
if (!emailRegex.test(value)) return 'Invalid email format';
};
};
form.registerField(
'email',
(fieldState) => updateEmailField(fieldState),
{ value: true, error: true },
{ getValidator: createEmailValidator }
);
// ✅ Good: Debounced async validation
const createAsyncValidator = () => {
let timeoutId;
return async (value) => {
if (!value) return 'Username is required';
// Debounce async validation
return new Promise((resolve) => {
clearTimeout(timeoutId);
timeoutId = setTimeout(async () => {
try {
const isAvailable = await checkUsernameAvailability(value);
resolve(isAvailable ? undefined : 'Username taken');
} catch (error) {
resolve('Validation error');
}
}, 300);
});
};
};
// ❌ Bad: Creating new validators on every render
form.registerField(
'email',
(fieldState) => updateEmailField(fieldState),
{ value: true, error: true },
{
getValidator: () => (value) => { // New function every time
if (!value) return 'Email is required';
return /\S+@\S+\.\S+/.test(value) ? undefined : 'Invalid email';
}
}
);
// ✅ Good: Conditional validation
const createConditionalValidator = (condition) => {
return (value, allValues) => {
if (!condition(allValues)) {
return undefined; // Skip validation if condition not met
}
if (!value) return 'This field is required';
// ... other validation logic
};
};
form.registerField(
'conditionalField',
(fieldState) => updateField(fieldState),
{ value: true, error: true },
{
getValidator: () => createConditionalValidator(
(values) => values.enableConditionalField === true
)
}
);