Quick Form Validation Hacks with Zod, React-Hook-Form, and Bootstrap
Forms are essential for collecting data in digital applications. Yet, making sure they are strong and user-friendly can sometimes feel overwhelming. In this blog post, we will explore a successful approach to addressing this challenge by using Zod Schema, React Hooks, and the advanced design elements of Bootstrap UI components.
We'll embark on a hands-on journey, constructing a signup form from the ground up. Our exploration will encompass various validation aspects such as name and email, among others. We'll be harnessing Bootstrap throughout this process. Our focus will extend beyond the technical facets of form creation. We aim to enhance the user experience, showcasing an efficient and user-centric approach to form development from a frontend developer's perspective
Building the Form
Let's kickstart our form creation with a single field for the contact number. We'll employ the useForm hook from react-hook-form and the Zod schema for validation.
//schema file for form
import { useForm } from 'react-hook-form';
import { z } from 'zod';
export const schema = z.object({
contactNumber: z.string().nonempty({ message: 'Contact number is required' }),
});
//Form component
import {schema} from './schema.js'
export function ContactForm() {
const { register, handleSubmit, formState: { errors } } = useForm({
resolver: zodResolver(schema)
});
const onSubmit = (data) => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register('contactNumber')} placeholder="Contact Number" />
{errors.contactNumber && <p>{errors.contactNumber.message}</p>}
<input type="submit" />
</form>
);
}
In the code snippet above, we define a Zod schema for our form. The schema specifies that the contactNumber field is a string and is required. We then use the useForm hook with the Zod resolver to create a form with validation based on our schema.
useForm is a custom hook provided by react-hook-form that helps manage form state and validation. When we call useForm, it returns an object with several properties that we can use to control our form:
register: This function allows us to "register" input/select fields with the form. When a field is registered, useForm will be able to track the field's value and handle validation.
handleSubmit: This function wraps around our form's submit event. Inside handleSubmit, we pass our own submit function where we can handle what happens when the form is submitted.
formState: This object contains information about the form state. In our case, we're destructuring errors out of formState, which is an object containing any validation errors for our fields.
From above, we successfully created a contact field with validation. Let's now look into the other fields as we complete our signup form.
export const signupFormSchema = z.object({
fullName: z.string()
.min(4, { message: 'Full name is required' })
.max(35)
.refine(value => value.trim().length > 0, {
message: 'Name should be 4-35 characters',
path: ['fullName']
}),
email: z.string()
.email({ message: 'Please enter a valid email address' }).nonempty({ message: 'Email is required' }),
contactNumber: z.string()
.min(10, { message: 'Contact number should be exactly 10 digits' })
.max(10, { message: 'Contact number should be exactly 10 digits' }).nonempty({ message: 'Contact is required' }),
dateOfBirth:z.string().date(),
password: z.string()
.min(8, { message: 'Password must be at least 8 characters long' })
.regex(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/, {
message: 'Password must contain at least one lowercase letter, one uppercase letter, one number, and one special character'
}),
});
import React from 'react';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import {signupFormSchema} from "./schema"
import { useState } from 'react';
const SignupForm = () => {
const [previewImage, setPreviewImage] = useState(null);
const { register, handleSubmit, setValue,formState: { errors } } = useForm({
resolver: zodResolver(signupFormSchema)
});
const handleContactNumberChange = (e) => {
const input = e.target.value.replace(/\D/g, ''); // Remove non-numeric characters
e.target.value = input; // Update the input value
};
const onSubmit = (data) => {
console.log(data);
};
return (
<div className="d-flex flex-column justify-content-center">
<h2 className=''>Sign up Form</h2>
<div className="card bg-gradient bg-dark text-white w-50 ">
<div className="card-body">
<form onSubmit={handleSubmit(onSubmit)}>
{/* full name */}
<div className="mb-3">
<label htmlFor="fullName" className="form-label">Full Name<span className="text-danger">*</span></label>
<input type="text" className="form-control" id="fullName" {...register("fullName")} />
<span className="text-danger">{errors.fullName && errors?.fullName?.message}</span>
</div>
{/* email */}
<div className="mb-3">
<label htmlFor="email" className="form-label">Email<span className="text-danger">*</span></label>
<input
type="email"
className="form-control"
id="email"
{...register("email")}
/>
<span className="text-danger">{errors.email && errors.email.message}</span>
</div>
{/* contact number */}
<div className="mb-3">
<label htmlFor="contactNumber" className="form-label">Contact Number<span className="text-danger">*</span></label>
<input
type="text"
className="form-control"
id="contactNumber"
{...register("contactNumber")}
onInput={handleContactNumberChange}
/>
<span className="text-danger">{errors.contactNumber &&errors.contactNumber.message}</span>
</div>
{/* Date of birth */}
<div className="mb-3">
<label htmlFor="dateOfBirth" className="form-label">Date of Birth<span className="text-danger">*</span></label>
<input
type="date"
className="form-control"
id="dateOfBirth"
{...register("dateOfBirth")}
onChange={(e) => {
const dateValue = e.target.value;
setValue('dateOfBirth', dateValue); // Format date before setting value
}}
/>
<span className="text-danger">{errors.dateOfBirth && errors.dateOfBirth.message}</span>
</div>
{/* password */}
<div className="mb-3">
<label htmlFor="password" className="form-label">Password</label>
<input
type="password"
className="form-control"
id="password"
{...register("password")}
/>
<span className="text-danger">{errors.password && errors.password.message}</span>
</div>
<button type="submit" className="btn btn-primary w-100">Submit</button>
</form>
</div>
</div>
</div>
);
};
export default SignupForm;
In this blog post, we've explored how to build a robust signup form in React using Zod, useForm. Here are the key takeaways: Zod: We used Zod to create a schema for our form. This schema defined the shape of our form data and included validation rules for each field.
useForm: We used the useForm hook from react-hook-form to manage our form state. This included registering fields, handling form submission, and managing validation errors.
By combining these tools and techniques, we were able to build a complex, robust form with ease. We hope this guide has been helpful and encourages you to explore these tools further in your projects.
Happy coding! ๐
Subscribe to my newsletter
Read articles from Bisinigi_Shivani directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Bisinigi_Shivani
Bisinigi_Shivani
Hey there! I'm a frontend intern with a passion for sharing knowledge through blogging. Follow along as I dive into the world of frontend development and share insights along the way!