How to use React Hook Form with TypeScript and Material UI – Complete Guide

Forms in React is a very important concept to understand if you are new to it. If your project is small, you could use a basic HTML form in React. The difference between a normal HTML form and an HTML form in React is that in React, the form data is handled by the React component. Whereas, in a simple HTML form, data is handled by the DOM.

Now, depending on your use case, implementing forms in React can get quite complex. For example, if you are building an enterprise application that has multiple forms and complex functionality then you have to consider the best option to use.

What is React Hook Form

React Hook Form is a library to manage and handle complex forms. When your forms get bigger, it becomes cumbersome to manage them. To manage handle input validations in each form individually can be a daunting task. React Hook form takes that complexity away by allowing you to write validations and handle errors in a dynamic and effective way without having to write complex validations manually.

More details can be found on their official website.

What benefits does react-hook-form offer

Here are some of the greatest benefits this library offers:

  • Number of re-renders is very small compared to regular forms as it uses refs instead of state.
  • The amount of code you have to write is significantly lesser.
  • The package size is very small and it doesn’t have any dependencies.
  • Mounts much faster than other form alternatives.
  • Can be easily integrated with React UI libraries including Material UI and Ant Design.

How to use React Hook Form

Firs of all you will need to install the library. To do that, run this command in your console (assuming you have created a React app already):

npm install react-hook-form

Now, to create a very simple form with react-hook-form, first we have to import useForm hook like so:

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

The useForm gives us access to a number of properties. For now, we will import three of them, namely, register and handleSubmit and errors.

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

So let’s see what these three properties are about:

register: The purpose of this function is to track the changes of an input field

handleSubmit: The function we call when the form is submitted

errors: It is a nested property in the formState object. This is going to contain validation errors.

Now, we can use the register function on our input field with a variable called name. This will make the value of the input field available for validation and form submission.

 <TextField className="input-field" {...register("name")} />

When the form is submitted, handleSubmit will handle the submission. It will send the data user entered to the onSubmit function that we have defined.

const onSubmit: SubmitHandler<FormValues> = (data: FormValues) => {
    setData(data)
  }

Here’s the complete code for the form (written in Typescript with Material UI and Styled Components):

import React, { useState } from "react"
import { useForm, SubmitHandler } from "react-hook-form"
import { Box, Typography, Button } from "@mui/material"
import TextField from "@mui/material/TextField/TextField"
import { StyledForm } from "./form.styles"

type FormValues = {
  name: string
}

const Form = () => {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<FormValues>()
  const [data, setData] = useState<FormValues>({ name: "" })

  const onSubmit: SubmitHandler<FormValues> = (data: FormValues) => {
    setData(data)
  }

  return (
    <StyledForm>
      <Typography className="heading">React Hook Form</Typography>
      <form onSubmit={handleSubmit(onSubmit)}>
        <Box className="form-container">
          <TextField className="input-field" {...register("name")} />
          <Button
            type="submit"
            className="submit"
            variant="contained"
            size="large"
          >
            Submit
          </Button>

          <Typography className="normal-text">
            Data: {JSON.stringify(data)}
          </Typography>
        </Box>
      </form>
    </StyledForm>
  )
}

export default Form

Here’s how it looks on the screen:

How to Add Validations to React Hook Form

This is the most interesting part of react-hook-form. You will see how easy it is to add validations to your forms and how hectic it would have been to do it without it.

For larger applications, it becomes quite cumbersome to add validations to each input field and manage error states.

Here’s a list of validation rules supported by the library:

  • required to set mandatory input fields
  • min to set the minimum accepted value
  • max to set the maximum accepted value
  • minLength the minimum input length that is accepted
  • maxLength the maximum input length that is accepted
  • pattern to specify a regex pattern for validation
  • validate a custom function to validate the input

Now, we will modify the form a bit to add a username and password instead of just a name field. This will help demonstrate the capabilities in a better way.

For our particular example, we will apply the following validations to each field:

  • Name: Minimum Length will be 3 and Maximum Length will be 10
  • Username: Username field will be required. Also maximum length will be 10
  • Password: Password field will be required. Min length will be 8 charaters

For example, for the name field, we have added validation for minLength and maxLength. Here’s how this would look:

<TextField
            className="input-field"
            label="Name"
            {...register("name", {
              minLength: {
                value: 3,
                message: "Name should be at least 3 characters",
              },
              maxLength: {
                value: 10,
                message: "Name should be at most 10 characters",
              },
            })}
          />

Now, to specify the error messge for this field, we simply have to do this:

{errors.name && <p className="error-msg">{errors.name.message}</p>}

What it’s saying is that if there are any errors related to the name field, render that error message.

Here’s how the complete form looks like:

<form onSubmit={handleSubmit(onSubmit)}>
        <Box className="form-container">
          <TextField
            className="input-field"
            label="Name"
            {...register("name", {
              minLength: {
                value: 3,
                message: "Name should be at least 3 characters",
              },
              maxLength: {
                value: 10,
                message: "Name should be at most 10 characters",
              },
            })}
          />
          {errors.name && <p className="error-msg">{errors.name.message}</p>}
          <TextField
            className="input-field"
            label="Username"
            {...register("username", {
              required: "Username is required",
              maxLength: {
                value: 10,
                message: "Username should be at most 10 characters",
              },
            })}
          />
          {errors.username && (
            <p className="error-msg">{errors.username.message}</p>
          )}
          <TextField
            className="input-field"
            label="Password"
            {...register("password", {
              required: "Password is required.",
              minLength: {
                value: 8,
                message: "Password should be at-least 8 characters.",
              },
            })}
          />
          {errors.password && (
            <p className="error-msg">{errors.password.message}</p>
          )}
          <Button
            type="submit"
            className="submit"
            variant="contained"
            size="large"
          >
            Submit
          </Button>

          <Typography className="normal-text">
            Data: {JSON.stringify(data)}
          </Typography>
        </Box>
      </form>

After adding validations, here’s how the final form will look like if there are any errors:

How to Reset Form Values

It is a better UX approach to always clear out the form fields after submitting the form. React hook form makes it very easy to do. The useForm hook returns a reset function that will reset the form fields to their default state.

const { reset } = useForm()

You can also specify the fields that you want to reset like so:

  const onSubmit: SubmitHandler<FormValues> = (data: FormValues) => {
    setData(data);

    reset({
      name: "",
      username: "",
      password: "",
    });
  };

How to set Initial form values using defaultValues

There’s a way to set initial form values for your react-hook-form when it renders. To do it, put the default values inside the useForm hook like so:

  const {
    register,
    handleSubmit,
    formState: { errors },
    reset,
  } = useForm<FormValues>({
    defaultValues: {
      name: "John Doe",
      username: "johndoe",
    },
  });

How to use React Hook Form with other Libraries like ‘React-Select’

Sometimes, we might need to use react-hook-form with other libraries like ‘React Select’. React Select allows you to have multiple inputs in a Select dropdown field.

In such cases, we cannot add the register function for the select dropdown. If you want to control React Select input with react-hook-form then you will have to use a Controller component that comes with the library.

We import it like this:

import { useForm, SubmitHandler, Controller } from "react-hook-form";

And import control from useForm hook like this:

  const {
    register,
    handleSubmit,
    formState: { errors },
    control,
    reset,
  } = useForm<FormValues>({
    defaultValues: {
      name: "John Doe",
      username: "johndoe",
    },
  });

We will give a unique name property “department” to the Controller component. This is how the input field looks like:

<Controller
            name="department"
            control={control}
            rules={{ required: true }}
            render={({ field }) => (
              <Select
                className="input-field"
                {...field}
                isMulti
                options={departments}
              />
            )}
          />

How to use radio box and check box inputs

In case of Material UI, we have controlled inputs for radio boxes and check boxes. To work with them, we can use the Controller wrapper component provided by react-hook-form.

Here’s an example of how to create a radio group component using MUI:

<Controller
              name="gender"
              render={({ field }) => (
                <RadioGroup {...field}>
                  <FormControlLabel
                    value="female"
                    control={<Radio />}
                    label="Female"
                  />
                  <FormControlLabel
                    value="male"
                    control={<Radio />}
                    label="Male"
                  />
                </RadioGroup>
              )}
              control={control}
            />

We can also add validation to this RadioGroup input field by simply putting this:

rules={{ required: "Gender is required" }}

So that’s it for now. I hope after reading this tutorial you have a good idea of how react-hook-form library works and how you can integrate into your existing react app.

For those who are still curious, HERE are some great example forms created using this library.

Github Repo

Spread the Word

You May Also Like

About the Author: Umair

A self-learned Javascript developer specializing in Frontend and Backend frameworks including React.js, Redux, Node.js, Express, MongoDB. He has extensive industry experience as a Tech Support lead and System Administrator. Currently learning Web3, (Solidity, Hardhat, Ethers.js) Smart contracts development, testing and auditing.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.