ForgeForm

Error Handeling

ForgeForm is designed to provide clear and actionable error feedback to users, enhancing form usability.

Contents

Error Handling in ForgeForm

  • Defining Custom Error Messages: Tailoring error messages within your schema to be user-friendly and context-specific.
  • Accessing Validation Errors: Retrieving and displaying error messages generated by ForgeForm's validation engine.
  • Wizard Form Error Handling: Managing errors within multi-step wizard forms, including step-specific errors and lifecycle callbacks.

Custom Error Messages in Schemas:

ForgeForm allows you to define custom error messages directly within your schema for each validation rule. This empowers you to provide more informative and user-centric error feedback instead of relying on generic default messages.

You can customize error messages for the following validation rules:

  • requiredErrorMessage: Message for required: true validation failure.
  • minLengthErrorMessage: Message for minLength validation failure (strings, arrays).
  • maxLengthErrorMessage: Message for maxLength validation failure (strings, arrays).
  • minErrorMessage: Message for min validation failure (numbers, dates).
  • maxErrorMessage: Message for max validation failure (numbers, dates).
  • formatErrorMessage: Message for built-in format validations (e.g., email, url, tel).
  • patternErrorMessage: Message for pattern (regex) validation failure.
  • customErrorMessage: Message for failures from customValidator functions.

If you do not provide custom error messages, ForgeForm will use sensible default error messages.

Example: Schema with Custom Error Messages:

import { createSchema } from 'forgeform';
 
interface CustomMessagesFormData {
  firstName?: string;
  emailAddress?: string;
  age?: number;
  message?: string;
}
 
const customMessageSchema = createSchema<CustomMessagesFormData>({
  fields: {
    firstName: { type: 'string', required: true, requiredErrorMessage: 'Please tell us your first name.' },
    emailAddress: { type: 'email', required: true, formatErrorMessage: 'Please enter a valid email format.', requiredErrorMessage: 'Your email is needed to contact you.' },
    age: { type: 'number', min: 18, minErrorMessage: 'You must be at least 18 years of age to register.' },
    message: { type: 'textarea', maxLength: 200, maxLengthErrorMessage: 'Your message is getting long! Please keep it under 200 characters.' },
  },
});

In this customMessageSchema:

  • Each field uses specific error message attributes (e.g., requiredErrorMessage, formatErrorMessage, minErrorMessage, maxLengthErrorMessage) to define user-friendly messages.
  • If validation for firstName fails because it's required, the error message will be "Please tell us your first name.".
  • For emailAddress, if it's missing, the error is "Your email is needed to contact you."; if it's in the wrong format, the error is "Please enter a valid email format.".

2. Accessing Validation Errors:

ForgeForm's validate() method and forgeFormResolver provide structured validation results that include any errors encountered.

a) Accessing Errors from schema.validate():

When you use schema.validate(formData) directly (without React Hook Form), the method returns a ValidationResult object. If validation fails, validationResult.success will be false, and validationResult.errors will contain an object of errors.

import { createSchema } from 'forgeform';
 
// ... (schema definition - e.g., basicSchema from earlier examples) ...
 
async function validateFormData(formData) {
  const validationResult = await basicSchema.validate(formData);
  if (!validationResult.success) {
    console.log('Validation Failed. Errors:', validationResult.errors);
    // Access individual errors like:
    if (validationResult.errors.email) {
      console.log('Email Error:', validationResult.errors.email.error); // Access error message
      console.log('Email Error Type:', validationResult.errors.email.type); // Access error type (e.g., 'required', 'format')
    }
    if (validationResult.errors.password) {
      console.log('Password Error:', validationResult.errors.password.error);
    }
  } else {
    console.log('Validation Success! Data:', validationResult.data);
  }
}

In the validationResult.errors object:

  • Keys are the field names (e.g., "email", "password").
  • Values are error objects with properties:
    • error: The error message string (either your custom message or the default).
    • type: The type of validation that failed (e.g., "required", "format", "minLength").

b) Accessing Errors with forgeFormResolver (React Hook Form):

When using forgeFormResolver with React Hook Form, errors are automatically populated into React Hook Form's formState.errors object. You access them directly within your React components:

import React from 'react';
import { useForm } from 'react-hook-form';
import { createSchema, forgeFormResolver } from 'forgeform';
 
interface ResolverErrorFormData {
  username?: string;
  password?: string;
}
 
const resolverErrorSchema = createSchema<ResolverErrorFormData>({
  fields: {
    username: { type: 'string', required: true, requiredErrorMessage: 'Username is required' },
    password: { type: 'password', minLength: 8, minLengthErrorMessage: 'Password must be at least 8 characters' },
  },
});
 
const ResolverErrorExample = () => {
  const { register, handleSubmit, formState: { errors } } = useForm<ResolverErrorFormData>({
    resolver: forgeFormResolver(resolverErrorSchema),
    mode: 'onSubmit',
  });
 
  const onSubmit = (data: ResolverErrorFormData) => {
    alert('Form submitted (Error Example - Check console if no errors)');
    console.log('Error Form Data:', data); // Data will only log if valid
  };
 
  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <div>
        <label htmlFor="username">Username:</label>
        <input {...register('username')} id="username" />
        {errors.username && <p style={{ color: 'red' }}>{errors.username.message}</p>} {/* Display username error */}
      </div>
      <div>
        <label htmlFor="password">Password (Min 8 chars):</label>
        <input {...register('password')} type="password" id="password" />
        {errors.password && <p style={{ color: 'red' }}>{errors.password.message}</p>} {/* Display password error */}
      </div>
      <button type="submit">Submit</button>
    </form>
  );
};
 
export default ResolverErrorExample;

React Hook Form's formState.errors object is structured similarly to validationResult.errors from schema.validate().

You can access error messages using paths like errors.username.message and errors.password.message in your JSX to display them next to the corresponding input fields.

3. Error Handling in Wizard Forms:

Error handling in Wizard Forms involves managing validation errors for each step and providing feedback as users navigate through the wizard.

a) Step Validation Errors:

When you call wizard.nextStep(stepData), it returns a Promise<boolean>.

  • If the current step's validation passes, the Promise resolves to true, and the wizard moves to the next step.
  • If validation fails, the Promise resolves to false. To get the specific validation errors for the step, you can call wizard.validateCurrentStep() after nextStep resolves to false.
async handleNextStep() {
  const success = await wizard.nextStep(currentStepData);
  if (!success) {
    const validationResult = await wizard.validateCurrentStep();
    if (validationResult && validationResult.errors) {
      // Access step errors from validationResult.errors
      console.error('Step Validation Errors:', validationResult.errors);
      // Display errors in UI - e.g., set to component state for rendering error messages
      setStepErrors(validationResult.errors);
    }
  } else {
    // Proceed to next step
  }
}

b) onValidationError Callback in createWizard:

The createWizard function's options argument allows you to define an onValidationError callback. This callback is invoked whenever validation fails for a step during a nextStep call. It provides the stepId and the errors object for that step, allowing you to centralize error handling logic within your wizard.

const wizardInstance = createWizard(steps, {}, {
  onValidationError: (stepId, errors) => {
    console.error(`Validation failed for step ${stepId}:`, errors);
    alert(`Validation errors in step ${stepId}. Please correct them.`); // Example user feedback
    // You can also manage step-specific error state here if needed
  },
  // ... other wizard options
});

Within the onValidationError callback:

  • stepId: The ID of the step where validation failed.
  • errors: The validation error object for that step.

You can use this callback to log errors, display step-level error messages, or manage error states specific to each step in your wizard UI.

c) Displaying Errors in Wizard Forms:

In a Wizard Form UI, you typically want to display error messages:

  • Near the Input Field: Displaying errors directly below or next to the input field that caused the error (as shown in React Hook Form examples).
  • Step-Level Summary: Optionally, you might want to display a summary of errors at the top of a step if there are multiple errors, or to highlight which steps have errors in a step navigation bar.

You can use component state to manage and display step-specific error messages. When validation fails in nextStep or in the onValidationError callback, you can update the error state and conditionally render error messages in your step components.

Title

By using custom error messages in your schemas, accessing error details from validation results, and utilizing the onValidationError callback and step-level error management in Wizard Forms, you can create forms with excellent user feedback and a smooth error correction experience.

Understanding onValidationError Callback

As previously mentioned, the onValidationError callback, configured within the options of createWizard, is your central point for handling step validation failures in a Wizard Form. It's invoked automatically by ForgeForm whenever a call to wizard.nextStep() results in validation errors for the current step.

const wizardInstance = createWizard(steps, initialData, {
  onValidationError: (stepId, errors) => {
    // Error handling logic here
  },
  onComplete: (finalData) => { /* ... */ },
  onStepChange: (stepId, index) => { /* ... */ }
});

Parameters of onValidationError:

  • stepId: string: This parameter provides the unique identifier (id) of the wizard step that failed validation. This is crucial for identifying which step has errors, especially in multi-step wizards, allowing you to apply step-specific error handling or UI updates.
  • errors: FormErrors: This parameter is the core of the error information. It's an object (FormErrors) containing the validation errors for the step. The structure of this object mirrors your schema's field structure for that step. For example:
errors = {
  fieldName1: { type: 'required', error: 'Field 1 is required' },
  fieldName2: { type: 'format', error: 'Invalid format for Field 2' },
  // ... more errors for this step
}

Within the errors object:

  • Keys are the field names that have validation errors.
  • Values are error objects with:
    • type: Indicates the type of validation that failed (e.g., 'required', 'minLength', 'customValidator').
    • error: The user-friendly error message string (customized or default).

Practical Uses of onValidationError Callback:

  • Centralized Error Logging: The callback is a good place to log validation errors for debugging or monitoring purposes.
const wizardInstance = createWizard(steps, initialData, {
  onValidationError: (stepId, errors) => {
    console.error(`[Wizard Validation Error] Step ID: ${stepId}`, errors); // Log errors to console
  },
  // ...
});

Displaying Step-Level Error Summaries:

You can use onValidationError to trigger the display of a summary message at the top of the step to inform the user that there are errors to correct within the current step.

import React, { useState } from 'react';
import { createWizard } from 'forgeform';
 
const MyWizardComponent = () => {
  const [stepErrorSummary, setStepErrorSummary] = useState(''); // State for step-level error message
  const wizard = createWizard(steps, initialData, {
    onValidationError: (stepId, errors) => {
      setStepErrorSummary('Please correct the errors in this step.'); // Set error summary message
      // ... rest of error handling
    },
    onValidationSuccess: (stepId, data) => {
      setStepErrorSummary(''); // Clear summary on successful validation
      // ...
    },
    // ...
  });
 
  return (
    <div>
      {stepErrorSummary && <div className="step-error-summary" style={{ color: 'red', marginBottom: '10px' }}>{stepErrorSummary}</div>} {/* Display error summary */}
      {/* ... rest of your wizard step UI and logic */}
    </div>
  );
};

Managing Step-Specific Error States:

The most common and powerful use of onValidationError is to update component state to manage and display errors directly within each step component. This allows you to associate specific error messages with their corresponding input fields.

Strategies for Displaying Errors in Wizard Form UI:

There are two primary strategies for displaying errors in Wizard Forms:

a) Near-Field Error Display:

This is the most user-friendly approach. Display error messages right next to or below the input field that caused the error. This provides immediate context and makes it easy for users to identify and correct mistakes.

Implementation: To achieve near-field error display, you'll typically use component state to store errors for the current step. When onValidationError is triggered, you parse the errors object and update your component's state to reflect these errors. Then, within your step component's JSX, you conditionally render error messages based on this error state, typically adjacent to the input fields.

Example (Conceptual - using React state in a step component):

import React, { useState, useEffect } from 'react';
import { useFormContext } from 'react-hook-form'; // If using React Hook Form
 
const Step1Component = () => {
  const { register, formState: { errors } } = useFormContext(); // If using RHF
  const [stepErrors, setStepErrors] = useState({}); // State for step-specific errors
 
  useEffect(() => {
    // Example of receiving errors from onValidationError (you'd need to pass down or use context)
    // In a real implementation, error state update would likely happen in a parent wizard component
    // that uses onValidationError and then passes down errors to steps.
    if (/* errors were passed down as props or via context */ stepValidationErrorsFromParent) {
      setStepErrors(stepValidationErrorsFromParent);
    }
  }, [/* dependency on props/context holding errors */ stepValidationErrorsFromParent]);
 
  return (
    <div>
      <label htmlFor="name">Name:</label>
      <input {...register('name')} id="name" />
      {stepErrors.name && <p className="error-message" style={{ color: 'red' }}>{stepErrors.name.error}</p>} {/* Display error near input */}
      {/* ... other fields for step 1 ... */}
    </div>
  );
};

In this conceptual example:

  • stepErrors state is used within the step component to hold errors relevant to this specific step.
  • When onValidationError is triggered in the parent wizard component, it would update state in the parent (e.g., a currentStepErrors state) and potentially pass down relevant errors to each step component as props or using context.
  • Within Step1Component, stepErrors.name && <p...> conditionally renders the error message next to the "name" input if there's an error for the "name" field.

b) Step-Level Error Summary (Optional Enhancement):

For longer forms or wizards with multiple fields per step, you can additionally provide a step-level error summary at the top of the step (as shown in the previous "Step-Level Error Summaries" example above). This summary acts as a quick overview to alert the user that there are problems on the current step. However, near-field error display is generally more important for usability.

Example: Integrating onValidationError with Step-Specific Error State (Conceptual Wizard Component)

Here's a conceptual outline of a wizard component demonstrating how to use onValidationError to manage step-specific error state and display near-field errors:

import React, { useState, useMemo } from 'react';
import { createWizard } from 'forgeform';
 
// ... (Assume step schemas and Wizard component structure similar to previous examples) ...
 
const MyWizardFormComponent = () => {
  const [currentStepIndexState, setCurrentStepIndexState] = useState(0);
  const [stepErrors, setStepErrors] = useState({}); // State to hold errors for the current step
  const wizard = useMemo(() => createWizard(steps, initialData, {
    onValidationError: (stepId, errors) => {
      setStepErrors(errors); // Update stepErrors state with errors from callback
      console.error(`Validation failed for step ${stepId}:`, errors);
      // (Optional) Step-level summary logic here if needed
    },
    onValidationSuccess: (stepId, data) => {
      setStepErrors({}); // Clear errors on successful step validation
      // (Optional) Clear step-level summary message
    },
    // ... other wizard options
  }), []);
 
  const handleNext = async (e) => {
    e.preventDefault();
    const success = await wizard.nextStep(currentStepData); // Get data from current step's inputs
    if (success) {
      setCurrentStepIndexState(wizard.currentStepIndex);
      setStepErrors({}); // Clear errors when moving to next step
    } // Else, onValidationError callback will handle setting stepErrors
  };
 
  const handlePrevious = (e) => {
    e.preventDefault();
    wizard.previousStep();
    setCurrentStepIndexState(wizard.currentStepIndex);
    setStepErrors({}); // Clear errors when going back
  };
 
  return (
    <div>
      {/* ... Step navigation UI ... */}
 
      {/* Conditionally render step components, e.g., Step1Component, Step2Component */}
      {currentStepIndexState === 0 && <Step1Component stepErrors={stepErrors} />} {/* Pass down stepErrors to Step1 */}
      {currentStepIndexState === 1 && <Step2Component stepErrors={stepErrors} />} {/* Pass down stepErrors to Step2 */}
      {/* ... other steps ... */}
 
      {/* Navigation buttons (Next/Previous/Submit) */}
      <button onClick={handlePrevious}>Previous</button>
      <button onClick={handleNext}>Next</button>
      <button onClick={handleSubmit}>Submit</button>
    </div>
  );
};
 
// Example Step Component (Step1Component) - receives stepErrors as props
const Step1Component = ({ stepErrors }) => {
  // ... (Input fields for step 1) ...
  return (
    <div>
      <label htmlFor="name">Name:</label>
      <input type="text" id="name" name="name" />
      {stepErrors.name && <p className="error-message" style={{ color: 'red' }}>{stepErrors.name.error}</p>} {/* Display error using stepErrors prop */}
      {/* ... other input fields for step 1 ... */}
    </div>
  );
};

In this outline:

  • The main MyWizardFormComponent component maintains stepErrors state to hold errors for the currently active step.
  • The onValidationError callback updates stepErrors state with the errors object received.
  • onValidationSuccess and handlePrevious are updated to clear the stepErrors state when validation succeeds or when navigating backward.
  • Step components (like Step1Component) receive stepErrors as props.
  • Within step components, stepErrors prop is used to conditionally render error messages next to the appropriate input fields (near-field error display).

By combining the onValidationError callback with step-specific error state management, you can create Wizard Forms that provide robust error handling and a clear, user-friendly error correction experience.