Hone logo
Hone
Problems

Advanced Form Validation with Dynamic Rules in React

This challenge requires you to build a robust and flexible form validation system in React using TypeScript. The system should allow for defining complex, dynamic validation rules that can be applied to form fields based on various conditions. This is a common requirement in real-world applications where forms can have intricate data entry logic and user feedback needs to be precise.

Problem Description

You need to create a React component that renders a form with several input fields. Each input field will have its own set of validation rules. The key challenge is to design a system where these rules can be:

  • Defined dynamically: Rules should not be hardcoded directly in the component's JSX. Instead, they should be provided via a configuration object.
  • Conditional: Some validation rules should only be applied if certain conditions are met (e.g., another field has a specific value).
  • Chained: Multiple rules can be applied to a single field, and all should be checked.
  • Customizable: The system should allow for easy addition of new validation rule types.
  • User-friendly feedback: The form should display clear error messages next to the relevant fields when validation fails.

Key Requirements:

  1. Configuration-Driven Validation: All validation rules for all fields must be defined in a configuration object. This object should map field names to an array of validation rule definitions.
  2. Rule Structure: Each validation rule definition should include:
    • type: A string identifying the type of validation (e.g., 'required', 'minLength', 'maxLength', 'pattern', 'custom').
    • message: The error message to display if the validation fails.
    • condition (optional): A function that takes the entire form's current values and returns true if the rule should be applied, false otherwise.
    • options (optional): An object containing parameters specific to the rule type (e.g., minLength: 5, pattern: /^[a-zA-Z]+$/).
  3. Validation Trigger: Validation should occur on blur (when a field loses focus) and on form submission.
  4. Error Display: Error messages should be displayed below the input field they correspond to.
  5. Form Submission: The form should only submit (or simulate submission, e.g., by logging to the console) if all fields are valid.
  6. TypeScript: All code, including the validation logic and the form component, must be written in TypeScript.

Expected Behavior:

  • When a user types into an input, no errors are shown until they blur the field or submit the form.
  • Upon blurring a field, if it fails any applicable validation rule, the corresponding error message should appear.
  • On form submission, all fields are validated. If any field has an error, the form submission is prevented, and all applicable errors are displayed.
  • If a conditional rule's condition function returns false, that rule is skipped, even if the field's value would otherwise fail it.

Edge Cases:

  • Fields with no validation rules defined should always be considered valid.
  • Handling of null or undefined values for optional fields.
  • Ensure asynchronous validation is not a requirement for this challenge (all validations are synchronous).

Examples

Let's consider a user registration form.

Example 1: Basic Fields

Input Configuration:
{
  "username": [
    { "type": "required", "message": "Username is required." },
    { "type": "minLength", "message": "Username must be at least 5 characters long.", "options": { "value": 5 } }
  ],
  "email": [
    { "type": "required", "message": "Email is required." },
    { "type": "pattern", "message": "Please enter a valid email address.", "options": { "value": /^[^\s@]+@[^\s@]+\.[^\s@]+$/ } }
  ]
}

Form State (before blur/submit):
{
  "username": "",
  "email": ""
}

User interacts: Types "us" in username, blurs. Types "test@" in email, blurs.
Form State (after blur):
{
  "username": "us",
  "email": "test@"
}

Expected Output (Errors shown after blur):
Username: "Username must be at least 5 characters long."
Email: "Please enter a valid email address."

Example 2: Conditional Validation

Input Configuration:
{
  "password": [
    { "type": "required", "message": "Password is required." },
    { "type": "minLength", "message": "Password must be at least 8 characters long.", "options": { "value": 8 } }
  ],
  "confirmPassword": [
    { "type": "required", "message": "Please confirm your password." },
    {
      "type": "custom",
      "message": "Passwords do not match.",
      "condition": (values) => values.password === values.confirmPassword,
      "options": { "value": "passwordsMatch" } // Custom option to identify rule
    }
  ],
  "accountType": [
    { "type": "required", "message": "Please select an account type." }
  ],
  "businessName": [
    {
      "type": "required",
      "message": "Business name is required for business accounts.",
      "condition": (values) => values.accountType === "business"
    },
    {
      "type": "minLength",
      "message": "Business name must be at least 5 characters.",
      "condition": (values) => values.accountType === "business",
      "options": { "value": 5 }
    }
  ]
}

Scenario: User selects "business" for accountType, then enters an empty string for password, and clicks submit.

Form State (before submit):
{
  "username": "valid_user",
  "email": "valid@example.com",
  "password": "",
  "confirmPassword": "",
  "accountType": "business",
  "businessName": ""
}

Expected Output (Errors shown on submit):
Password: "Password is required."
Business Name: "Business name is required for business accounts."

Example 3: All Valid Scenario

Input Configuration: (Same as Example 2)

Form State (after user fills everything correctly and submits):
{
  "username": "user123",
  "email": "user@example.com",
  "password": "SecurePassword123!",
  "confirmPassword": "SecurePassword123!",
  "accountType": "personal",
  "businessName": "" // This field is optional for personal accounts
}

Expected Output: Form submission proceeds. No errors are displayed.

Constraints

  • The validation system must be implemented entirely in TypeScript.
  • The form component should be a functional component using React Hooks (useState, useCallback, etc.).
  • No external validation libraries (like react-hook-form, formik, yup) should be used. Implement the validation logic from scratch.
  • The maximum number of fields in a form is 20.
  • The maximum number of rules per field is 5.
  • The validation logic for each rule should execute in under 5ms.

Notes

  • Consider how you will manage form state and error state within the React component.
  • Think about how to make the validation logic reusable and easily extensible for new rule types.
  • The condition function provides a powerful way to create complex dependencies between fields.
  • For the custom rule type, you'll need to define how a "custom" validation check is performed within your logic. This might involve passing a function or a specific identifier in the options.
  • The goal is to create a framework for validation, not just a single form. The component should be able to render any form based on the provided configuration.
Loading editor...
typescript