How I use React Hook Form with Yup and TypeScript
Dicky Saputra
Posted on May 24, 2024
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
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;
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: "",
};
...
2. setup the handle form with useForm
const form = useForm({
defaultValues: initialValue,
resolver: yupResolver(resolver),
});
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(),
});
...
- 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
/>
...
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>;
...
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>
...
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.
Posted on May 24, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.