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;
To make the form look good, I've added some custom TailwindCSS classes so here is how it looks:
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;
}
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
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:
// 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):
<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:
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:
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,
});
// ...
}
We can pass in default values right into the useForm
hook!
Here is a screenshot after I added in the 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"),
});
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),
});
// ...
}
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),
});
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>
)}
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>
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"
});
Now we can see some beautiful errors:
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"),
});
Now we can see the following error:
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;
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)
so on point, short and concise, love it <3