ForgeForm

ReactHookForm Resolver

ForgeForm is designed to integrate flawlessly with React Hook Form, a popular library for form state management in React. To bridge ForgeForm's schema-based validation with React Hook Form's form handling, ForgeForm provides a custom resolver called forgeFormResolver.

Contents

What is a Resolver in React Hook Form?

In React Hook Form, a resolver is a plugin that connects external validation libraries (like Yup, Zod, Joi, and now ForgeForm) to React Hook Form's form validation process. The resolver acts as an adapter: it takes the form data from React Hook Form, passes it to the external validation library for validation based on a defined schema, and then translates the validation results back into a format that React Hook Form can understand and use to display errors and manage form validity.

Installation

To use React Hook Form with ForgeForm, you need to install the react-hook-form package. You can do this using npm or yarn:

npm install react-hook-form
yarn add react-hook-form

ForgeForm Resolver: forgeFormResolver

forgeFormResolver is the bridge that allows you to use your ForgeForm schemas directly within React Hook Form for validation. It enables you to define your form structure and validation rules using ForgeForm's intuitive schema DSL, and then seamlessly apply these rules to your React Hook Form forms.

Basic Usage Example:

Here's a basic example demonstrating how to integrate forgeFormResolver with useForm in React Hook Form:

// src/components/ResolverBasicExample.tsx
import React from 'react';
import { useForm } from 'react-hook-form';
import { createSchema, forgeFormResolver } from 'forgeform';
 
interface ResolverFormData {
  firstName?: string;
  lastName?: string;
}
 
const resolverSchema = createSchema<ResolverFormData>({
  fields: {
    firstName: { type: 'string', required: true, requiredErrorMessage: 'First name is required' },
    lastName: { type: 'string', required: true, requiredErrorMessage: 'Last name is required' },
  },
});
 
const ResolverBasicExample = () => {
  const { register, handleSubmit, formState: { errors } } = useForm<ResolverFormData>({
    resolver: forgeFormResolver(resolverSchema), // Integrate ForgeForm schema using resolver
  });
 
  const onSubmit = (data: ResolverFormData) => {
    alert('Form submitted successfully! (Check console)');
    console.log('Form Data:', data);
  };
 
  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <div>
        <label htmlFor="firstName">First Name:</label>
        <input {...register('firstName')} id="firstName" />
        {errors.firstName && <p style={{ color: 'red' }}>{errors.firstName.message}</p>}
      </div>
      <div>
        <label htmlFor="lastName">Last Name:</label>
        <input {...register('lastName')} id="lastName" />
        {errors.lastName && <p style={{ color: 'red' }}>{errors.lastName.message}</p>}
      </div>
      <button type="submit">Submit</button>
    </form>
  );
};
 
export default ResolverBasicExample;

Explanation of the Example:

  • Import forgeFormResolver:

    import { forgeFormResolver } from 'forgeform';

    Import the resolver function from the ForgeForm library.

  • Define ForgeForm Schema:

    const resolverSchema = createSchema<ResolverFormData>({...});

    Create your validation schema using createSchema as you normally would in ForgeForm.

  • Integrate Resolver in useForm:

    const { register, handleSubmit, formState: { errors } } = useForm<ResolverFormData>({
      resolver: forgeFormResolver(resolverSchema),
    });

    Within React Hook Form's useForm hook, pass the resolverSchema to forgeFormResolver() and assign the result to the resolver option. This tells React Hook Form to use ForgeForm for validation based on the provided schema.

  • Register Input Fields:

    <input {...register('firstName')} id="firstName" />
    <input {...register('lastName')} id="lastName" />

    Register your form input fields using React Hook Form's register function as usual. The field names (e.g., 'firstName', 'lastName') must match the field names defined in your ForgeForm schema.

  • Display Errors:

    {errors.firstName && <p style={{ color: 'red' }}>{errors.firstName.message}</p>}
    {errors.lastName && <p style={{ color: 'red' }}>{errors.lastName.message}</p>}

    React Hook Form's errors object will now be populated with validation errors from ForgeForm. You can access these errors using dot notation (e.g., errors.firstName.message) and display them in your UI.

Benefits of Using forgeFormResolver:

  • Schema Reusability: Define your validation logic once in your ForgeForm schema and reuse it seamlessly in your React Hook Form components.
  • Centralized Validation: Keep your validation rules separate from your component logic, promoting cleaner and more maintainable code.
  • Leverage ForgeForm's Features: Take full advantage of ForgeForm's robust validation engine, built-in field types, custom validators, and sanitization capabilities within your React Hook Form forms.
  • Simplified Form Logic: React Hook Form handles form state management and submission, while ForgeForm resolver handles validation, leading to a more streamlined development experience.
  • Consistent Validation: Ensures consistent validation logic across your application by using a single source of truth – your ForgeForm schemas.

Detailed Examples and Use Cases:

Example 1: Form with Various Field Types and Validations using Resolver

This example builds upon the basic example to showcase more field types and validations within a React Hook Form integrated with forgeFormResolver:

// src/components/ResolverDetailedExample.tsx
import React from 'react';
import { useForm } from 'react-hook-form';
import { createSchema, forgeFormResolver } from 'forgeform';
 
interface ResolverDetailedFormData {
  email?: string;
  age?: number;
  website?: string;
  message?: string;
}
 
const detailedResolverSchema = createSchema<ResolverDetailedFormData>({
  fields: {
    email: { type: 'email', required: true, formatErrorMessage: 'Invalid email format', requiredErrorMessage: 'Email is required' },
    age: { type: 'number', min: 18, minErrorMessage: 'Must be at least 18 years old' },
    website: { type: 'url', formatErrorMessage: 'Invalid URL format' },
    message: { type: 'textarea', maxLength: 150, maxLengthErrorMessage: 'Message cannot exceed 150 characters' },
  },
});
 
const ResolverDetailedExample = () => {
  const { register, handleSubmit, formState: { errors } } = useForm<ResolverDetailedFormData>({
    resolver: forgeFormResolver(detailedResolverSchema),
  });
 
  const onSubmit = (data: ResolverDetailedFormData) => {
    alert('Form submitted! (Detailed Example - Check console)');
    console.log('Detailed Form Data:', data);
  };
 
  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <div>
        <label htmlFor="email">Email:</label>
        <input {...register('email')} type="email" id="email" />
        {errors.email && <p style={{ color: 'red' }}>{errors.email.message}</p>}
      </div>
      <div>
        <label htmlFor="age">Age (18+):</label>
        <input {...register('age', { valueAsNumber: true })} type="number" id="age" />
        {errors.age && <p style={{ color: 'red' }}>{errors.age.message}</p>}
      </div>
      <div>
        <label htmlFor="website">Website URL:</label>
        <input {...register('website')} type="url" id="website" />
        {errors.website && <p style={{ color: 'red' }}>{errors.website.message}</p>}
      </div>
      <div>
        <label htmlFor="message">Message (Max 150 chars):</label>
        <textarea {...register('message')} id="message" />
        {errors.message && <p style={{ color: 'red' }}>{errors.message.message}</p>}
      </div>
 
      <button type="submit">Submit</button>
    </form>
  );
};
 
export default ResolverDetailedExample;

Example 2: Handling Validation Modes and Resolver

React Hook Form offers different validation modes (onSubmit, onBlur, onChange, etc.). forgeFormResolver works seamlessly with these modes. You configure the mode in useForm options as usual, and the resolver will perform validation according to the chosen mode.

// src/components/ResolverModeExample.tsx
import React from 'react';
import { useForm } from 'react-hook-form';
import { createSchema, forgeFormResolver } from 'forgeform';
 
interface ResolverModeFormData {
  username?: string;
}
 
const modeResolverSchema = createSchema<ResolverModeFormData>({
  fields: {
    username: { type: 'string', required: true, minLength: 5, requiredErrorMessage: 'Username is required', minLengthErrorMessage: 'Username must be at least 5 characters' },
  },
});
 
const ResolverModeExample = () => {
  const { register, handleSubmit, formState: { errors }, trigger } = useForm<ResolverModeFormData>({
    resolver: forgeFormResolver(modeResolverSchema),
    mode: 'onBlur', // Validation will trigger onBlur for inputs
  });
 
  const onSubmit = (data: ResolverModeFormData) => {
    alert('Form submitted (Mode Example)! - Check console');
    console.log('Mode Form Data:', data);
  };
 
  const handleCheckManually = async () => {
    const isValid = await trigger(); // Manually trigger validation
    if (!isValid) {
      alert('Manual validation check failed. See errors below.');
    } else {
      alert('Manual validation check passed!');
    }
  };
 
  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <div>
        <label htmlFor="username">Username (Min 5 chars - onBlur validation):</label>
        <input {...register('username')} id="username" />
        {errors.username && <p style={{ color: 'red' }}>{errors.username.message}</p>}
      </div>
 
      <div>
        <button type="submit">Submit</button>
        <button type="button" onClick={handleCheckManually}>Check Validation Manually</button>
      </div>
    </form>
  );
};
 
export default ResolverModeExample;

In this example, mode: 'onBlur' is set in useForm. Validation will now trigger when an input field loses focus (on blur). The trigger() function is also demonstrated to manually trigger validation when needed.

Example 3: Resolver with Nested Fields and Dot Notation

If your ForgeForm schema uses dot notation for nested fields (e.g., "address.city"), forgeFormResolver handles this structure correctly, mapping errors to the nested field paths in React Hook Form's errors object.

(You can refer back to the "React Form State Management" section's example that uses dot notation for nested fields; the resolver will work seamlessly with such schemas.)

Error Handling with forgeFormResolver:

When validation fails through forgeFormResolver, React Hook Form's errors object will be populated with error messages from ForgeForm. The structure of the errors object will directly correspond to your schema's field structure.

For example, if you have a schema like:

const mySchema = createSchema({
  fields: {
    email: { type: 'email', required: true },
    "address.city": { type: 'string', required: true }
  }
});

And validation fails, the errors object from React Hook Form might look like:

errors = {
  email: { type: 'required', message: 'Email is required' },
  address: {
    city: { type: 'required', message: 'City is required' }
  }
}

You can then access and display these errors in your component using paths like errors.email.message and errors.address.city.message.