skip to content
Samuel Edwin's Website

React Hook Form Tutorial - Numbers, Dates, Checkboxes

/ 3 min read

In the previous post we have learned how to validate text inputs in our form.

In this post we’re going to learn:

  • How to validate numbers and dates in <input > fields.
  • How to handle checkbox inputs.

Form requirements

We’re going to build a hotel booking form that has these requirements:

  • Number of guests: number between 1 to 5.
  • Check in date: date starting from tomorrow.
  • Extra services that are optional for users to choose.
Hotel booking form

Define the validation schema

We define the schema fields and validations, and also convert them to the types we want instead of just using strings.

schema.ts
const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
tomorrow.setHours(0);
tomorrow.setMinutes(0);
tomorrow.setSeconds(0);
export const formSchema = z.object({
guests: z
.number()
.int("Must be a whole number")
.min(1, "Must have at least 1 guest or more")
.max(5, "Cannot have more than 5 guests"),
checkIn: z.date().min(tomorrow, "Can only book starting from tomorrow"),
breakfast: z.boolean(),
wifi: z.boolean(),
airportTransfer: z.boolean(),
});
export type Form = z.TypeOf<typeof formSchema>;

Build the UI

Same drill with the previous tutorial, we’re going to create the UI components and handle inputs and submissions.

App.tsx
function App() {
const {
register,
formState: { errors },
handleSubmit,
} = useForm<Form>({
resolver: zodResolver(formSchema),
});
return (
<form
onSubmit={handleSubmit((data) => {
console.log(data);
})}
>
<div>
<label htmlFor="guests">Number of Guests</label>
<input id="guests" {...register("guests")} type="number" />
<div className="error">{errors.guests?.message}</div>
</div>
<div className="field">
<label htmlFor="check-in">Check-In Date</label>
<input id="check-in" {...register("checkIn")} type="date" />
<div className="error">{errors.checkIn?.message}</div>
</div>
<div className="field">
<label>Extra Services</label>
<label className="checkbox">
<input type="checkbox" {...register("breakfast")} />
<span className="check-label">Breakfast</span>
</label>
<label className="checkbox">
<input type="checkbox" {...register("wifi")} />
<span className="check-label">Wi-Fi</span>
</label>
<label className="checkbox">
<input type="checkbox" {...register("airportTransfer")} />
<span className="check-label">Airport Transfer</span>
</label>
</div>
<button type="submit">Submit</button>
</form>
);
}
export default App;

Notice that for the guests and check-ins, I use <input type="number" /> and <input type="date" />, and I don’t do anything special for the checkboxes.

Now submit the form with valid number of guests and check-in date and see what happens.

Form with bad error messages

We already provided the valid inputs but why did the validation fail?

One important thing we need to remember about <input />s behave: they return strings most of the time.

The type=number and type=date props that we use only change the appearance of the text field.

Making dates and numbers work

In order to fix this, we can convert the string inputs to the desired types using type coercion in Zod.

schema.ts
export const formSchema = z.object({
guests: z
.number({ coerce: true })
.int("Must be a whole number")
.min(1, "Must have at least 1 guest or more")
.max(5, "Cannot have more than 5 guests"),
checkIn: z
.date({
coerce: true,
})
.min(tomorrow, "Can only book starting from tomorrow"),
breakfast: z.boolean(),
wifi: z.boolean(),
airportTransfer: z.boolean(),
});

Now they should work just fine. Form with valid data

Terminal window
Object {
guests: 2,
checkIn: Date Thu Feb 29 2024 07:00:00 GMT+0700 (Western Indonesia Time),
breakfast: false,
wifi: true,
airportTransfer: false
}

Checkbox handlings are the opposite of numbers and dates, they just work.

Refining error messages

If you submit the form with blank fields you’ll get error messages like below.

Blank form with error messages

The guest number field shows the message like what we defined.

As for the date error message we can modify by doing a minor modification in the schema.

schema.ts
export const formSchema = z.object({
guests: z
.number({ coerce: true })
.int("Must be a whole number")
.min(1, "Must have at least 1 guest or more")
.max(5, "Cannot have more than 5 guests"),
checkIn: z
.date({
coerce: true,
errorMap: () => ({
message: "Please provide a valid check-in date",
}),
})
.min(tomorrow, "Can only book starting from tomorrow"),
breakfast: z.boolean(),
wifi: z.boolean(),
airportTransfer: z.boolean(),
});
Form with custom invalid date error message

Try it yourself

I’ve summarized everything in this tutorial into the sandbox.