React Hook Form
Yup
Validation
React
Web development
Creating Forms using React Hook Form and Yup

by: Sangeeta Saha

April 22, 2024

titleImage

Introduction

Creating a form within React application involves numerous steps and can be quite tedious if it is a complex one. We need to manage the state (both initial state and changes made by user), validations and form submission.

A few libraries have come up to stream-line this process, such as SurveyJS, Formik, React Hook Form and React Final Form. Out of these, React Hook Form has become very popular. Its main advantages are:

  • Easy form state management.
  • Ability to seamlessly integrate with other validation libraries.
  • Performance improvement due to lesser re-renders.

React Hook Form can handle form validation by itself, as well as integrate with other schema declaration and validation libraries such as Yup, Zod and Mui. In this blog, we will cover the basics steps of creating a react form using React Hook Form for state management and submission and Yup for validation. The steps would be as follows:

  1. Adding Form controls
  2. Setting Default values
  3. Disabling form fields
  4. Validation
  5. Form submission

1. Adding Form controls

Let us take the example of a form within a library application. This form will be used to add new book details to the application. We can have following fields :

  1. Serial Number
  2. Title
  3. Author
  4. Price
  5. Date of Purchase

Serial Number field will be auto populated, its value can be fetched from the database. We will need to enter appropriate values in the remaining fields. A new record should be created in the database on clicking the Submit button.

my library

First, we need to install react-hook-form library

## Code snippet 1
npm install react-hook-form

Then, we can import the useForm hook and destructure the register function. Each of the form controls need to be registered with the form object. This enables react-hook-form to start tracking form values.

// Code snippet 2
import { useForm } from 'react-hook-form';
export const AddBookForm = () => {
  const form = useForm();
  const { register } = form;
  return(
    <div>
      <form>
        <div className="wrapper">
          <label htmlFor="serialno"> Serial # </label>
          <input type="text" id="serialno" {...register("serialno")} />                                     
        </div>                
        <div className="wrapper">
          <label htmlFor="title">  Title </label>
          <input type="text" id="title"  {...register("title")} /> 
        </div>
        <div className="wrapper">
          <label htmlFor="author"> Author </label>
          <input type="text" id="author"  {...register("author")}/> 
        </div>
        <div className="wrapper">
          <label htmlFor="price"> Price in</label>
          <input type="text" id="price"  {...register("price")}/> 
        </div>
        <div className="wrapper">
          <label htmlFor="dop"> Date Of Purchase</label>
          <input type="date" id="dop"  {...register("dop")}/> 
        </div>
        <button> Submit </button>  
      </form>
    </div>
  )
}

2. Setting Initial values

We can set initial values for the form fields, both synchronously as well as asynchronously.

This is done by passing the defaultValues object as a parameter to the useForm hook. Example of setting default values synchronously -

// Code snippet 3
const form = useForm ({
        defaultValues:{
            serialno: "123",
            author:"",
            title:"",
            price: 0,
            dop: "2021-01-01"
        }       
        });

Example of setting default values asynchronously – (For demo purposes we are using jsonplaceholder API)

// Code snippet 4
const defaultPrice = 1;
const form = useForm ({
        defaultValues: async () => {
            const response = await fetch(
               "https://jsonplaceholder.typicode.com/posts/12"
            );
            const data = await response.json();
            return({
              serialno: data.id,
              author:"",
              title:"",
              price: defaultPrice.toFixed(2),
              dop: moment(new Date()).format("YYYY-MM-DD")})
            }        
        });

The form will now appear as

my library 12

3. Disabling form fields

Since we are populating the Serial Number from the backend, we can disable this field.

We can disable, either using the disabled attribute of the input field or adding disabled property to the register function.

// Code snippet 5
<input type="text" id="serialno" disabled {...register("serialno")} />
OR
<input type="text" id="serialno" {...register("serialno", {disabled:true})} />

Setting the disabled property to true using react-hook-form, will make the value of the field as undefined, so we cannot use it in this example. It can be used in a scenario where disabled field’s value is not being used. The disabled property can also be conditionally set to true based on the value of any other form field.

4. Validation

React-hook-form can be very easily integrated with yup. Validation can be carried out by yup and error messages displayed by react-hook-form. To integrate with yup we need to install the following dependencies

## Code snippet 6
npm i yup @hookform/resolvers

Then, we can create a yup schema object and integrate it with the yup form object. For each form field, we can specify the validations along with the error message to be displayed in case validation fails.

// Code snippet 7
import { yupResolver } from "@hookform/resolvers/yup";
import * as yup from "yup"; 
const schema = yup.object({
    serialno: yup.string().required("Serial # is required"),
    title: yup.string().required("Title is required"),
    author: yup.string().required("Author is required"),
    price: yup.number()
    .typeError("Please enter a number")
    .positive("Price cannot be negative")    
    .required("Price is required")
    .test(
        "decimal-places",
        "Only up to 2 decimal places allowed",
        (value) => value == null || /^\d+(\.\d{1,2})?$/.test((value || "").toString())
      ),
    dop: yup.date()
    .typeError("Please select a date")
    .required("Date of Purchase is required")
    .max(new Date(),"Date cannot be greater than today")
})
export const AddBookForm = () => {
    const form = useForm ({resolver:yupResolver(schema)});
. . .

Validations for the first three fields are simple, they need a string value and must not be empty. For Price, we specify that it is a positive number and only up to 2 decimal places is allowed. Date of Purchase must be a date not greater than today.

The error messages are displayed with help of errors object.

// Code snippet 8
const { register, formState } = form;
const {errors} = formState;
<div className="wrapper">
                    <label htmlFor="serialno"> Serial # </label>
                    <input type="text" id="serialno"  {...register("serialno")} />                                    
                </div>
                <p className='error'> {errors.serialno?.message}</p>                
                <div className="wrapper">
                    <label htmlFor="title">  Title </label>
                    <input type="text" id="title"  {...register("title")} /> 
                </div>
                <p className='error'> {errors.title?.message}</p> 
 . . .

Error messages will appear as

my library 12 red

By default, validation occurs when the form is submitted, i.e. the error messages will be displayed when user clicks on the Submit button. Validation mode can be changed using the mode key in the useForm hook object parameter.

// Code snippet 9
const form = useForm ({mode:"onSubmit"})

As mentioned, by default the mode is “onSubmit,” it can be changed to the following values:

  • onBlur – validation is triggered when user leaves a form field.
  • onTouched - validation is triggered when user first leaves a form field and while making changes to the input.
  • OnChange - validation is triggered on every change to the input.
  • all – this is a combination of “onBlur” and “onChange” modes.

Based on the application requirements we can set the mode to any of the above.

5. Submitting a form

Normally form submission is handled by a submitHandler function which will calls an API and will contain both the success and error paths. With react-hook-forms, there is a small difference. The submitHandler function will handle only success path and a separate errorHandler function will handle the error path. Both these handler functions are provided as input to the form's handleSubmit method. Form data is available as data object to the submitHandler function. Similarly, errors object is available to the errorHandler function. In our example, handler functions will just print the data and the errors object. In actual application, submitHandler will call the post API to create a new book record in the database. Within the errorHandler, further processing can be done or the error message can be displayed in application or logs.

// Code snippet 10
const form = useForm ();
const { register, handleSubmit } = form;
const submitHandler = (data) => {
   console.log("Data which will be submitted is", data);
}
const errorHandler = (errors) => {        
   console.log("Error in form submission", errors);
}
. . .
<form onSubmit={handleSubmit(submitHandler, errorHandler)}>
. . .

If form is successfully submitted, we get the following output.

data

However, if the code fails, errors object will be displayed as

error

Sometimes, we may want to disable form submission until all fields are filled. We can do so with the help of isDirty and isValid flags. They are available from the formState object and can be used within the Submit button in following manner.

// Code snippet 11
const {errors, isValid, isDirty} = formState;
. . .
<button disabled = {!isValid || !isDirty}> Submit </button>

We can use reset method and isSubmitSuccessful flag to reset all form values after Submit button is clicked. Once Submit button is clicked, isSubmitSuccessful has value true if form submission is successful.

// Code snippet 11
const { register, handleSubmit, formState, reset} = form;
const {errors, isValid, isDirty, isSubmitSuccessful} = formState;
useEffect(() => {
  if(isSubmitSuccessful) {
    reset();
  }
}, [isSubmitSuccessful])}

Conclusion

In this article we have seen how to create and manage form states using react-hook-form. We have created a yup schema object and used it for validation. We have gone through the various validation modes and finally form submission and error handling. Using such a library greatly simplifies and standardizes the form creation process. A schema object helps define all validation rules in one place along with the error messages. Validation code is thus localized and is not spread throughout the entire form, within various events of each field. The primary benefit however is performance, as the form does not re-render every time user input changes. This is because React Hook form follows uncontrolled form behavior.

Hope you have found this article useful and will consider using react hook forms for your applications. Thanks for reading.

contact us

Get started now

Get a quote for your project.
logofooter
title_logo

USA

Edstem Technologies LLC
254 Chapman Rd, Ste 208 #14734
Newark, Delaware 19702 US

INDIA

Edstem Technologies Pvt Ltd
Office No-2B-1, Second Floor
Jyothirmaya, Infopark Phase II
Ernakulam, Kerala 682303

© 2024 — Edstem All Rights Reserved

Privacy PolicyTerms of Use