Form Validation in React using the useForm Hook

Form validation is essential in web applications for data integrity and enhancing user experience. It prevents incorrect data submissions and provides immediate feedback to users. In React applications, form validation is even more critical due to dynamic user interactions and state management.

React offers several ways to handle form validation which include built-in form handling, controlled components, uncontrolled components, and third-party libraries such as Formik and Yup (read our article on React Form Validation using Yup). Using the useForm hook from the react-hook-form library is especially effective. This hook simplifies form validation, offering a robust and flexible way to manage form state and validation. By using the useForm hook, developers can easily implement complex validation rules, handle errors gracefully, and improve overall application performance.

Setting Up the Project

In this section, we'll outline the prerequisites needed, followed by the initial setup steps for our React project. Let's dive in!

Prerequisites

Before you begin, ensure you have:

  • Node.js: Download and install Node.js from its official website.

  • npm or yarn: These are JavaScript package managers. npm comes with Node.js, while yarn needs to be installed separately.

  • Basic React Knowledge: Understand React concepts like components, state, and props.

Initial Project Setup

Quickly set up a new React project, using create-react-app. Open your terminal and run:

npx create-react-app react-form-validation

Next, navigate to the project directory:

cd react-form-validation

To handle form validation, we'll use the react-hook-form library. Install it by running the following command in your project directory:

npm install react-hook-form

In your App.js file, clear out the existing code and import the useForm dependency:

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

Basic Usage of the useForm Hook

In this section, we will explain a straightforward syntax for using the useForm hook.

Defining Form Fields with useForm

To set up a form, define the input fields needed within your form component. For instance, if you want fields for name, email, and password, you can create them like this:

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

  return (
    <form>
      <input placeholder="Name" />
      <input placeholder="Email" type="email" />
      <input placeholder="Password" type="password" />
      <button type="submit">Submit</button>
    </form>
  );
}

Afterward, register these input fields with useForm using the register method. This step ensures that react-hook-form can manage their state and validation:

const { register, handleSubmit } = useForm();

{/* Inside the form JSX */}
<input {...register('name')} placeholder="Name" />
<input {...register('email')} placeholder="Email" type="email" />
<input {...register('password')} placeholder="Password" type="password" />

Each input field utilizes the register function to associate itself with a unique identifier, such as 'name', 'email', or 'password'. This enables react-hook-form to handle their values and validation.

To handle form submission, utilize the handleSubmit function from useForm and define a callback function to process the form data:

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

{/* Inside the form JSX */}
<form onSubmit={handleSubmit(onSubmit)}>
  <input {...register('name')} placeholder="Name" />
  <input {...register('email')} placeholder="Email" type="email" />
  <input {...register('password')} placeholder="Password" type="password" />
  <button type="submit">Submit</button>
</form>

The onSubmit function receives the form data as an argument, allowing you to process it or send it to a server as needed.

Adding Validation Rules

react-hook-form provides various validation options to ensure form fields meet certain criteria. These options include:

  • required: Checks if a field is filled.

  • minLength and maxLength: Ensures the input meets length requirements.

  • pattern: Validates the input against a regular expression.

  • validate: Allows custom validation logic.

To set validation rules, pass an object with validation criteria to the register function for each field.

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

function MyForm() {
  const { register, handleSubmit, formState: { errors } } = useForm();

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

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <div>
        <input
          {...register('name', { required: true, minLength: 2 })}
          placeholder="Name"
        />
        {errors.name && <span>Name is required and must be at least 2 characters long</span>}
      </div>

      <div>
        <input
          {...register('email', { required: true, pattern: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/ })}
          placeholder="Email"
          type="email"
        />
        {errors.email && <span>Please enter a valid email</span>}
      </div>

      <div>
        <input
          {...register('password', { required: true, minLength: 6 })}
          placeholder="Password"
          type="password"
        />
        {errors.password && <span>Password is required and must be at least 6 characters long</span>}
      </div>

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

export default MyForm;

In this example:

  • The name field is required and must be at least 2 characters long.

  • The email field is required and must match a valid email pattern.

  • The password field is required and must be at least 6 characters long.

Custom Validation Logic

For custom validation, use the validate option, which accepts a function returning true or an error message.

Consider the following code snippet:

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

function MyForm() {
  // Destructuring the necessary functions and objects from the useForm hook
  const { register, handleSubmit, formState: { errors } } = useForm();

  // Function to handle form submission
  const onSubmit = (data) => {
    console.log(data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      {/* Input field for username */}
      <div>
        <input
          // Registering the username field with custom validation rules
          {...register('username', {
            // Required validation
            required: 'Username is required',
            // Custom validation to ensure username includes "admin"
            validate: (value) => value.includes('admin') || 'Username must include "admin"'
          })}
          placeholder="Username"
        />
        {/* Displaying error message if username validation fails */}
        {errors.username && <span>{errors.username.message}</span>}
      </div>

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

export default MyForm;

This code sets up a form with a username input field that must be filled out and includes custom validation logic to ensure that the username contains the substring "admin". If the validation fails, an error message is displayed.

Dynamically Displaying Validation Errors

To enable error messages to display when users interact with input fields, such as clicking or moving away from them, you can employ the useForm hook along with the watch function and the onBlur event.

Here's how to achieve this:

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

function MyForm() {
  const { register, handleSubmit, formState: { errors }, watch, trigger } = useForm();

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

  // Watch form fields to trigger validation on change
  const watchFields = watch();

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <div>
        <input
          {...register('name', { 
            required: 'Name is required', 
            minLength: { value: 2, message: 'Name must be at least 2 characters long' } 
          })}
          placeholder="Name"
          onBlur={() => trigger('name')}
        />
        {errors.name && <span className="error-message">{errors.name.message}</span>}
      </div>

      <div>
        <input
          {...register('email', {
            required: 'Email is required',
            pattern: {
              value: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/,
              message: 'Please enter a valid email'
            }
          })}
          placeholder="Email"
          type="email"
          onBlur={() => trigger('email')}
        />
        {errors.email && <span className="error-message">{errors.email.message}</span>}
      </div>

      <div>
        <input
          {...register('password', {
            required: 'Password is required',
            minLength: {
              value: 6,
              message: 'Password must be at least 6 characters long'
            }
          })}
          placeholder="Password"
          type="password"
          onBlur={() => trigger('password')}
        />
        {errors.password && <span className="error-message">{errors.password.message}</span>}
      </div>

      <div>
        <input
          {...register('username', {
            required: 'Username is required',
            validate: (value) => value.includes('admin') || 'Username must include "admin"'
          })}
          placeholder="Username"
          onBlur={() => trigger('username')}
        />
        {errors.username && <span className="error-message">{errors.username.message}</span>}
      </div>

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

export default MyForm;

Here, we utilized the watch function to track changes in the form fields and attached the onBlur event to each input field. When the user moves away from the field, call trigger('fieldName') to validate that specific field.

Below is a preview of the image dynamically displaying the errors if the input does not meet the criteria specified in the form validation rules we created.

After successfully validating the input, we'll be able to view it in the console.

Conclusion

Form validation is essential in web applications to ensure data integrity and provide immediate feedback to users. The useForm hook from the react-hook-form library offers a streamlined approach for managing form state and validation in React applications. By leveraging this hook, developers can easily define form fields, apply complex validation rules, and handle errors gracefully. This not only simplifies the implementation process but also enhances the user experience by providing real-time validation feedback. Integrating react-hook-form results in more efficient and user-friendly forms, making it a valuable tool for React developers.

0
Subscribe to my newsletter

Read articles from Anamhomhe Precious directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Anamhomhe Precious
Anamhomhe Precious