Dynamic Forms Made Simple: Create and Validate Forms with React Hook Form and Yup

Forms are an essential part of web development, used for everything from registration and user input to search queries and contact forms. However, creating and validating forms can be a tedious and error-prone task. In this tutorial, we’ll learn how to use React Hook Form and Yup to create and validate dynamic forms in a React application with ease.

What is React Hook Form?

React Hook Form is a lightweight library for building performant and scalable forms in React. It provides a simple and intuitive API, allowing us to easily create and manage forms with minimal boilerplate code. React Hook Form uses uncontrolled components to reduce the number of rerenders and improve performance, as well as providing flexible and customizable validation rules and error messages.

What is Yup?

Yup is a JavaScript object schema validation library. It provides a simple and intuitive way to define validation rules for objects and their properties, including nested objects and arrays. Yup supports a wide range of validation types, including string, number, date, boolean, and more. It also allows us to create custom validation functions and error messages, making it easy to tailor our validation logic to our specific needs.

Getting Started

To follow along with this tutorial, you’ll need a basic understanding of React and JSX syntax. You’ll also need to have Node.js and npm installed on your system. If you haven’t already, you can create a new React app using npx create-react-app my-app, then navigate to the app directory with cd my-app.

First, we’ll need to install React Hook Form and Yup as dependencies. Open a terminal window and run the following command:

    
      npm install react-hook-form yup
    
  

Creating a Simple Form

Let’s start by creating a simple registration form with two fields: email and password. We’ll begin by importing the necessary dependencies and creating our form element using React Hook Form:

    
      import React from 'react';
      import { useForm } from 'react-hook-form';

      function RegistrationForm() {
        const { register, handleSubmit } = useForm();

        const onSubmit = (data) => {
          console.log(data);
        };

        return (
          <form onSubmit={handleSubmit(onSubmit)}>
            <label htmlFor="email">Email</label>
            <input id="email" name="email" type="text" ref={register} />

            <label htmlFor="password">Password</label>
            <input id="password" name="password" type="password" ref={register} />

            <button type="submit">Submit</button>
          </form>
        );
      }

      export default RegistrationForm;
    
  

Here we’ve imported the useForm() hook from React Hook Form and called it to create our form component. We’ve also defined an onSubmit() function that will log the form data to the console when the form is submitted. Notice that we’re using the ref callback to register our input elements with React Hook Form. This allows the library to manage the state and validation of our form inputs.

Our form currently has no validation rules, meaning that the user can submit it with empty or invalid input values. Let’s add some validation to our form using Yup.

Adding Validation Rules with Yup

To use Yup with React Hook Form, we need to create a schema validation object using Yup’s API, then pass it to the useForm() hook as an option. Let’s create a new file named validationSchema.js in our project root directory and define our validation schema for our form:

    
      import * as yup from 'yup';

      const validationSchema = yup.object().shape({
        email: yup.string().email('Invalid email').required('Email is required'),
        password: yup.string().min(6, 'Password must be at least 6 characters').required('Password is required'),
      });

      export default validationSchema;
    
  

Here we’ve imported Yup and defined a schema using its API. The schema specifies that the email field must be a valid email address and cannot be empty, and that the password field must be at least 6 characters long and cannot be empty. We’ve also defined custom error messages for each validation rule.

Let’s import our validation schema into our registration form component and pass it to the useForm() hook as an option:

    
      import React from 'react';
      import { useForm } from 'react-hook-form';
      import validationSchema from './validationSchema';

      function RegistrationForm() {
        const { register, handleSubmit, errors } = useForm({
          validationSchema,
        });

        const onSubmit = (data) => {
          console.log(data);
        };

        return (
          <form onSubmit={handleSubmit(onSubmit)}>
            <label htmlFor="email">Email</label>
            <input id="email" name="email" type="text" ref={register} />
            {errors.email && <p>{errors.email.message}</p>}

            <label htmlFor="password">Password</label>
            <input id="password" name="password" type="password" ref={register} />
            {errors.password && <p>{errors.password.message}</p>}

            <button type="submit">Submit</button>
          </form>
        );
      }

      export default RegistrationForm;
    
  

Here we’ve passed our validation schema as an option to the useForm() hook. We’ve also destructured the errors object from the hook, which contains any validation errors for our form inputs. We’ve used the && operator to conditionally render error messages below our input elements if the corresponding validation rule has failed.

Now if we try to submit our form with empty or invalid input values, we’ll see real-time error messages displayed below each input field. If all validation rules pass, our onSubmit() function will be called and the form data will be logged to the console.

Creating Custom Input Components

So far we’ve been using standard HTML input elements to create our form. While this is perfectly fine for simple forms, for more complex forms with custom styling or functionality, it may be beneficial to create custom input components instead. Let’s see how we can use React Hook Form with custom input components.

First, let’s create a new custom input component for our email field. In the same directory as our registration form component, create a new file named EmailInput.js and define our custom input component:

    
      import React from 'react';
      import { useController } from 'react-hook-form';

      function EmailInput({ name, control, rules }) {
        const { field, fieldState } = useController({
          name,
          control,
          rules,
        });

        return (
          <div>
            <label htmlFor={name}>Email</label>
            <input id={name} {...field} />

            {fieldState.error && <p>{fieldState.error.message}</p>}
          </div>
        );
      }

      export default EmailInput;
    
  

Here we’ve imported the useController() hook from React Hook Form and used it to create our custom input component. Inside the component, we’ve defined a field object containing our input element’s props and a fieldState object containing any validation errors for our input. We’ve also passed our input’s name, control (from useForm()), and validation rules to the useController() hook. Notice that we’re using {...field} to spread the field object’s props onto our input element, allowing us to pass values like name, id, and type without having to define them manually.

Let’s import our custom input component into our registration form component and use it in place of our standard email input:

    
      import React from 'react';
      import { useForm } from 'react-hook-form';
      import validationSchema from './validationSchema';
      import EmailInput from './EmailInput';

      function RegistrationForm() {
        const { control, handleSubmit, formState: { errors } } = useForm({
          validationSchema,
        });

        const onSubmit = (data) => {
          console.log(data);
        };

        return (
          <form onSubmit={handleSubmit(onSubmit)}>
            <EmailInput name="email" control={control} rules={{required: 'Email is required', pattern: {value: /^\\S+@\\S+$/i, message: 'Invalid email'}}} />
            {errors.email && <p>{errors.email.message}</p>}

            <label htmlFor="password">Password</label>
            <input id="password" name="password" type="password" ref={register} />
            {errors.password && <p>{errors.password.message}</p>}

            <button type="submit">Submit</button>
          </form>
        );
      }

      export default RegistrationForm;
    
  

Here we’ve imported our custom input component and used it in place of our standard email input. Notice that we’re passing a new rules prop to our input component, which specifies that the email field is required and must match a regular expression for a valid email address. We’ve also abstracted the name, control, and rules props into a reusable component that can be used in other forms throughout our app.

Conclusion

With React Hook Form and Yup, creating and validating dynamic forms in a React application has never been easier. We’ve shown how to create and customize form inputs, add validation rules and error messages, and use external validation schema objects to manage complex form validation. By using these two powerful libraries together, we can streamline our form development process and create more robust and user-friendly forms with less code.