CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-final-form

Framework-agnostic, high-performance form state management library with subscription-based updates.

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

validation.mddocs/

Validation

Comprehensive validation system supporting both synchronous and asynchronous validation at form and field levels with performance optimization and cross-field validation capabilities.

Capabilities

Form-Level Validation

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;
  }
});

Field-Level Validation

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';
      }
    }
  }
);

Validation Control

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');
}

Error Handling Patterns

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 }
);

Validation Performance

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
    )
  }
);

docs

field-registration.md

form-actions.md

form-creation.md

index.md

state-management.md

utilities.md

validation.md

tile.json