DEV Community

Dicky Saputra
Dicky Saputra

Posted on • Updated on

How I use React Hook Form with Yup and TypeScript

React Hook Form is a powerful library for managing forms in React applications. When combined with Yup for schema validation and TypeScript for type safety, it provides a robust solution for building and validating forms. This article will guide you through the process of integrating React Hook Form with Yup and TypeScript.

Install the package into your project

To get started, create a new React project and install the necessary dependencies.

npm install react-hook-form yup @hookform/resolvers
Enter fullscreen mode Exit fullscreen mode

Example

import Button from "@atoms/Button";
import Container from "@atoms/Container";
import InputField from "@atoms/InputField";
import TextAreaField from "@atoms/TextAreaField";
import Typography from "@atoms/Typography";
import { submitFormService } from "@features/contact/services/submitForm.service";
import { yupResolver } from "@hookform/resolvers/yup";
import { emailRules, phoneNumberRules } from "@utils/generalRegex.utils";
import handleError from "@utils/handleError.utils";
import { notifySuccess } from "@utils/notify.util";
import { useState } from "react";
import { useForm } from "react-hook-form";
import * as yup from "yup";
import s from "./ContactForm.module.scss";

interface IInitialValue {
  email: string;
  firstName: string;
  lastName: string;
  phoneNumber?: string;
  question?: string;
}
const initialValue: IInitialValue = {
  email: "",
  firstName: "",
  lastName: "",
  phoneNumber: "",
  question: "",
};
const ContactForm = () => {
  const resolver = yup.object({
    email: yup
      .string()
      .matches(emailRules, {
        message: "E-mail moet een geldig e-mailadres zijn!",
      })
      .required("Vul alstublieft dit veld in!"),
    firstName: yup.string().required("Vul alstublieft dit veld in!"),
    lastName: yup.string().required("Vul alstublieft dit veld in"),
    phoneNumber: yup
      .string()
      .test(
        "matches-phone",
        "Het mobiele telefoonnummer moet een geldig mobiel nummer zijn!",
        (value) => {
          if (!value) return true;
          return phoneNumberRules.test(value);
        }
      ),
    question: yup.string(),
  });

  const form = useForm({
    defaultValues: initialValue,
    resolver: yupResolver(resolver),
  });

  const onSubmit = async (data: typeof initialValue) => {
    try {
      await submitFormService({
        data: data,
      });
      form.reset();
      notifySuccess("Het contact is succesvol verzonden!");
    } catch (error) {
      handleError(error);
    }
  };

  return (
    <section className={s._Root}>
      <Container className={s._Container}>
        <form
          className={s._Card}
          onSubmit={form.handleSubmit(onSubmit)}
          method="post"
        >
          <Typography component="h2" variant="h2">
            Neem contact met ons op
          </Typography>
          <div className={s._Card__Row}>
            <InputField
              {...form.register("firstName")}
              id="voornaam"
              label="Voornam"
              helperText={form.formState.errors.firstName?.message}
              error={Boolean(form.formState.errors.firstName?.message)}
              required
            />
            <InputField
              {...form.register("lastName")}
              id="achternaam"
              label="Achternaam"
              helperText={form.formState.errors.lastName?.message}
              error={Boolean(form.formState.errors.lastName?.message)}
              required
            />
          </div>
          <div className={s._Card__Row}>
            <InputField
              {...form.register("phoneNumber")}
              id="telefoonnummer"
              label="Telefoonnummer"
              helperText={form.formState.errors.phoneNumber?.message}
              error={Boolean(form.formState.errors.phoneNumber?.message)}
            />
            <InputField
              {...form.register("email")}
              id="e-mail"
              label="E-mail"
              helperText={form.formState.errors.email?.message}
              error={Boolean(form.formState.errors.email?.message)}
              required
            />
          </div>
          <div className={s._Card__Row}>
            <TextAreaField
              id="jouwVraag"
              label="Jouw vraag"
              {...form.register("question")}
            />
          </div>
          <Button
            component="button"
            type="submit"
            disabled={form.formState.isSubmitting}
          >
            {form.formState.isSubmitting ? "Bezig met laden..." : "Verzend"}
          </Button>
        </form>
      </Container>
    </section>
  );
};

export default ContactForm;
Enter fullscreen mode Exit fullscreen mode

Let's breakdown part by part

1. setup the initial value with the type or interface

define the default value of the form that you will create

...
interface IInitialValue {
  email: string;
  firstName: string;
  lastName: string;
  phoneNumber?: string;
  question?: string;
}
const initialValue: IInitialValue = {
  email: "",
  firstName: "",
  lastName: "",
  phoneNumber: "",
  question: "",
};
...
Enter fullscreen mode Exit fullscreen mode

2. setup the handle form with useForm

const form = useForm({
  defaultValues: initialValue,
  resolver: yupResolver(resolver),
});
Enter fullscreen mode Exit fullscreen mode

3. write the resolver or validation

...
const resolver = yup.object({
  email: yup
    .string()
    .matches(emailRules, {
      message: "E-mail moet een geldig e-mailadres zijn!",
    })
    .required("Vul alstublieft dit veld in!"),
  firstName: yup.string().required("Vul alstublieft dit veld in!"),
  lastName: yup.string().required("Vul alstublieft dit veld in"),
  phoneNumber: yup
    .string()
    .test(
      "matches-phone",
      "Het mobiele telefoonnummer moet een geldig mobiel nummer zijn!",
      (value) => {
        if (!value) return true;
        return phoneNumberRules.test(value);
      }
    ),
  question: yup.string(),
});
...
Enter fullscreen mode Exit fullscreen mode
  • email field use the custom rules for email, in this case I use the regex from emailRules
  • firstName & lastName field is required
  • question is not required and only allow string value
  • phoneNumber is not required but the value should the correct phoneNumber in this case I use regex to handle the rules

4. register each field to useForm

I register each field like below. I not only register, I also put the error state and error message when the value not match with the rules. and make the field disabled when the form submiting

...
<InputField
    {...form.register("firstName")}
    id="voornaam"
    label="Voornam"
    helperText={form.formState.errors.firstName?.message}
    error={Boolean(form.formState.errors.firstName?.message)}
    disabled={form.formState.isSubmitting}
    required
/>
...
Enter fullscreen mode Exit fullscreen mode

5. Handle submit

...
const onSubmit = async (data: typeof initialValue) => {
  try {
    await submitFormService({
      data: data,
    });
    form.reset();
    notifySuccess("Het contact is succesvol verzonden!");
  } catch (error) {
    handleError(error);
  }
};
...
<form className={s._Card} onSubmit={form.handleSubmit(onSubmit)} method="post">
  ...
</form>;
...
Enter fullscreen mode Exit fullscreen mode

6. Add state to Button

Add state text if necessary, and make the button disabled while submitting

...
<Button component="button" type="submit" disabled={form.formState.isSubmitting}>
  {form.formState.isSubmitting ? "Bezig met laden..." : "Verzend"}
</Button>
...
Enter fullscreen mode Exit fullscreen mode

Conclusion

combining React Hook Form with Yup and TypeScript, you can create powerful, type-safe forms with robust validation. This integration allows you to leverage the strengths of each library: React Hook Form for efficient form management, Yup for schema-based validation, and TypeScript for static type checking.

This setup ensures that your forms are not only functional but also maintainable and easy to debug, providing a solid foundation for building complex forms in your React applications.

Top comments (0)