close icon
daily.dev platform

Discover more from daily.dev

Personalized news feed, dev communities and search, much better than what’s out there. Maybe ;)

Start reading - Free forever
Start reading - Free forever
Continue reading >

Create powerful and flexible forms with React Hook Form

Create powerful and flexible forms with React Hook Form
Author
Vaibhav Khulbe
Related tags on daily.dev
toc
Table of contents
arrow-down

🎯

Someone inputting his details and hitting that ‘Submit’ button is one of the most widely used and reliable forms of getting the data from a user on any website. We call such web entities ‘forms’ and forms on a webpage are nothing but a collection of elements that allows a user to enter data that is then sent over to the server for further processing.

While the traditional approach to build, code and gather information from a form in a webpage can simply be done with the power of HTML code, but when the website gets complex or scales up in size, (say, when you are on an e-commerce website), we need to leverage some of the external libraries that were meant to get things done easily and are able to integrate with other tech stacks quickly.

One such open-source library to handle all things related to forms is React Form Hook and in this article, we will get to know how to use this with a suitable example.

About the library

React Form Hook is a React library that is used to make performant, flexible, and extensible forms with easy-to-use validation support.

Sure, there have been other libraries like Formik, React Final Form, etc. that falls under similar lines but here’s why it goes above all those:

  1. Small in size: it’s a tiny library with no other dependencies to install.
  2. Performance: the best part is that it minimizes the re-renders required and also mounts up fairly quickly, thereby increasing the overall experience.
  3. Uses existing HTML: you can leverage the powers of HTML to work with this library.
  4. Less code and more features: almost every form handling scenario is covered all in fairly fewer lines of code.

Now that we have some information, here’s what the rest of the blog will tell you about:

  • Installation
  • API references with examples
  • A quick but powerful demo
  • Conclusion

Installation

Time to make our forms powerful with the use of React Hook Forms! Fire up your terminal and then execute the following commands which makes a new React project:


npx create-react-app react-form-hook-demo 
cd react-form-hook-demo
npm start

Your default React project should be running in your browser by now. Next, we need to add the library. Use the following command to install:

npm install react-hook-form

API References

Before we move ahead typing code carelessly, we need to know how this library works, or more precisely what are the different APIs it has and how they function. Here are some common ones:

  1. The useForm API: this is one of those functions which you will be calling first before you apply any handling logic to your existing forms. It takes some optional arguments like mode, defaultValues, shouldFocusError, etc.

const { register } = useForm({
  mode: 'onSubmit',
  reValidateMode: 'onChange',
  defaultValues: {},
  resolver: undefined,
  context: undefined,
  criteriaMode: "firstError",
  shouldFocusError: true,
  shouldUnregister: true,
})

As for its methods, take a look at how these are used:

  • register: allows you to register an input/select ref and apply validation rules into React Hook Form based on both HTML default and custom validations.

<input name="test" ref={register} />

<input
  name="test"
  ref={
    register({
      required: true
    })
  }
/>

  • unregister: allows you to unregister a single input or an array of inputs.

const {  unregister } = useForm();

<button type="button" onClick={() => unregister("lastName")}>unregister</button>

  • errors: it’s an object which contains form errors and error messages corresponding to each field.

Note that this will get deprecated in the next major version (i.e. v7). formState is likely to be the alternative.


const { errors } = useForm();

<input name="singleErrorInput" ref={register({ required: true })} />
{errors.singleErrorInput && "Your input is required"}

  • handleSubmit: it’s a function that will pass the form data when form validation is successful and can be invoked remotely as well.

const { register, handleSubmit } = useForm();
const onSubmit = (data, e) => console.log(data, e);
const onError = (errors, e) => console.log(errors, e);

<form onSubmit={handleSubmit(onSubmit, onError)}>
      <input name="firstName" ref={register} />
      <input name="lastName" ref={register} />
      <button type="submit">Submit</button>
 </form>

  • setError: allows you to manually set one or more errors.

const { setError, errors } = useForm();

<input 
  name="username"
  type="text"
  onChange={() => {
      setError("username", {
      type: "manual",
      message: "Dont Forget Your Username Should Be Cool!"
    });
  }}
  ref={register} />

  {errors.username && <p>{errors.username.message}</p>}

The Controller API: it’s a wrapper component that makes it easier to work with external components from other libraries and frameworks like React-Select, Material UI, etc.


<Controller
  control={control}
  name="test"
  render={(
    { onChange, onBlur, value, name, ref },
    { invalid, isTouched, isDirty }
  ) => (
    <Checkbox
      onBlur={onBlur}
      onChange={(e) => onChange(e.target.checked)}
      checked={value}
      inputRef={ref}
    />
  )}
/>

The useWatch API: if you want to reduce the re-rendering at the component level of your form then this API ‘watches’ for any changes to result in better performance.


function IsolateReRender({ control }) {
const firstName = useWatch({
            control,
        name: 'firstName',    
        defaultValue: 'default'  
 });

return <div>{firstName}</div>; 
}

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

  return (
    <form onSubmit={handleSubmit(data => console.log("data", data))}>
      <input ref={register} name="firstName" />
      <input ref={register} name="last" />
      <IsolateReRender control={control} />

      <input type="submit" />
    </form>
  );
}

A quick but powerful demo

Okay, now we know most of the APIs and functions to use from this library. Let’s put that into work by creating something fruitful. How about we make the following form?

Here is what’s happening in the form interface:

We have six basic elements in the form; 4 text fields, 1 select field, and lastly a button. We will be adding some important form validations in each of these elements except the select field. Here are all the rules to be implemented:

  1. The First Name: it should not be empty and should have a minimum length of 5 characters.
  2. The Last Name: it is required and has a maximum of 15 characters.
  3. The Email: like others, this cannot be left blank.
  4. Username: this also should not be blank but also we have a custom validation where only the string “John” will be accepted. The error will pop out asynchronously after 3 seconds.

Finally, when you click on the Submit button, an alert is shown along with the entered details. If any validation check goes wrong, the error will be shown instantly and the data won’t submit.

Okay, enough of the information, let’s quickly begin coding all of this..

1. Code the interface

Open the index.js file and inside the return statement, start by using a <form> element. Make sure you don’t add any action or method attributes. Everything will be handled by the library.

Our first label will be of the “First Name” field. Make sure you add a suitable placeholder value to its <input> element. Here we added “John”. The same goes for the “Last Name” field.

Then we have the “Email” field. Here we can specify that the type of the form element is “email” so that it knows beforehand that a proper email syntax should be added. Why not use some good old powers of HTML!

The “Username” field is the same as the name fields. So, it’s important to add a placeholder value here too.

Up next the select field is to be added with the name attribute along with multiple options you want to provide in the drop-down. Here we need to make sure that each of the elements must have a proper “value” attribute attached to it. It will be used later when we use the library.

After you have coded all the form interface you will have a code similar to this:


<form>
     <h1>Sign Up</h1>

     <label>First Name</label>
     <input
       name="name"
       placeholder="John" />

     <label>Last Name</label>
     <input
       name="name"
       placeholder="Doe"  />

     <label>Email</label>
     <input
       name="email"
       type="email"
       placeholder="john@doe.com" />

     <label>Username</label>
     <input
       name="username"
       placeholder="Only John is accepted" />

     <label>How you got to know about us?</label>
     <select name="info">
       <option value="">Select source...</option>
       <option value="Twitter">Twitter</option>
       <option value="Facebook">Facebook</option>
       <option value="Other">Other</option>
     </select>

     <input type="submit" />
 </form>

2. Power up the form with validation

With the single useForm hook, we will be doing almost all of the operations to make it functional.

Start by importing the hook from the library:


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

Now it’s time to register all the inputs we have used in the HTML code above. For that, we will be calling the imported useForm hook as:


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

We have to handle the functioning of the data submission. For that, we will be using the onSubmit attribute of the <form> element. We call the handleSubmit function passing on a new onSubmit() method to it. We haven’t added any code to this new method so let’s do that first.

Make this new method after you register the inputs. We will pass on the data obtained from the form. We use JavaScript’s alert() method inside which we use string interpolation so that we have a dynamic text string shown in the alert box. We can also log down the data into the console just to make sure our object prints out as we are expecting.


const onSubmit = (data) => {
   console.log(data);
   alert(
     `Here are your details: \nName: ${data.name} \nEmail: ${data.email} \nUsername: ${data.username} \nSource: ${data.info}`
   );
 };

With the power of refs in React, we access a particular DOM node in the render() method. Hence, for all the 4 inputs and the 1 select element of the form, we add the ref attribute which calls the register function of the useHook API.

Now here comes all the goodies! Everything we need to validate, we can pass inside this register function. This makes the form very flexible to change and each element can have similar or dissimilar validations as you like.

For the “First Name” and the “Last Name” fields, we pass on the boolean true value to the required property along with a minLength of 5 and max length of 15 characters.

To apply the error messages to a specific field (say, name), simply use the errors prop along with the property name like errors.name or errors.name.type. Then we can add any HTML element inside this check like a simple paragraph that says “First Name is required!”.


{errors.name && errors.name.type === "required" && (
    <p>First name is required!</p>
)}

As for the message for the minimum length check, we check the errors.name.type === "minLength" and pass on the custom message.


{errors.name && errors.name.type === "minLength" && (
       <p>This requires min length of 5!</p>
)}

But what about the Username field where we only want specific data to be valid? For this to work as expected, we need to use the validate property of the register function. The value of this property can be a separate function where the actual logic of the validation can be stored.

We are using the sleep() method so that the error is shown after 3 seconds to the user and this will only fire if the value of the string is not equal to “John”.


const validateUserName = async (value) => {
   await sleep(3000);

   if (value === "John") return true;

   return false;
 };

Make sure you predefine the async event code for this to function:


const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

Sweet! The form should now be backed by powerful validations and is equally flexible with its elements. Test out your new form and you will see an alert box with all the inputs.

Here’s the entire code along with accessibility attributes we wrote for making this form:


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

 const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

 const onSubmit = (data) => {
   console.log(data);
   alert(
     `Here are your details: \nName: ${data.name} \nEmail: ${data.email} \nUsername: ${data.username} \nSource: ${data.info}`
   );
 };

 const validateUserName = async (value) => {
   await sleep(3000);

   if (value === "John") return true;

   return false;
 };

 return (
   <form className="App" onSubmit={handleSubmit(onSubmit)}>
     <h1 aria-level="1">Sign Up</h1>

     <label htmlFor="First name">First Name</label>

     <input
       name="name"
       aria-invalid={errors.name ? "true" : "false"}
       placeholder="John"
       ref={register({ required: true, minLength: 5 })}
     />

     {errors.name && errors.name.type === "required" && (
       <p role="alert">First name is required!</p>
     )}

     {errors.name && errors.name.type === "minLength" && (
       <p role="alert">This requires min length of 5!</p>
     )}

     <label htmlFor="Last name">Last Name</label>
     <input
       name="name"
       placeholder="Doe"
       aria-invalid={errors.name ? "true" : "false"}
       ref={register({ required: true, minLength: 5, max: 15 })}
     />

     {errors.name && errors.name.type === "minLength" && (
       <p role="alert">This requires a min length of 5!</p>
     )}

     <label>Email</label>
     <input
       name="email"
       type="email"
       aria-invalid={errors.email ? "true" : "false"}
       placeholder="john@doe.com"
       ref={register({ required: true })}
     />

     <label>Username</label>

     <input
       name="username"
       aria-invalid={errors.username ? "true" : "false"}
       placeholder="Only John is accepted"
       ref={register({
         required: true,
         validate: validateUserName
       })}
     />

     <label>How you got to know about us?</label>

     <select
       name="info"
       aria-invalid={errors.info ? "true" : "false"}
       ref={register({ required: true })}
     >
       <option value="">Select source...</option>
       <option value="Twitter">Twitter</option>
       <option value="Facebook">Facebook</option>
       <option value="Other">Other</option>
     </select>

     <input type="submit" />
   </form>
 );
}

Conclusion

This is just the tip of an iceberg when you start making forms with React Hook Form. With its easy-to-go syntax and great features, you can also use it with TypeScript, create wizard-like multiple forms and even use their online form builder where you simply add the elements you want along with rules, and bam! You get your generated code in one click.

Why not level up your reading with

Stay up-to-date with the latest developer news every time you open a new tab.

Read more