Story Behind the Blog
Until recently, we managed all our form data using MobX. However, after introducing React Hook Form, form validation became much simpler. We realized that there was no need to save the form data in multiple places since React Hook Form manages its own state. This eliminated a lot of code for us.
What is React Hook Form?
React Hook Form is a lightweight library that uses React hooks to handle form state and validation. It fits right into the existing React component structure, making it easy to build and manage forms without a lot of extra code.
Why You Don’t Need a Separate State
Managing form state usually means keeping track of local state with the useState hook, Context API, or a state management library like Redux or Mobx. However, React Hook Form takes care of this for you by providing its own context api.
Getting Started with React Hook Form
npm install react-hook-form --save
Now, let's create a basic form component using React Hook Form:
import React from 'react';
import { useForm } from 'react-hook-form';
const SimpleForm = () => {
const { register, handleSubmit, watch, formState: { errors } } = useForm();
const onSubmit = data => {
console.log(data)
// make api call to save the form
alert(JSON.stringify(data));
}
console.log(watch("example")); // watch input value by passing the name of it
return (
<form onSubmit={handleSubmit(onSubmit)} noValidate>
<input {...register("example")} />
{errors.example && <span>This field is required</span>}
<button type="submit">Submit</button>
</form>
);
};
export default SimpleForm;
In this example, the useForm
hook provides several methods and properties to manage the form:
register
: A function to register input fields.handleSubmit
: A function to handle form submission.watch
: A function to watch the value of a specific input field.formState
: An object containing the form's state, including validation errors.
Handling Validation
React Hook Form makes form validation simple and declarative. You can specify validation rules directly within the register
function. Here’s an example:
import React from 'react';
import { useForm } from 'react-hook-form';
const ValidationForm = () => {
const { register, handleSubmit, formState: { errors } } = useForm();
const onSubmit = data => {
console.log(data)
// make api call to save the form
alert(JSON.stringify(data));
}
return (
<form onSubmit={handleSubmit(onSubmit)} noValidate>
<input {...register("username", { required: true })} />
{errors.username && <span>This field is required</span>}
<input {...register("email", {
required: "Email is required",
pattern: {
value: /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/,
message: "Invalid email address"
}
})} />
{errors.email && <span>{errors.email.message}</span>}
<button type="submit">Submit</button>
</form>
);
};
export default ValidationForm;
In this example, we've added validation rules for the username
and email
fields. If the validation rules are not met, error messages will be displayed. We can simplify adding validation even more by using popular libraries like Zod or Yup.
here is how we can simplify this further by using zod -
import React from 'react';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
// Define your schema with zod
const schema = z.object({
username: z.string().nonempty({ message: "This field is required" }),
email: z.string().nonempty({ message: "Email is required" }).email({ message: "Invalid email address" }),
});
const ValidationFormWithZod = () => {
const { register, handleSubmit, formState: { errors } } = useForm({
resolver: zodResolver(schema),
});
const onSubmit = data => {
console.log(data)
// make api call to save the form
alert(JSON.stringify(data));
}
return (
<form onSubmit={handleSubmit(onSubmit)} noValidate>
<div>
<input {...register("username")} />
{errors.username && <span>{errors.username.message}</span>}
</div>
<div>
<input {...register("email")} />
{errors.email && <span>{errors.email.message}</span>}
</div>
<button type="submit">Submit</button>
</form>
);
};
export default ValidationFormWithZod;
Eliminating the Need for Separate State
By leveraging the useForm
hook, React Hook Form manages the form state internally. This means you don't need to use useState
to manage input values or validation errors. React Hook Form takes care of updating the form state as users interact with the form, making your code cleaner and more maintainable.
Consider a traditional approach with useState
:
import React, { useState } from 'react';
const TraditionalForm = () => {
const [formData, setFormData] = useState({ username: '', email: '' });
const [errors, setErrors] = useState({});
const handleChange = (e) => {
const { name, value } = e.target;
setFormData({ ...formData, [name]: value });
};
const handleSubmit = (e) => {
e.preventDefault();
const newErrors = {};
if (!formData.username) newErrors.username = "Username is required";
if (!formData.email) {
newErrors.email = "Email is required";
} else if (!/\S+@\S+\.\S+/.test(formData.email)) {
newErrors.email = "Email is invalid";
}
setErrors(newErrors);
if (Object.keys(newErrors).length === 0) {
console.log(formData);
// make api call to save the form
alert(JSON.stringify(data));
}
};
return (
<form onSubmit={handleSubmit} noValidate>
<input
name="username"
value={formData.username}
onChange={handleChange}
/>
{errors.username && <span>{errors.username}</span>}
<input
name="email"
value={formData.email}
onChange={handleChange}
/>
{errors.email && <span>{errors.email}</span>}
<button type="submit">Submit</button>
</form>
);
};
export default TraditionalForm;
In this traditional approach, you manage form state and validation manually using useState
. This can quickly become cumbersome as the form complexity grows. React Hook Form simplifies this by handling the form state internally, reducing the amount of boilerplate code you need to write.