Simple React-Hook-Form v7 Tutorial with Typescript
Tags: react, webdev, tutorial, typescript
Published: 2022-12-18 00:18:26

Here is the repo with starter code if you want to follow along: https://github.com/PranavB6/tutorials

## Part 1: Basic Form

Let's get started with some basic code:

```react
import "./App.css";

function App() {
  const onSubmitHandler = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();

    console.log(`Submitted`);
  };

  return (
    <main className="main prose">
      <h1>Forms</h1>

      <form onSubmit={onSubmitHandler} className="form">
        <div>
          <label htmlFor="fullName">Full Name</label>
          <input name="fullName" id="fullName" type="text" />
        </div>

        <div>
          <label htmlFor="food">Select Your Favourite Food!</label>
          <select name="food" id="food">
            <option value="" disabled>
              Please Select...
            </option>
            <option value="pizza">Pizza</option>
            <option value="burger">Burger</option>
            <option value="ice-cream">Ice Cream</option>
          </select>
        </div>

        <div>
          <input
            name="approvesTutorial"
            id="approves-tutorial"
            type="checkbox"
          />
          <label htmlFor="approves-tutorial">
            Do you approve this tutorial?
          </label>
        </div>

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

export default App;
```

To make the form look good, I've added some custom TailwindCSS classes so here is how it looks:

![Simple form in react](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o3dmh0m9w9jp5ql6x4we.png)

## Part 2: Integrating Inputs With React-Hook-Form

Next, we are going to define an interface that represents our form:

```react
interface UserInput {
  fullName: string;
  food: string;
  approvesTutorial: boolean;
}
```

Note that the keys of our interface correspond with the `name`s of our fields

Next, let's call the `useForm` hook from `react-hook-form`

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

function App() {
  const { register } = useForm<UserInput>();
  // ...
}
```
By giving the `useForm` hook our `UserInput` interface, `react-hook-form` can understand our form better and give us typesafety with it. 

The register function allows us to register an input, so let's register one of our fields:

```react
// Before:
<input name="fullName" id="fullName" type="text" />

// After:
<input {...register("fullName")} id="fullName" type="text" />
```
Note that we can get rid of the `name` prop since the `register("fullName")` also provides the `name` prop

Now lets register all of our inputs (and select):

```react
<div>
  <label htmlFor="fullName">Full Name</label>
  <input {...register("fullName")} id="fullName" type="text" />
</div>

<div>
  <label htmlFor="food">Select Your Favourite Food!</label>
  <select {...register("food")} id="food">
    <option value="" disabled>
      Please Select...
    </option>
    <option value="pizza">Pizza</option>
    <option value="burger">Burger</option>
    <option value="ice-cream">Ice Cream</option>
  </select>
</div>

<div>
  <input
    {...register("approvesTutorial")}
    id="approves-tutorial"
    type="checkbox"
  />
  <label htmlFor="approves-tutorial">
    Do you approve this tutorial?
  </label>
</div>
```
Note that the name we pass into our register function **must** be a key of our `UserInput` interface, we if try to pass in a name that's not in our `UserInput` interface such as `register("fullName2")`, typescript will complain, which is great! This is all because we pass in our interface to the `useForm` hook ie. `useForm<UserInput>()`

## Part 3: Configuring the Submit Functionality

We also need to let `react-hook-form` handle the submit functionality. To do this, let's get the `handleSubmit` function from the `useForm` hook and pass it to the `form`'s `onSubmit` prop. We can then pass in our own `onSubmitHandler` to the `handleSubmit` function. That was a mouthful so let's just show it:

```react
function App() {
  const { register, handleSubmit } = useForm<UserInput>();

  const onSubmitHandler = (values: UserInput) => {
    console.log(`Submitted`);
    console.table(values);
  };

  return (
    {/* ... */}

      <form onSubmit={handleSubmit(onSubmitHandler)} className="form">
      {/* ... */}
      </form>
  );
}
```

Let's check out the console logs:

![React-Hook-Form with registered inputs and on submit methods and console logs](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/05jd3oe4v1dui8q4rduc.png)

Awesome! we have a working form with react-hook-form!

Note how the `onSubmitHandler` takes in a `values` parameter of type `UserInput`! Because we told `useForm` to use our `UserInput` interface, it knows that the values submitted by our form will be of type `UserInput`!

## Part 4: Adding Default Values

Next, let's setup some default values:

```react
const defaultValues: UserInput = {
  fullName: "John Doe",
  food: "",
  approvesTutorial: true,
};

function App() {
  const { register, handleSubmit } = useForm<UserInput>({
    defaultValues,
  });
  
  // ...
}
```
We can pass in default values right into the `useForm` hook!
Here is a screenshot after I added in the default values:

![react-hook-form with default values](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/68hehoirie4i7ggqrvj4.png)

## Part 5: Error handling

Next, let's add in some error handling. `react-hook-form` ships with some built-in error handling which you can pass into the register function like so: `register("fullName", { required: true, maxLength: 10})`, but its not as powerful as libraries like `yup`. Luckily, `react-hook-form` can integrate with libraries like `yup`.

Let's import `yup` and create a validation schema for our form:

```react
import * as yup from "yup";

const validationSchema = yup.object({
  fullName: yup.string().required("Full Name is required"),
  food: yup.string().required("Food is required"),
  approvesTutorial: yup.boolean().required("Approves Tutorial is required"),
});
```
To integrate our `yup` `validationSchema` with `react-hook-form`, we need to first import `yupResolver` from the `@hookform/resolvers` package and then call the `yupResolver()` function and finally pass it into the `useForm` hook:

```react
import * as yup from "yup";
import { yupResolver } from "@hookform/resolvers/yup";

// ...

const validationSchema = yup.object({
  fullName: yup.string().required("Full Name is required"),
  food: yup.string().required("Food is required"),
  approvesTutorial: yup.boolean().required("Approves Tutorial is required"),
});

function App() {

  const { register, handleSubmit } = useForm<UserInput>({
    defaultValues,
    resolver: yupResolver(validationSchema),
  });

  // ...
}
```

Now you can see our form doesn't submit until the validation schema is satisfied. However, we don't actually see the errors. We need to get the `errors` object from the `formState` object in the `useForm` hook like so:

```react
  const {
    register,
    handleSubmit,
    formState: { errors }, // get errors
  } = useForm<UserInput>({
    defaultValues,
    resolver: yupResolver(validationSchema),
  });
```
We can view the error message by getting the `errors[<field-name>].message` like so:

```react
{errors.fullName && (
  <p className="error-message">{errors.fullName.message}</p>
)}
```

Now let's add an error message to all the fields.

```react
<div>
  <label htmlFor="fullName">Full Name</label>
  <input {...register("fullName")} id="fullName" type="text" />
  {errors.fullName && (
    <p className="error-message">{errors.fullName.message}</p>
  )}
</div>

<div>
  <label htmlFor="food">Select Your Favourite Food!</label>
  <select {...register("food")} id="food">
    <option value="" disabled>
      Please Select...
    </option>
    <option value="pizza">Pizza</option>
    <option value="burger">Burger</option>
    <option value="ice-cream">Ice Cream</option>
  </select>
  {errors.food && (
    <p className="error-message">{errors.food.message}</p>
  )}
</div>

<div>
  <input
    {...register("approvesTutorial")}
    id="approves-tutorial"
    type="checkbox"
  />
  <label htmlFor="approves-tutorial">
    Do you approve this tutorial?
  </label>
  {errors.approvesTutorial && (
    <p className="error-message">{errors.approvesTutorial.message}</p>
  )}
</div>
```

Note that we can only see the errors after we press the submit button. This is because the `mode` property of the `useForm` hook is set to `onSubmit` by default which means it will start validation on a submit event and then trigger every time a field is changed. We can change this to `onTouched` which will start validation the first time a user clicks out of an input (aka "onBlur") and then trigger every time a field is changed.

```react
  const {
    register,
    handleSubmit,
    formState: { errors }, // get errors of the form
  } = useForm<UserInput>({
    defaultValues,
    resolver: yupResolver(validationSchema),
    mode: "onTouched", // default is "onSubmit"
  });
```

Now we can see some beautiful errors:

![react-hook-form error handling](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/edvgusq8x5l9u56vy3s6.png)

Because of how powerful `yup` is, we can even require the `checkbox` field to be true

```react
const validationSchema = yup.object({
  fullName: yup
    .string()
    .required("Full Name is required")
    .max(10, "Name is too long"),
  food: yup.string().required("Food is required"),
  approvesTutorial: yup
    .boolean()
    .isTrue("You must approve of this tutorial")
    .required("Approves tutorial is required"),
});
```

Now we can see the following error:

![react-hook-form error handling with yup](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fhjxgvocet6bk9caymny.png)

Here is the full final code:

```react
import { useForm } from "react-hook-form";
import "./App.css";
import * as yup from "yup";
import { yupResolver } from "@hookform/resolvers/yup";

interface UserInput {
  fullName: string;
  food: string;
  approvesTutorial: boolean;
}

const defaultValues: UserInput = {
  fullName: "John Doe",
  food: "",
  approvesTutorial: true,
};

const validationSchema = yup.object({
  fullName: yup
    .string()
    .required("Full Name is required")
    .max(10, "Name is too long"),
  food: yup.string().required("Food is required"),
  approvesTutorial: yup
    .boolean()
    .isTrue("You must approve of this tutorial")
    .required("Approves tutorial is required"),
});

function App() {
  const {
    register,
    handleSubmit,
    formState: { errors }, // get errors of the form
  } = useForm<UserInput>({
    defaultValues,
    resolver: yupResolver(validationSchema),
    mode: "onTouched", // default is "onSubmit"
  });

  const onSubmitHandler = (values: UserInput) => {
    console.log(`Submitted`);
    console.table(values);
  };

  return (
    <main className="main prose">
      <h1>Forms</h1>

      <form onSubmit={handleSubmit(onSubmitHandler)} className="form">
        <div>
          <label htmlFor="fullName">Full Name</label>
          <input {...register("fullName")} id="fullName" type="text" />
          {errors.fullName && (
            <p className="error-message">{errors.fullName.message}</p>
          )}
        </div>

        <div>
          <label htmlFor="food">Select Your Favourite Food!</label>
          <select {...register("food")} id="food">
            <option value="" disabled>
              Please Select...
            </option>
            <option value="pizza">Pizza</option>
            <option value="burger">Burger</option>
            <option value="ice-cream">Ice Cream</option>
          </select>
          {errors.food && (
            <p className="error-message">{errors.food.message}</p>
          )}
        </div>

        <div>
          <input
            {...register("approvesTutorial")}
            id="approves-tutorial"
            type="checkbox"
          />
          <label htmlFor="approves-tutorial">
            Do you approve this tutorial?
          </label>
          {errors.approvesTutorial && (
            <p className="error-message">{errors.approvesTutorial.message}</p>
          )}
        </div>

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

export default App;
```

This is everything you need to know to get started using `react-hook-form`, I hope you enjoy using it as much as I have. 

This is my first tutorial I hope you enjoyed reading and let me know if I made any mistakes or how I could improve it, I would appreciate some constructive criticism :)