or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

context-integration.mdcore-components.mderror-handling.mdfield-arrays.mdform-state-management.mdindex.md
tile.json

form-state-management.mddocs/

Form State Management

Hook-based form state management for functional components, providing complete form control without component wrappers.

Capabilities

useFormik Hook

Primary hook for managing form state, validation, and submission in functional components.

/**
 * Hook for form state management without component wrapper
 * @param config - Formik configuration object
 * @returns Complete formik props object with state and handlers
 */
function useFormik<Values extends FormikValues = FormikValues>(
  config: FormikConfig<Values>
): FormikProps<Values>;

interface FormikConfig<Values> {
  /** Initial values of the form */
  initialValues: Values;
  /** Form submission handler */
  onSubmit: (values: Values, formikHelpers: FormikHelpers<Values>) => void | Promise<any>;
  /** Yup schema or validation function */
  validationSchema?: any | (() => any);
  /** Custom validation function */
  validate?: (values: Values) => void | object | Promise<FormikErrors<Values>>;
  /** Validate on input change (default: true) */
  validateOnChange?: boolean;
  /** Validate on input blur (default: true) */
  validateOnBlur?: boolean;
  /** Validate on component mount (default: false) */
  validateOnMount?: boolean;
  /** Whether initial form values are valid */
  isInitialValid?: boolean | ((props: any) => boolean);
  /** Reset form when initialValues change */
  enableReinitialize?: boolean;
  /** Initial status value */
  initialStatus?: any;
  /** Initial errors object */
  initialErrors?: FormikErrors<Values>;
  /** Initial touched object */
  initialTouched?: FormikTouched<Values>;
  /** Reset handler */
  onReset?: (values: Values, formikHelpers: FormikHelpers<Values>) => void;
}

interface FormikProps<Values> {
  /** Current form values */
  values: Values;
  /** Current form errors */
  errors: FormikErrors<Values>;
  /** Fields that have been visited */
  touched: FormikTouched<Values>;
  /** Whether form is currently submitting */
  isSubmitting: boolean;
  /** Whether form is currently validating */
  isValidating: boolean;
  /** Top-level status object */
  status?: any;
  /** Number of submit attempts */
  submitCount: number;
  /** Whether form has been modified */
  dirty: boolean;
  /** Whether form is valid */
  isValid: boolean;
  /** Initial form values */
  initialValues: Values;
  /** Initial form errors */
  initialErrors: FormikErrors<Values>;
  /** Initial touched state */
  initialTouched: FormikTouched<Values>;
  /** Initial status */
  initialStatus?: any;
  /** Form submit handler */
  handleSubmit: (e?: React.FormEvent<HTMLFormElement>) => void;
  /** Form reset handler */
  handleReset: (e?: React.SyntheticEvent<any>) => void;
  /** Input blur handler */
  handleBlur: (e: React.FocusEvent<any>) => void;
  /** Input change handler */
  handleChange: (e: React.ChangeEvent<any>) => void;
  /** Get field props */
  getFieldProps: (props: string | FieldConfig) => FieldInputProps<any>;
  /** Get field meta data */
  getFieldMeta: (name: string) => FieldMetaProps<any>;
  /** Get field helpers */
  getFieldHelpers: (name: string) => FieldHelperProps<any>;
  /** Set field value */
  setFieldValue: (field: string, value: any, shouldValidate?: boolean) => Promise<void | FormikErrors<Values>>;
  /** Set field error */
  setFieldError: (field: string, message: string | undefined) => void;
  /** Set field touched state */
  setFieldTouched: (field: string, isTouched?: boolean, shouldValidate?: boolean) => Promise<void | FormikErrors<Values>>;
  /** Set form status */
  setStatus: (status?: any) => void;
  /** Set form errors */
  setErrors: (errors: FormikErrors<Values>) => void;
  /** Set form submitting state */
  setSubmitting: (isSubmitting: boolean) => void;
  /** Set form touched state */
  setTouched: (touched: FormikTouched<Values>, shouldValidate?: boolean) => Promise<void | FormikErrors<Values>>;
  /** Set form values */
  setValues: (values: React.SetStateAction<Values>, shouldValidate?: boolean) => Promise<void | FormikErrors<Values>>;
  /** Validate entire form */
  validateForm: (values?: any) => Promise<FormikErrors<Values>>;
  /** Validate single field */
  validateField: (field: string) => Promise<void> | Promise<string | undefined>;
  /** Reset form to initial state */
  resetForm: (nextState?: Partial<FormikState<Values>>) => void;
  /** Submit form programmatically */
  submitForm: () => Promise<void>;
  /** Set entire Formik state */
  setFormikState: (f: FormikState<Values> | ((prevState: FormikState<Values>) => FormikState<Values>), cb?: () => void) => void;
  /** Register field with Formik */
  registerField: (name: string, fns: { validate?: FieldValidator }) => void;
  /** Unregister field from Formik */
  unregisterField: (name: string) => void;
}

Usage Examples:

import { useFormik } from "formik";
import * as Yup from "yup";

// Basic usage
const SignupForm = () => {
  const formik = useFormik({
    initialValues: {
      firstName: '',
      lastName: '',
      email: '',
    },
    validationSchema: Yup.object({
      firstName: Yup.string()
        .max(15, 'Must be 15 characters or less')
        .required('Required'),
      lastName: Yup.string()
        .max(20, 'Must be 20 characters or less')
        .required('Required'),
      email: Yup.string().email('Invalid email address').required('Required'),
    }),
    onSubmit: values => {
      alert(JSON.stringify(values, null, 2));
    },
  });

  return (
    <form onSubmit={formik.handleSubmit}>
      <label htmlFor="firstName">First Name</label>
      <input
        id="firstName"
        name="firstName"
        type="text"
        onChange={formik.handleChange}
        onBlur={formik.handleBlur}
        value={formik.values.firstName}
      />
      {formik.touched.firstName && formik.errors.firstName ? (
        <div>{formik.errors.firstName}</div>
      ) : null}

      <label htmlFor="lastName">Last Name</label>
      <input
        id="lastName"
        name="lastName"
        type="text"
        onChange={formik.handleChange}
        onBlur={formik.handleBlur}
        value={formik.values.lastName}
      />
      {formik.touched.lastName && formik.errors.lastName ? (
        <div>{formik.errors.lastName}</div>
      ) : null}

      <label htmlFor="email">Email Address</label>
      <input
        id="email"
        name="email"
        type="email"
        onChange={formik.handleChange}
        onBlur={formik.handleBlur}
        value={formik.values.email}
      />
      {formik.touched.email && formik.errors.email ? (
        <div>{formik.errors.email}</div>
      ) : null}

      <button type="submit">Submit</button>
    </form>
  );
};

// Using getFieldProps for cleaner code
const LoginForm = () => {
  const formik = useFormik({
    initialValues: { username: '', password: '' },
    onSubmit: values => console.log(values),
  });

  return (
    <form onSubmit={formik.handleSubmit}>
      <input
        placeholder="Username"
        {...formik.getFieldProps('username')}
      />
      
      <input
        type="password"
        placeholder="Password"
        {...formik.getFieldProps('password')}
      />
      
      <button type="submit" disabled={formik.isSubmitting}>
        Submit
      </button>
    </form>
  );
};

// Async validation and submission
const AsyncForm = () => {
  const formik = useFormik({
    initialValues: { email: '' },
    validate: async (values) => {
      const errors = {};
      
      if (!values.email) {
        errors.email = 'Required';
      } else {
        // Simulate async validation
        const isAvailable = await checkEmailAvailability(values.email);
        if (!isAvailable) {
          errors.email = 'Email already taken';
        }
      }
      
      return errors;
    },
    onSubmit: async (values, { setSubmitting, setStatus }) => {
      try {
        await submitForm(values);
        setStatus({ type: 'success', message: 'Form submitted successfully!' });
      } catch (error) {
        setStatus({ type: 'error', message: 'Submission failed' });
      } finally {
        setSubmitting(false);
      }
    },
  });

  return (
    <form onSubmit={formik.handleSubmit}>
      <input {...formik.getFieldProps('email')} />
      {formik.errors.email && <div>{formik.errors.email}</div>}
      
      {formik.status && (
        <div className={formik.status.type}>
          {formik.status.message}
        </div>
      )}
      
      <button type="submit" disabled={formik.isSubmitting || formik.isValidating}>
        {formik.isSubmitting ? 'Submitting...' : 'Submit'}
      </button>
    </form>
  );
};

Validation Utilities

Functions for integrating with external validation libraries and handling validation errors.

/**
 * Convert Yup validation errors to Formik error format
 * @param yupError - Yup validation error object
 * @returns Formik-compatible errors object
 */
function yupToFormErrors<Values>(yupError: any): FormikErrors<Values>;

/**
 * Validate values against Yup schema
 * @param values - Values to validate
 * @param schema - Yup validation schema
 * @param sync - Whether to run synchronously
 * @param context - Validation context
 * @returns Promise resolving to validation errors
 */
function validateYupSchema<T extends FormikValues>(
  values: T,
  schema: any,
  sync?: boolean,
  context?: any
): Promise<Partial<T>>;

/**
 * Prepare form data for validation by handling nested objects and arrays
 * @param values - Form values to prepare
 * @returns Prepared values for validation
 */
function prepareDataForValidation<T extends FormikValues>(values: T): any;

Usage Examples:

import { useFormik, yupToFormErrors, validateYupSchema } from "formik";
import * as Yup from "yup";

// Custom validation using Yup utilities
const FormWithCustomValidation = () => {
  const schema = Yup.object({
    name: Yup.string().required('Name is required'),
    age: Yup.number().min(18, 'Must be 18 or older').required('Age is required'),
  });

  const formik = useFormik({
    initialValues: { name: '', age: '' },
    validate: async (values) => {
      try {
        await validateYupSchema(values, schema);
        return {};
      } catch (error) {
        return yupToFormErrors(error);
      }
    },
    onSubmit: values => console.log(values),
  });

  return (
    <form onSubmit={formik.handleSubmit}>
      <input {...formik.getFieldProps('name')} placeholder="Name" />
      {formik.errors.name && <div>{formik.errors.name}</div>}
      
      <input {...formik.getFieldProps('age')} type="number" placeholder="Age" />
      {formik.errors.age && <div>{formik.errors.age}</div>}
      
      <button type="submit">Submit</button>
    </form>
  );
};

// Manual error handling
const ManualValidationForm = () => {
  const formik = useFormik({
    initialValues: { username: '' },
    onSubmit: async (values, { setFieldError, setSubmitting }) => {
      try {
        await submitUser(values);
      } catch (error) {
        if (error.field === 'username') {
          setFieldError('username', error.message);
        }
      } finally {
        setSubmitting(false);
      }
    },
  });

  const handleCheckAvailability = async () => {
    if (!formik.values.username) return;
    
    try {
      const available = await checkUsernameAvailability(formik.values.username);
      if (!available) {
        formik.setFieldError('username', 'Username not available');
      } else {
        formik.setFieldError('username', undefined);
      }
    } catch (error) {
      formik.setFieldError('username', 'Error checking availability');
    }
  };

  return (
    <form onSubmit={formik.handleSubmit}>
      <input {...formik.getFieldProps('username')} placeholder="Username" />
      <button type="button" onClick={handleCheckAvailability}>
        Check Availability
      </button>
      {formik.errors.username && <div>{formik.errors.username}</div>}
      
      <button type="submit" disabled={formik.isSubmitting}>
        Submit
      </button>
    </form>
  );
};

Form State Types

Core TypeScript interfaces for form state management.

interface FormikState<Values> {
  /** Current form values */
  values: Values;
  /** Current form errors */
  errors: FormikErrors<Values>;
  /** Fields that have been visited */
  touched: FormikTouched<Values>;
  /** Whether form is currently submitting */
  isSubmitting: boolean;
  /** Whether form is currently validating */
  isValidating: boolean;
  /** Top-level status object */
  status?: any;
  /** Number of submit attempts */
  submitCount: number;
}

interface FormikComputedProps<Values> {
  /** Whether form has been modified from initial values */
  readonly dirty: boolean;
  /** Whether form passes validation */
  readonly isValid: boolean;
  /** Initial form values */
  readonly initialValues: Values;
  /** Initial form errors */
  readonly initialErrors: FormikErrors<Values>;
  /** Initial touched state */
  readonly initialTouched: FormikTouched<Values>;
  /** Initial status value */
  readonly initialStatus?: any;
}

interface FormikHelpers<Values> {
  /** Set top-level status */
  setStatus: (status?: any) => void;
  /** Set form errors object */
  setErrors: (errors: FormikErrors<Values>) => void;
  /** Set form submitting state */
  setSubmitting: (isSubmitting: boolean) => void;
  /** Set form touched state */
  setTouched: (touched: FormikTouched<Values>, shouldValidate?: boolean) => Promise<void | FormikErrors<Values>>;
  /** Set entire form values */
  setValues: (values: React.SetStateAction<Values>, shouldValidate?: boolean) => Promise<void | FormikErrors<Values>>;
  /** Set individual field value */
  setFieldValue: (field: string, value: any, shouldValidate?: boolean) => Promise<void | FormikErrors<Values>>;
  /** Set individual field error */
  setFieldError: (field: string, message: string | undefined) => void;
  /** Set individual field touched state */
  setFieldTouched: (field: string, isTouched?: boolean, shouldValidate?: boolean) => Promise<void | FormikErrors<Values>>;
  /** Validate entire form */
  validateForm: (values?: any) => Promise<FormikErrors<Values>>;
  /** Validate single field */
  validateField: (field: string) => Promise<void> | Promise<string | undefined>;
  /** Reset form to initial or provided state */
  resetForm: (nextState?: Partial<FormikState<Values>>) => void;
  /** Submit form programmatically */
  submitForm: () => Promise<void>;
  /** Set entire Formik state */
  setFormikState: (f: FormikState<Values> | ((prevState: FormikState<Values>) => FormikState<Values>), cb?: () => void) => void;
}

interface FormikHandlers {
  /** Form submit event handler */
  handleSubmit: (e?: React.FormEvent<HTMLFormElement>) => void;
  /** Form reset event handler */
  handleReset: (e?: React.SyntheticEvent<any>) => void;
  /** Input blur event handler */
  handleBlur: (e: React.FocusEvent<any>) => void;
  /** Input change event handler */
  handleChange: (e: React.ChangeEvent<any>) => void;
  /** Get field input props */
  getFieldProps: (props: string | FieldConfig) => FieldInputProps<any>;
  /** Get field metadata */
  getFieldMeta: (name: string) => FieldMetaProps<any>;
  /** Get field helper functions */
  getFieldHelpers: (name: string) => FieldHelperProps<any>;
}

Utility Functions

Type checking and object manipulation utilities for advanced form scenarios and custom integrations.

/**
 * Check if value is a function
 * @param obj - Value to check
 * @returns True if value is a function
 */
function isFunction(obj: any): obj is Function;

/**
 * Check if value is an object
 * @param obj - Value to check
 * @returns True if value is an object
 */
function isObject(obj: any): obj is Object;

/**
 * Check if value is a string
 * @param obj - Value to check
 * @returns True if value is a string
 */
function isString(obj: any): obj is string;

/**
 * Check if value is an integer
 * @param obj - Value to check
 * @returns True if value is an integer
 */
function isInteger(obj: any): boolean;

/**
 * Check if value is NaN
 * @param obj - Value to check
 * @returns True if value is NaN
 */
function isNaN(obj: any): boolean;

/**
 * Check if value is an empty array
 * @param value - Value to check
 * @returns True if value is an empty array
 */
function isEmptyArray(value?: any): boolean;

/**
 * Check if React children are empty
 * @param children - React children to check
 * @returns True if children are empty
 */
function isEmptyChildren(children: any): boolean;

/**
 * Check if value is Promise-like
 * @param value - Value to check
 * @returns True if value has then method
 */
function isPromise(value: any): value is PromiseLike<any>;

/**
 * Check if value is a React input event
 * @param value - Value to check
 * @returns True if value is a React SyntheticEvent
 */
function isInputEvent(value: any): value is React.SyntheticEvent<any>;

/**
 * Get the currently active DOM element
 * @param doc - Document object (defaults to global document)
 * @returns Currently active element or null
 */
function getActiveElement(doc?: Document): Element | null;

/**
 * Get nested object value by path
 * @param obj - Object to traverse
 * @param key - Path string or array of keys
 * @param def - Default value if path not found
 * @returns Value at path or default value
 */
function getIn(obj: any, key: string | string[], def?: any): any;

/**
 * Set nested object value by path
 * @param obj - Object to modify
 * @param path - Path string to set value at
 * @param value - Value to set
 * @returns Modified object
 */
function setIn(obj: any, path: string, value: any): any;

/**
 * Set values in nested object structure
 * @param object - Object to modify
 * @param value - Value to set throughout structure
 * @returns Modified object with values set
 */
function setNestedObjectValues<T>(object: any, value: any): T;

Usage Examples:

import { getIn, setIn, isFunction, isPromise } from "formik";

// Object path manipulation
const user = {
  profile: {
    contact: {
      email: 'user@example.com'
    }
  }
};

// Get nested value
const email = getIn(user, 'profile.contact.email'); // 'user@example.com'
const phone = getIn(user, 'profile.contact.phone', 'No phone'); // 'No phone'

// Set nested value
const updatedUser = setIn(user, 'profile.contact.phone', '555-1234');

// Type checking
const validate = (value) => {
  if (!isString(value)) {
    return 'Value must be a string';
  }
  
  if (isFunction(value)) {
    return 'Functions are not allowed';
  }
  
  // Handle async validation
  if (isPromise(value)) {
    return value.then(result => validate(result));
  }
  
  return undefined;
};

// Custom field component using utilities
const AdvancedField = ({ name, ...props }) => {
  const [field, meta, helpers] = useField(name);
  
  const handleChange = (e) => {
    if (isInputEvent(e)) {
      field.onChange(e);
    } else {
      // Handle programmatic value changes
      helpers.setValue(e);
    }
  };
  
  return (
    <input 
      {...field} 
      {...props}
      onChange={handleChange}
    />
  );
};