Managing Forms In ReactJS Using React-Hook-Form

8 min read

react hook form

The React-Hook-Form Library

In this post we've seen how to manage a very basic form in ReactJS using the built-in useState hook. While that works just fine, we will inevitably come across different form-related requirements such as validation which will require us to handle everything ourselves. To help us create better forms with React, we'll surely try to look on the internet if there are libraries that address most of use-cases involving forms. And yes, there are TONS of them. But in this post, I'll try to walk you through with the basics of my favorite form library for React - the React Hook Form.

According to the react-hook-form docs, the library is a:

Performant, flexible and extensible forms with easy-to-use validation.

That sounds promising, and that's because it is! Now, to get started, let us install install it first.

  npm install react-hook-form

Using React-hook-form

To demonstrate the basic features of the library, let us create a simple form:

App.tsx
import React from 'react';

function App() {
  // let's create a simple form first
  return (
    <form>
      <input placeholder='First Name' />
      <input placeholder='Last Name' />
      <input type='email' placeholder='Email' />
      <button type='submit'>Submit</button>
    </form>
  );
}

Now, let us throw react-hook-form into the mix by importing a hook called useForm from it:

App.tsx
import * as React from 'react';
import { useForm } from 'react-hook-form';

function App() {
  const myForm = useForm();

  return (
    <form>
      <input placeholder='First Name' />
      <input placeholder='Last Name' />
      <input type='email' placeholder='Email' />
      <button type='submit'>Submit</button>
    </form>
  );
}

Let us define now the type or shape of the data that we want to submit to the server and then supply that type to the useForm generic. So we'll have:

App.tsx
import * as React from 'react';
import { useForm } from 'react-hook-form';

type UserForm = {
  firstName: string;
  lastName: string;
  email: string;
};

function App() {
  const myForm = useForm<UserForm>();

  return (
    <form>
      <input placeholder='First Name' />
      <input placeholder='Last Name' />
      <input type='email' placeholder='Email' />
      <button type='submit'>Submit</button>
    </form>
  );
}

The next thing to do is to supply an options object to the useForm hook. To keep things simple, we'll only supply the defaultValues and the mode which tells the library when to validate the form. Having the mode as onSubmit means that only when the form is submitted the validations will run.

App.tsx
const myForm = useForm<UserForm>({
  mode: 'onSubmit', // other values can be: onChange | onBlur | onSubmit | onTouched
  defaultValues: {
    firstName: '',
    lastName: '',
    email: '',
  },
});

To make the form working, we need to 'register' our inputs. Also, we need to provide a submit handler for our <form />. React-hook-form will give us all that we need to achieve that. We will use the register function to register our inputs and the handleSubmit function to submit our form.

App.tsx
import * as React from 'react';
import { useForm, SubmitHandler } from 'react-hook-form';

type UserForm = {
  firstName: string;
  lastName: string;
  email: string;
};

function App() {
  const myForm = useForm<UserForm>({
    mode: 'onSubmit', // other values can be: onChange | onBlur | onSubmit | onTouched
    defaultValues: {
      firstName: '',
      lastName: '',
      email: '',
    },
  });

  const { register, handleSubmit } = myForm;

  // the type of the formData will be `UserForm`
  const onSubmit: SubmitHandler<UserForm> = (formData) => {
    console.log(formData);

    //submit the form the server
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input placeholder='First Name' {...register('firstName')} />
      <input placeholder='Last Name' {...register('lastName')} />
      <input type='email' placeholder='Email' {...register('email')} />
      <button type='submit'>Submit</button>
    </form>
  );
}

The register function is required so that the value of our inputs will be available for validation and submission. The capturing of the values of our form inputs is now working as well as the submission.

Notice that we spread the returned value of the register function. That function returns the following:

  • onChange of type ChangeHandler
  • onBlur of type ChangeHandler
  • ref of type React.Ref<any>
  • name of type string

For further information about this awesome function, you may refer to this link.

Validations and Catching Errors

To further improve our form, let us add validations. Fortunately, react-hook-form also gives us a way to validate our inputs. For this example, let us make all fields required.

App.tsx
<form onSubmit={handleSubmit(onSubmit)}>
  <input placeholder='First Name' {...register('firstName', { required: true })} />
  <input placeholder='Last Name' {...register('lastName', { required: true })} />
  <input type='email' placeholder='Email' {...register('email', { required: true })} />
  <button type='submit'>Submit</button>
</form>

That was nice! There are other validation rules which we can add to each register function: Such are as follows:

  • required
  • min
  • max
  • minLength
  • maxLength
  • pattern
  • validate

Now, when we hit the Submit button with any of the inputs having no value, react-hook-form gives us the validation errors. To catch such potential errors, we need to provide an errorHandler function to our handleSubmit.

App.tsx
import * as React from 'react';
import { useForm, SubmitHandler, SubmitErrorHandler } from 'react-hook-form';

type UserForm = {
  firstName: string;
  lastName: string;
  email: string;
};

function App() {
  const myForm = useForm<UserForm>({ ... });

  const { register, handleSubmit } = myForm;

  const onError: SubmitErrorHandler<UserForm> = (error) => {
    console.error(error);
  };

  const onSubmit: SubmitHandler<UserForm> = (formData) => {
    console.log(formData);

    //submit the form the server
  };

  return (
    <form onSubmit={handleSubmit(onSubmit, onError)}>
      {/*
        your
        inputs
        here
      */}
    </form>
  );
}

Let us now try to render the errors so that the user of our form will know what has gone wrong. For this, we'll do some refactoring on our mark up. Let us wrap our <input /> inside a div and with put underneath it a <span /> in which a potential error message will be rendered.

App.tsx
/* other code here */

// get the `errors` object from the `formState`
const { register, handleSubmit, formState } = myForm;
const { errors } = formState;

<form onSubmit={handleSubmit(onSubmit, onError)}>
  <div>
    <input placeholder='First Name' {...register('firstName', { required: true })} />
    {errors.firstName && <span>{errors.firstName.message}</span>}
  </div>
  <div>
    <input placeholder='Last Name' {...register('lastName', { required: true })} />
    {errors.lastName && <span>{errors.lastName.message}</span>}
  </div>
  <div>
    <input type='email' placeholder='Email' {...register('email', { required: true })} />
    {errors.email && <span>{errors.email.message}</span>}
  </div>
  <button type='submit'>Submit</button>
</form>;

First, we accessed the errors object from the formState which also comes from the register function. Then, we render the error inside a <span/> tag by accessing errors.[key].message. Notice also that we're only rendering the error when the errors.[key] is truthy.

Validation Using Yup

React-hook-form gives us a way to provide validation rules to our input fields. But there are times that we are required to use schema-based form validation. Fortunately, react-hook-form also supports schema-based validation with popular libraries like Yup and Zod. In this section, we will validate our existing form using Yup. To get started, let's install the needed dependencies:

  npm install @hookform/resolvers yup

The next step is to create the schema for our User object. We do this as follows:

import { yupResolver } from '@hookform/resolvers/yup';
import * as yup from 'yup';

const userSchema = yup
  .object({
    firstName: yup.string().required('First name is required').trim(),
    lastName: yup.string().required('Last name is required').trim(),
    email: yup.string().required('Email is required').email('Invalid email format').trim(),
  })
  .required();

That's how we define a schema with validation rules using yup. Notice that we can provide a custom error message to a rule and we can also do value sanitation like trimming whitespaces using .trim().

Now inside App, we'll add a resolver value to the useForm options. With this, we can now remove the validation rule (required) that we have put earlier as the second parameter of the register function.

function App() {
  const myForm = useForm<UserForm>({
    mode: 'onSubmit',
    resolver: yupResolver(userSchema),
    defaultValues: {
      firstName: '',
      lastName: '',
      email: '',
    },
  });

  /* other codes here */
}

That's it! our validation becomes simpler and our mark is now cleaner.

Resetting the form

Our form may have a Cancel button which when clicked must clear all our inputs or it may be a must to reset the form state after the form was submitted successfully. For this requirement, react-hook-form provides us a way to reset the form anytime we want. From the object returned by useForm we can get the reset function.

const myForm = useForm<UserForm>({...})
const { register, handleSubmit, formState , resey } = myForm;

The reset function has optional arguments which when provided can let us do partial form state reset. However, when no optional argument is supplied, the entire form state will be reset. We can use this function by just simply calling it.

<form onSubmit={handleSubmit(onSubmit, onError)}>
  {/* inputs here */}
  <button onClick={reset}>Cancel</button>
  <button type='submit'>Submit</button>
</form>

Conclusion

Using the react-hook-form library can largely help us build better forms. Leveraging the power of this library is a wise decision when it comes to managing forms. As your form grows more complex, you will surely appreciate this piece of amazing software. Again, for more information about react-hook-form, you may refer to its website here.