or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

field-registration.mdform-actions.mdform-creation.mdindex.mdstate-management.mdutilities.mdvalidation.md
tile.json

field-registration.mddocs/

Field Registration

Field registration and management system for dynamic form field handling with subscription capabilities and lifecycle management.

Capabilities

Register Field

Register a field with the form and subscribe to its state changes.

type RegisterField<FormValues = Record<string, any>> = <F extends keyof FormValues>(
  name: F,
  subscriber: FieldSubscriber<FormValues[F]>,
  subscription: FieldSubscription,
  config?: FieldConfig<FormValues[F]>
) => Unsubscribe;

interface FormApi<FormValues, InitialFormValues> {
  /** Register a field with subscription and optional configuration */
  registerField: RegisterField<FormValues>;
}

Usage Examples:

import { createForm } from "final-form";

const form = createForm({
  onSubmit: (values) => console.log(values)
});

// Basic field registration
const unsubscribe = form.registerField(
  'firstName',
  (fieldState) => {
    console.log('First name:', fieldState.value);
    console.log('Has error:', fieldState.error);
    console.log('Is touched:', fieldState.touched);
  },
  { value: true, error: true, touched: true }
);

// Field with configuration
const unsubscribeEmail = form.registerField(
  'email',
  (fieldState) => {
    updateEmailInput(fieldState);
  },
  { value: true, error: true, touched: true, valid: true },
  {
    initialValue: '',
    validateFields: ['confirmEmail'], // Validate confirmEmail when email changes
    getValidator: () => (value) => {
      if (!value) return 'Email is required';
      if (!/\S+@\S+\.\S+/.test(value)) return 'Invalid email format';
    }
  }
);

// Cleanup when component unmounts
return () => {
  unsubscribe();
  unsubscribeEmail();
};

Field State Access

Methods for accessing field state without subscribing to changes.

interface FormApi<FormValues, InitialFormValues> {
  /** Get current state of a specific field */
  getFieldState<F extends keyof FormValues>(
    field: F
  ): FieldState<FormValues[F]> | undefined;
  
  /** Subscribe to field state changes with callback approach */
  subscribeFieldState<F extends keyof FormValues>(
    name: F,
    onChange: () => void,
    subscription: FieldSubscription
  ): Unsubscribe;
  
  /** Get a snapshot of current field state */
  getFieldSnapshot<F extends keyof FormValues>(
    name: F
  ): FieldState<FormValues[F]> | undefined;
  
  /** Get names of all currently registered fields */
  getRegisteredFields(): string[];
}

Usage Examples:

// Get current field state
const emailState = form.getFieldState('email');
if (emailState?.error) {
  showFieldError('email', emailState.error);
}

// Callback-based field subscription
const unsubscribeCallback = form.subscribeFieldState(
  'firstName',
  () => {
    const snapshot = form.getFieldSnapshot('firstName');
    console.log('First name changed:', snapshot?.value);
  },
  { value: true }
);

// Get all registered fields
const registeredFields = form.getRegisteredFields();
console.log('Registered fields:', registeredFields);

Field State Interface

Complete field state object with all available properties and methods.

interface FieldState<FieldValue = any> {
  /** Whether field is currently active (focused) */
  active?: boolean;
  
  /** Function to blur the field */
  blur: () => void;
  
  /** Function to change the field value */
  change: (value: FieldValue | undefined) => void;
  
  /** Custom data attached to the field */
  data?: AnyObject;
  
  /** Whether field value differs from initial value */
  dirty?: boolean;
  
  /** Whether field has been modified since last submission */
  dirtySinceLastSubmit?: boolean;
  
  /** Field validation error */
  error?: any;
  
  /** Function to focus the field */
  focus: () => void;
  
  /** Initial value of the field */
  initial?: FieldValue;
  
  /** Whether field has validation errors */
  invalid?: boolean;
  
  /** Array length (for array fields) */
  length?: number;
  
  /** Whether field has been modified from initial value */
  modified?: boolean;
  
  /** Whether field has been modified since last submission */
  modifiedSinceLastSubmit?: boolean;
  
  /** Field name */
  name: string;
  
  /** Whether field is in its initial, unmodified state */
  pristine?: boolean;
  
  /** Field submission error */
  submitError?: any;
  
  /** Whether last submission failed for this field */
  submitFailed?: boolean;
  
  /** Whether last submission succeeded */
  submitSucceeded?: boolean;
  
  /** Whether form is currently being submitted */
  submitting?: boolean;
  
  /** Whether field has been touched (focused and blurred) */
  touched?: boolean;
  
  /** Whether field passes validation */
  valid?: boolean;
  
  /** Whether field is currently being validated */
  validating?: boolean;
  
  /** Current field value */
  value?: FieldValue;
  
  /** Whether field has been visited (focused) */
  visited?: boolean;
}

Field Subscription Interface

Subscription object for controlling which field state properties trigger updates.

interface FieldSubscription {
  active?: boolean;
  data?: boolean;
  dirty?: boolean;
  dirtySinceLastSubmit?: boolean;
  error?: boolean;
  initial?: boolean;
  invalid?: boolean;
  length?: boolean;
  modified?: boolean;
  modifiedSinceLastSubmit?: boolean;
  pristine?: boolean;
  submitError?: boolean;
  submitFailed?: boolean;
  submitSucceeded?: boolean;
  submitting?: boolean;
  touched?: boolean;
  valid?: boolean;
  validating?: boolean;
  value?: boolean;
  visited?: boolean;
}

Field Configuration Interface

Configuration options when registering a field.

interface FieldConfig<FieldValue = any> {
  /** Callback executed after form submission */
  afterSubmit?: () => void;
  
  /** Callback executed before form submission, can prevent submission by returning false */
  beforeSubmit?: () => void | false;
  
  /** Custom data to attach to the field */
  data?: any;
  
  /** Default value when field value is undefined */
  defaultValue?: any;
  
  /** Function that returns a validator for this field */
  getValidator?: GetFieldValidator<FieldValue>;
  
  /** Initial value for the field */
  initialValue?: any;
  
  /** Custom equality function for determining if field value changed */
  isEqual?: IsEqual;
  
  /** Whether field changes should trigger notifications (default: false) */
  silent?: boolean;
  
  /** Names of other fields to validate when this field changes */
  validateFields?: string[];
  
  /** Whether field validation is asynchronous */
  async?: boolean;
}

Field Configuration Examples:

// Field with custom validator
form.registerField(
  'age',
  (fieldState) => updateAgeField(fieldState),
  { value: true, error: true },
  {
    getValidator: () => (value) => {
      if (!value) return 'Age is required';
      if (value < 18) return 'Must be 18 or older';
      if (value > 120) return 'Invalid age';
    }
  }
);

// Field with cross-field validation
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';
    }
  }
);

// Field with lifecycle callbacks
form.registerField(
  'specialField',
  (fieldState) => updateSpecialField(fieldState),
  { value: true },
  {
    beforeSubmit: () => {
      // Perform validation or cleanup before submission
      const isValid = performSpecialValidation();
      return isValid; // Return false to prevent submission
    },
    afterSubmit: () => {
      // Cleanup or reset state after submission
      resetSpecialFieldState();
    }
  }
);

// Field with custom equality
form.registerField(
  'objectField',
  (fieldState) => updateObjectField(fieldState),
  { value: true },
  {
    isEqual: (a, b) => JSON.stringify(a) === JSON.stringify(b), // Deep comparison
    initialValue: { nested: { value: 'default' } }
  }
);

Field Subscriber Type

Type definition for field state subscriber functions.

type FieldSubscriber<FieldValue = any> = (state: FieldState<FieldValue>) => void;

type GetFieldValidator<FieldValue = any> = () => FieldValidator<FieldValue> | undefined;

type FieldValidator<FieldValue = any> = (
  value: FieldValue,
  allValues: object,
  meta?: FieldState<FieldValue>
) => any | Promise<any>;

Field Subscription Items

List of all available field subscription properties.

const fieldSubscriptionItems: readonly string[];

The fieldSubscriptionItems array contains: ["active", "data", "dirty", "dirtySinceLastSubmit", "error", "initial", "invalid", "length", "modified", "modifiedSinceLastSubmit", "pristine", "submitError", "submitFailed", "submitSucceeded", "submitting", "touched", "valid", "value", "visited", "validating"]

Dynamic Field Management

Examples of managing fields dynamically:

// Dynamic field registration based on conditions
const conditionalField = (fieldName: string, shouldRegister: boolean) => {
  if (shouldRegister) {
    return form.registerField(
      fieldName,
      (fieldState) => console.log(`${fieldName}:`, fieldState.value),
      { value: true }
    );
  }
  return () => {}; // No-op unsubscribe function
};

// Array field management
const registerArrayField = (arrayName: string, index: number) => {
  const fieldName = `${arrayName}[${index}]`;
  return form.registerField(
    fieldName,
    (fieldState) => updateArrayItem(arrayName, index, fieldState),
    { value: true, error: true },
    {
      getValidator: () => (value) => validateArrayItem(value, index)
    }
  );
};

// Bulk field registration
const registerMultipleFields = (fieldNames: string[]) => {
  const unsubscribeFunctions = fieldNames.map(fieldName =>
    form.registerField(
      fieldName,
      (fieldState) => updateField(fieldName, fieldState),
      { value: true, error: true, touched: true }
    )
  );
  
  // Return cleanup function
  return () => {
    unsubscribeFunctions.forEach(unsubscribe => unsubscribe());
  };
};