Hone logo
Hone
Problems

Robust Form Validation System in React with TypeScript

Building reusable and flexible form validation systems is a crucial skill for any React developer. This challenge asks you to create a component that handles complex form validation, including custom rules, dependency-based validation, and clear error messaging, all while leveraging the power of TypeScript for type safety. This system will be a building block for more complex forms in your applications.

Problem Description

You are tasked with creating a ValidationSystem component in React using TypeScript. This component should accept a form definition object and render a form with input fields and validation logic. The form definition will specify the fields, their types, validation rules, and any dependencies between fields. The component should dynamically generate input fields based on the form definition, apply validation rules as the user types, and display clear and concise error messages.

Key Requirements:

  • Dynamic Form Generation: The component should dynamically generate input fields (text, number, email, select) based on the formDefinition object.
  • Validation Rules: Support various validation rules: required, minLength, maxLength, pattern (regex), email, number, and a custom validation function.
  • Dependency-Based Validation: Validation of one field can depend on the value of another field. For example, if "Country" is "USA", then "State" is required.
  • Real-time Validation: Validation should occur as the user types (onBlur or onChange).
  • Clear Error Messages: Display error messages directly below the corresponding input field. Error messages should be user-friendly and informative.
  • Form Submission Handling: Provide a mechanism to submit the form and handle validation errors before submission.
  • TypeScript: Use TypeScript to ensure type safety and improve code maintainability.

Expected Behavior:

  • The component should render a form with input fields based on the formDefinition.
  • As the user interacts with the form, validation rules should be applied in real-time.
  • Error messages should be displayed immediately below invalid fields.
  • The component should provide a way to submit the form, and prevent submission if there are validation errors.
  • The component should handle different input types correctly (text, number, email, select).

Edge Cases to Consider:

  • Empty formDefinition object.
  • Invalid validation rules in the formDefinition.
  • Fields with dependencies that are not properly defined.
  • Handling of asynchronous validation rules (e.g., checking if a username is available). (While not required to implement asynchronous validation, consider how your design would accommodate it).
  • Large and complex form definitions.

Examples

Example 1:

Input:
formDefinition = {
  fields: [
    {
      name: 'firstName',
      type: 'text',
      label: 'First Name',
      rules: ['required', 'minLength:3', 'maxLength:20']
    },
    {
      name: 'email',
      type: 'email',
      label: 'Email',
      rules: ['required', 'email']
    }
  ],
  onSubmit: (values) => { console.log(values); }
}
Output:
A form with two input fields: "First Name" (text) and "Email" (email).  Validation errors are displayed below the fields if they are invalid.  A submit button is present.
Explanation: The component dynamically generates the form fields and applies the specified validation rules.

Example 2:

Input:
formDefinition = {
  fields: [
    {
      name: 'country',
      type: 'select',
      label: 'Country',
      options: ['USA', 'Canada', 'Mexico'],
    },
    {
      name: 'state',
      type: 'text',
      label: 'State',
      rules: ['required'],
      dependencies: {
        country: 'USA'
      }
    }
  ],
  onSubmit: (values) => { console.log(values); }
}
Output:
A form with a "Country" (select) and "State" (text) field. The "State" field is initially disabled. When "Country" is selected as "USA", the "State" field becomes enabled and is required.
Explanation: The component handles dependencies between fields, enabling/disabling fields and applying validation rules based on the values of other fields.

Example 3: (Complex Scenario)

Input:
formDefinition = {
  fields: [
    {
      name: 'username',
      type: 'text',
      label: 'Username',
      rules: ['required', 'minLength:5', 'pattern:/^[a-zA-Z0-9]+$/'],
      customValidation: (value) => {
        // Simulate checking if username exists in database
        return new Promise((resolve) => {
          setTimeout(() => {
            if (value === 'existinguser') {
              resolve('Username already exists.');
            } else {
              resolve(null);
            }
          }, 500);
        });
      }
    }
  ],
  onSubmit: (values) => { console.log(values); }
}
Output:
A form with a "Username" (text) field.  After a short delay (500ms), if the username is "existinguser", an error message "Username already exists." is displayed.
Explanation: The component handles custom validation functions, including asynchronous ones (though full asynchronous handling is not required).

Constraints

  • Time Limit: You have 4 hours to complete this challenge.
  • Dependencies: You can use standard React and TypeScript libraries. No external validation libraries are allowed.
  • Performance: The component should render efficiently, even with a large number of fields. Avoid unnecessary re-renders.
  • Code Quality: Write clean, well-documented, and maintainable code.
  • Input Format: The formDefinition object must adhere to the structure described in the problem description.

Notes

  • Consider using React Hooks (useState, useEffect) to manage form state and validation logic.
  • Think about how to make your component reusable and configurable.
  • Focus on creating a robust and flexible validation system that can handle various scenarios.
  • Error messages should be clear and actionable for the user.
  • While asynchronous validation is not required, design your component to be easily extensible to support it. Consider how you would handle promises and loading states.
  • Prioritize a clean and well-structured codebase over implementing every possible feature. A solid foundation is more important than a fully-featured but messy solution.
Loading editor...
typescript