DEV Community

PranavB6
PranavB6

Posted on

Simple React-Hook-Form v7 Tutorial with Typescript

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:



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;


Enter fullscreen mode Exit fullscreen mode

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

Simple form in react

Part 2: Integrating Inputs With React-Hook-Form

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



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


Enter fullscreen mode Exit fullscreen mode

Note that the keys of our interface correspond with the names of our fields

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



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

function App() {
  const { register } = useForm<UserInput>();
  // ...
}


Enter fullscreen mode Exit fullscreen mode

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:



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

// After:
<input {...register("fullName")} id="fullName" type="text" />


Enter fullscreen mode Exit fullscreen mode

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):



<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>


Enter fullscreen mode Exit fullscreen mode

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:



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>
  );
}


Enter fullscreen mode Exit fullscreen mode

Let's check out the console logs:

React-Hook-Form with registered inputs and on submit methods and console logs

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:



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

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

  // ...
}


Enter fullscreen mode Exit fullscreen mode

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

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:



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"),
});


Enter fullscreen mode Exit fullscreen mode

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:



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),
  });

  // ...
}


Enter fullscreen mode Exit fullscreen mode

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:



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


Enter fullscreen mode Exit fullscreen mode

We can view the error message by getting the errors[<field-name>].message like so:



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


Enter fullscreen mode Exit fullscreen mode

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



<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>


Enter fullscreen mode Exit fullscreen mode

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.



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


Enter fullscreen mode Exit fullscreen mode

Now we can see some beautiful errors:

react-hook-form error handling

Because of how powerful yup is, we can even require the checkbox field to be 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"),
});


Enter fullscreen mode Exit fullscreen mode

Now we can see the following error:

react-hook-form error handling with yup

Here is the full final code:



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;


Enter fullscreen mode Exit fullscreen mode

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 :)

Top comments (1)

Collapse
 
northship profile image
louai amrouche

so on point, short and concise, love it <3