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

error-handling.mddocs/

Error Handling

Components and patterns for displaying validation errors with flexible rendering options.

Capabilities

ErrorMessage Component

Component for displaying field error messages with flexible rendering patterns.

/**
 * Component for displaying field error messages
 * @param props - ErrorMessage configuration
 * @returns JSX element displaying error or null if no error
 */
function ErrorMessage(props: ErrorMessageProps): JSX.Element | null;

interface ErrorMessageProps {
  /** Name of the field to display errors for */
  name: string;
  /** Component or HTML element to render error with */
  component?: React.ComponentType<any> | keyof JSX.IntrinsicElements;
  /** Children render function receiving error message */
  children?: (error: string) => React.ReactNode;
  /** Render prop function receiving error message */
  render?: (error: string) => React.ReactNode;
  /** CSS class name for styling */
  className?: string;
  /** Additional HTML attributes */
  [key: string]: any;
}

Usage Examples:

import { Formik, Form, Field, ErrorMessage } from "formik";
import * as Yup from "yup";

// Basic usage with default div element
const BasicForm = () => (
  <Formik
    initialValues={{ email: '', password: '' }}
    validationSchema={Yup.object({
      email: Yup.string().email('Invalid email').required('Required'),
      password: Yup.string().min(6, 'Too short').required('Required'),
    })}
    onSubmit={values => console.log(values)}
  >
    <Form>
      <Field name="email" type="email" />
      <ErrorMessage name="email" />
      
      <Field name="password" type="password" />
      <ErrorMessage name="password" />
      
      <button type="submit">Submit</button>
    </Form>
  </Formik>
);

// With custom component
const StyledError = ({ children }) => (
  <div className="error-message" style={{ color: 'red', fontSize: '12px' }}>
    {children}
  </div>
);

const StyledForm = () => (
  <Formik
    initialValues={{ username: '' }}
    validate={values => {
      const errors = {};
      if (!values.username) {
        errors.username = 'Username is required';
      }
      return errors;
    }}
    onSubmit={values => console.log(values)}
  >
    <Form>
      <Field name="username" />
      <ErrorMessage name="username" component={StyledError} />
      <button type="submit">Submit</button>
    </Form>
  </Formik>
);

// With render prop
const RenderPropForm = () => (
  <Formik
    initialValues={{ age: '' }}
    validate={values => {
      const errors = {};
      if (!values.age) {
        errors.age = 'Age is required';
      } else if (values.age < 18) {
        errors.age = 'Must be 18 or older';
      }
      return errors;
    }}
    onSubmit={values => console.log(values)}
  >
    <Form>
      <Field name="age" type="number" />
      <ErrorMessage
        name="age"
        render={error => (
          <div className="alert alert-danger">
            <strong>Error:</strong> {error}
          </div>
        )}
      />
      <button type="submit">Submit</button>
    </Form>
  </Formik>
);

// With children function
const ChildrenFunctionForm = () => (
  <Formik
    initialValues={{ phone: '' }}
    validate={values => {
      const errors = {};
      if (values.phone && !/^\d{10}$/.test(values.phone)) {
        errors.phone = 'Phone must be 10 digits';
      }
      return errors;
    }}
    onSubmit={values => console.log(values)}
  >
    <Form>
      <Field name="phone" placeholder="Phone number" />
      <ErrorMessage name="phone">
        {error => (
          <div className="error-tooltip">
            <span className="icon">⚠️</span>
            <span className="message">{error}</span>
          </div>
        )}
      </ErrorMessage>
      <button type="submit">Submit</button>
    </Form>
  </Formik>
);

// With HTML element and styling
const HtmlElementForm = () => (
  <Formik
    initialValues={{ website: '' }}
    validate={values => {
      const errors = {};
      if (values.website && !/^https?:\/\/.+/.test(values.website)) {
        errors.website = 'Website must be a valid URL';
      }
      return errors;
    }}
    onSubmit={values => console.log(values)}
  >
    <Form>
      <Field name="website" placeholder="Website URL" />
      <ErrorMessage
        name="website"
        component="span"
        className="field-error"
        style={{ color: 'red', fontStyle: 'italic' }}
      />
      <button type="submit">Submit</button>
    </Form>
  </Formik>
);

Error Display Patterns

Common patterns for displaying and handling form errors.

Field-Level Error Display:

import { useField } from "formik";

// Custom field component with integrated error display
const TextInputWithError = ({ label, ...props }) => {
  const [field, meta] = useField(props);
  
  return (
    <div className="form-field">
      <label htmlFor={props.id || props.name}>{label}</label>
      <input {...field} {...props} />
      {meta.touched && meta.error ? (
        <div className="error">{meta.error}</div>
      ) : null}
    </div>
  );
};

// Usage
const CustomFieldForm = () => (
  <Formik
    initialValues={{ firstName: '', lastName: '' }}
    validate={values => {
      const errors = {};
      if (!values.firstName) errors.firstName = 'Required';
      if (!values.lastName) errors.lastName = 'Required';
      return errors;
    }}
    onSubmit={values => console.log(values)}
  >
    <Form>
      <TextInputWithError
        label="First Name"
        name="firstName"
        type="text"
      />
      <TextInputWithError
        label="Last Name"
        name="lastName"
        type="text"
      />
      <button type="submit">Submit</button>
    </Form>
  </Formik>
);

Form-Level Error Summary:

const FormWithErrorSummary = () => (
  <Formik
    initialValues={{ name: '', email: '', phone: '' }}
    validate={values => {
      const errors = {};
      if (!values.name) errors.name = 'Name is required';
      if (!values.email) errors.email = 'Email is required';
      if (!values.phone) errors.phone = 'Phone is required';
      return errors;
    }}
    onSubmit={values => console.log(values)}
  >
    {({ errors, touched, isValid }) => (
      <Form>
        {/* Error Summary */}
        {!isValid && Object.keys(touched).length > 0 && (
          <div className="error-summary">
            <h4>Please fix the following errors:</h4>
            <ul>
              {Object.entries(errors).map(([field, error]) => (
                touched[field] && <li key={field}>{error}</li>
              ))}
            </ul>
          </div>
        )}
        
        <Field name="name" placeholder="Name" />
        <ErrorMessage name="name" component="div" className="field-error" />
        
        <Field name="email" type="email" placeholder="Email" />
        <ErrorMessage name="email" component="div" className="field-error" />
        
        <Field name="phone" placeholder="Phone" />
        <ErrorMessage name="phone" component="div" className="field-error" />
        
        <button type="submit">Submit</button>
      </Form>
    )}
  </Formik>
);

Async Validation Errors:

const AsyncValidationForm = () => (
  <Formik
    initialValues={{ username: '' }}
    validate={async (values) => {
      const errors = {};
      
      if (!values.username) {
        errors.username = 'Username is required';
      } else if (values.username.length < 3) {
        errors.username = 'Username must be at least 3 characters';
      } else {
        // Simulate async validation
        try {
          const available = await checkUsernameAvailability(values.username);
          if (!available) {
            errors.username = 'Username is already taken';
          }
        } catch (error) {
          errors.username = 'Error checking username availability';
        }
      }
      
      return errors;
    }}
    onSubmit={values => console.log(values)}
  >
    {({ isValidating }) => (
      <Form>
        <Field name="username" placeholder="Username" />
        {isValidating && <span>Checking availability...</span>}
        <ErrorMessage name="username" component="div" className="error" />
        <button type="submit">Submit</button>
      </Form>
    )}
  </Formik>
);

const checkUsernameAvailability = async (username) => {
  // Simulate API call
  await new Promise(resolve => setTimeout(resolve, 1000));
  return !['admin', 'user', 'test'].includes(username.toLowerCase());
};

Server Error Handling:

const ServerErrorForm = () => (
  <Formik
    initialValues={{ email: '', password: '' }}
    onSubmit={async (values, { setSubmitting, setFieldError, setStatus }) => {
      try {
        await loginUser(values);
        setStatus({ type: 'success', message: 'Login successful!' });
      } catch (error) {
        if (error.field) {
          // Field-specific error
          setFieldError(error.field, error.message);
        } else {
          // General error
          setStatus({ type: 'error', message: error.message });
        }
      } finally {
        setSubmitting(false);
      }
    }}
  >
    {({ status, isSubmitting }) => (
      <Form>
        {/* Global status messages */}
        {status && (
          <div className={`alert alert-${status.type}`}>
            {status.message}
          </div>
        )}
        
        <Field name="email" type="email" placeholder="Email" />
        <ErrorMessage name="email" component="div" className="error" />
        
        <Field name="password" type="password" placeholder="Password" />
        <ErrorMessage name="password" component="div" className="error" />
        
        <button type="submit" disabled={isSubmitting}>
          {isSubmitting ? 'Logging in...' : 'Login'}
        </button>
      </Form>
    )}
  </Formik>
);

const loginUser = async (credentials) => {
  // Simulate API call
  await new Promise(resolve => setTimeout(resolve, 1000));
  
  if (credentials.email === 'taken@example.com') {
    throw { field: 'email', message: 'Email already registered' };
  }
  
  if (credentials.password === 'wrong') {
    throw { field: 'password', message: 'Incorrect password' };
  }
  
  if (!credentials.email || !credentials.password) {
    throw { message: 'Please fill in all fields' };
  }
  
  return { success: true };
};

Array Field Errors:

const ArrayErrorForm = () => (
  <Formik
    initialValues={{ emails: [''] }}
    validate={values => {
      const errors = {};
      
      if (!values.emails || values.emails.length === 0) {
        errors.emails = 'At least one email is required';
      } else {
        const emailErrors = [];
        values.emails.forEach((email, index) => {
          if (!email) {
            emailErrors[index] = 'Email is required';
          } else if (!/\S+@\S+\.\S+/.test(email)) {
            emailErrors[index] = 'Invalid email format';
          }
        });
        
        if (emailErrors.length > 0) {
          errors.emails = emailErrors;
        }
      }
      
      return errors;
    }}
    onSubmit={values => console.log(values)}
  >
    {({ values, errors, touched }) => (
      <Form>
        <FieldArray name="emails">
          {({ push, remove }) => (
            <div>
              {values.emails.map((email, index) => (
                <div key={index}>
                  <Field name={`emails.${index}`} placeholder="Email" />
                  {/* Individual field error */}
                  {errors.emails && 
                   errors.emails[index] && 
                   touched.emails && 
                   touched.emails[index] && (
                    <div className="error">{errors.emails[index]}</div>
                  )}
                  <button type="button" onClick={() => remove(index)}>
                    Remove
                  </button>
                </div>
              ))}
              <button type="button" onClick={() => push('')}>
                Add Email
              </button>
            </div>
          )}
        </FieldArray>
        
        {/* Array-level error */}
        {typeof errors.emails === 'string' && (
          <div className="error">{errors.emails}</div>
        )}
        
        <button type="submit">Submit</button>
      </Form>
    )}
  </Formik>
);

Error Types and Interfaces

TypeScript interfaces for error handling.

type FormikErrors<Values> = {
  [K in keyof Values]?: Values[K] extends any[]
    ? Values[K][number] extends object
      ? FormikErrors<Values[K][number]>[] | string | string[]
      : string | string[]
    : Values[K] extends object
    ? FormikErrors<Values[K]>
    : string;
};

interface ErrorMessageProps {
  /** Field name to display errors for */
  name: string;
  /** Component to render error with */
  component?: React.ComponentType<any> | keyof JSX.IntrinsicElements;
  /** Children render function */
  children?: (error: string) => React.ReactNode;
  /** Render prop function */
  render?: (error: string) => React.ReactNode;
  /** CSS class name */
  className?: string;
  /** Additional props */
  [key: string]: any;
}