When working with React, forms are something you will deal with in almost every project.

Whether you're building a login page, registration form, admin dashboard, job application portal, e-commerce checkout, or a multi-step form, managing form data and validation can quickly become complicated.

Initially, handling forms using React's useState seems straightforward. But as the application grows, things get messy.

You start dealing with:

  • Managing multiple input values.
  • Handling validation for every field.
  • Displaying error messages.
  • Tracking touched and dirty fields.
  • Managing form submission.
  • Handling API errors.
  • Preventing unnecessary re-renders.
  • Supporting dynamic fields and complex validations.

This is where form management libraries become useful.

Two popular choices in the React ecosystem are React Hook Form and Formik.

I've worked with both approaches, and one thing I've noticed is that choosing a form library isn't just about writing fewer lines of code. It's also about understanding how it manages form state and how that affects your application.

In this article, we'll implement the same registration form using both libraries, understand their differences, and discuss when to choose each one.

Let's get started!

1. Understanding the Two Libraries

Before comparing them, let's understand their fundamental approaches.

What is Formik?

Formik is a React form management library that helps developers manage form values, validation, errors, touched fields, and submissions.

Formik follows a controlled component approach.

This means Formik manages the form values in its state. Whenever a user types something into an input, the corresponding form state gets updated.

For example:

const formik = useFormik({ initialValues: { email: "", password: "", }, onSubmit: (values) => { console.log(values); }, }); 

Formik provides useful handlers such as:

  • handleChange
  • handleBlur
  • handleSubmit
  • setFieldValue
  • setFieldTouched
  • setFieldError

It also provides components such as Formik, Field, ErrorMessage, and FieldArray.

What is React Hook Form?

React Hook Form is a React library designed to manage forms with minimal re-renders and a lightweight API.

It primarily uses an uncontrolled component approach for native HTML inputs.

Instead of updating React state on every keystroke, it connects inputs to the form using refs and manages their values internally.

For example:

const { register, handleSubmit } = useForm({ defaultValues: { email: "", password: "", }, }); const onSubmit = (values) => { console.log(values); }; 

Some important methods include:

  • register
  • handleSubmit
  • watch
  • setValue
  • reset
  • setError
  • useFieldArray
  • Controller

React Hook Form also supports controlled components when necessary.

2. Let's Build the Same Registration Form Using Both Libraries

Theory is good, but implementing the same functionality with both libraries will make the differences much clearer.

We'll build a registration form with the following fields:

  • Full Name
  • Email
  • Password
  • Confirm Password

Our requirements:

  1. All fields are required.
  2. Email must have a valid format.
  3. Password must contain at least 8 characters.
  4. Confirm Password must match Password.
  5. Display validation errors.
  6. Submit the form data.

Approach 1: Implementing the Form Using Formik

Step 1: Install the package

npm install formik 

Step 2: Create the Registration Component

import React from "react"; import { useFormik } from "formik"; const RegistrationForm = () => { const formik = useFormik({ initialValues: { fullName: "", email: "", password: "", confirmPassword: "", }, validate: (values) => { const errors = {}; if (!values.fullName.trim()) { errors.fullName = "Full name is required"; } if (!values.email) { errors.email = "Email is required"; } else if ( !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(values.email) ) { errors.email = "Invalid email address"; } if (!values.password) { errors.password = "Password is required"; } else if (values.password.length < 8) { errors.password = "Password must contain at least 8 characters"; } if (!values.confirmPassword) { errors.confirmPassword = "Please confirm your password"; } else if ( values.confirmPassword !== values.password ) { errors.confirmPassword = "Passwords do not match"; } return errors; }, onSubmit: (values) => { console.log("Form Submitted:", values); }, }); return ( <form onSubmit={formik.handleSubmit}> <div> <label>Full Name</label> <input type="text" name="fullName" value={formik.values.fullName} onChange={formik.handleChange} onBlur={formik.handleBlur} /> {formik.touched.fullName && formik.errors.fullName && ( <p>{formik.errors.fullName}</p> )} </div> <div> <label>Email</label> <input type="email" name="email" value={formik.values.email} onChange={formik.handleChange} onBlur={formik.handleBlur} /> {formik.touched.email && formik.errors.email && ( <p>{formik.errors.email}</p> )} </div> <div> <label>Password</label> <input type="password" name="password" value={formik.values.password} onChange={formik.handleChange} onBlur={formik.handleBlur} /> {formik.touched.password && formik.errors.password && ( <p>{formik.errors.password}</p> )} </div> <div> <label>Confirm Password</label> <input type="password" name="confirmPassword" value={formik.values.confirmPassword} onChange={formik.handleChange} onBlur={formik.handleBlur} /> {formik.touched.confirmPassword && formik.errors.confirmPassword && ( <p>{formik.errors.confirmPassword}</p> )} </div> <button type="submit"> Register </button> </form> ); }; export default RegistrationForm; 

That's it!

Formik handles the form state and provides the necessary methods to update values, track user interactions, and validate the form.

But how does it actually work?

Let's understand some important properties:

Property Purpose
initialValues Defines initial form values
values Contains current form values
handleChange Updates the value when an input changes
handleBlur Tracks when a user leaves an input
touched Tracks fields the user has interacted with
errors Stores validation errors
handleSubmit Handles form submission

One important thing to understand is that Formik keeps the form values in React-managed state.

When an input changes, Formik updates its state, which can trigger re-renders in components subscribed to its context.

This is an important consideration when working with large forms.

Approach 2: Implementing the Same Form Using React Hook Form

Now let's implement exactly the same functionality using React Hook Form.

Step 1: Install the package

npm install react-hook-form 

Step 2: Create the Registration Component

import React from "react"; import { useForm } from "react-hook-form"; const RegistrationForm = () => { const { register, handleSubmit, watch, formState: { errors }, } = useForm({ defaultValues: { fullName: "", email: "", password: "", confirmPassword: "", }, }); const password = watch("password"); const onSubmit = (values) => { console.log("Form Submitted:", values); }; return ( <form onSubmit={handleSubmit(onSubmit)}> <div> <label>Full Name</label> <input type="text" {...register("fullName", { required: "Full name is required", })} /> {errors.fullName && ( <p>{errors.fullName.message}</p> )} </div> <div> <label>Email</label> <input type="email" {...register("email", { required: "Email is required", pattern: { value: /^[^\s@]+@[^\s@]+\.[^\s@]+$/, message: "Invalid email address", }, })} /> {errors.email && ( <p>{errors.email.message}</p> )} </div> <div> <label>Password</label> <input type="password" {...register("password", { required: "Password is required", minLength: { value: 8, message: "Password must contain at least 8 characters", }, })} /> {errors.password && ( <p>{errors.password.message}</p> )} </div> <div> <label>Confirm Password</label> <input type="password" {...register("confirmPassword", { required: "Please confirm your password", validate: (value) => value === password || "Passwords do not match", })} /> {errors.confirmPassword && ( <p>{errors.confirmPassword.message}</p> )} </div> <button type="submit"> Register </button> </form> ); }; export default RegistrationForm; 

Notice something?

We didn't write separate onChange, onBlur, or value handlers for every input.

That's because register() handles the connection between the input and React Hook Form.

Let's understand the important methods:

Method / Property Purpose
register() Registers an input with the form
handleSubmit() Validates and handles form submission
watch() Watches the value of a field
setValue() Updates a field programmatically
reset() Resets the form
errors Contains validation errors
defaultValues Defines initial form values

One thing worth highlighting is that register() returns the necessary props, including name, ref, onChange, and onBlur.

This makes it particularly convenient when working with native HTML inputs.

3. The Biggest Difference: Controlled vs Uncontrolled Components

This is probably the most important concept to understand when comparing these two libraries.

Let's break it down.

Controlled Components

In controlled components, React manages the input value through state.

Example:

const [email, setEmail] = useState(""); <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} /> 

Here:

  1. React state stores the input value.
  2. The user types something.
  3. The onChange event updates the state.
  4. React renders the component with the updated value.

Formik follows this approach for managing form values.

Uncontrolled Components

In uncontrolled components, the DOM maintains the current input value.

Example:

const inputRef = useRef(null); <input type="email" ref={inputRef} /> 

We can access the input value using:

console.log(inputRef.current.value); 

React Hook Form primarily uses this approach with native inputs.

It uses refs to connect inputs to its internal form management system.

The result?

It can avoid triggering a React render for every input change in the same way a fully controlled form typically does.

Important: React Hook Form also supports controlled components through Controller and useController. So, it's not limited to uncontrolled inputs.

4. Performance: Why Does React Hook Form Have an Advantage?

Let's consider a practical example.

Imagine you're building an employee management dashboard with a form containing 30-40 fields.

Each field has:

  • Input values
  • Validation rules
  • Error messages
  • Conditional fields
  • Dynamic dropdowns

With a controlled form approach, updating form values can cause subscribed React components to re-render as state changes.

React Hook Form takes a different approach.

It minimizes unnecessary re-renders by managing form values outside the typical React component state update cycle and allowing components to subscribe to specific form state.

For example:

const { register, formState: { errors }, } = useForm(); 

React Hook Form also provides useWatch and useFormState to subscribe to specific values or state updates.

This can be particularly useful when building large forms.

However, don't misunderstand this:

Formik is not automatically slow, and React Hook Form is not automatically fast in every situation.

Performance depends on component structure, validation complexity, subscriptions, and how the form is implemented.

For a small login form, you might never notice a meaningful difference.

For a large dynamic form, React Hook Form's approach can offer a noticeable advantage.

5. Validation: How Do Both Libraries Handle It?

Validation is one of the most important parts of form management.

Both libraries support:

  • Required field validation
  • Minimum and maximum length
  • Email validation
  • Custom validation
  • Cross-field validation
  • Schema-based validation

Let's understand how they handle schema validation.

Formik with Yup

Formik has built-in support for Yup integration.

First, install Yup:

npm install yup 

Create a validation schema:

import * as Yup from "yup"; const validationSchema = Yup.object({ fullName: Yup.string() .trim() .required("Full name is required"), email: Yup.string() .email("Invalid email address") .required("Email is required"), password: Yup.string() .min(8, "Minimum 8 characters required") .required("Password is required"), confirmPassword: Yup.string() .oneOf( [Yup.ref("password")], "Passwords do not match" ) .required("Please confirm your password"), }); 

Now pass this schema to Formik:

const formik = useFormik({ initialValues: { fullName: "", email: "", password: "", confirmPassword: "", }, validationSchema, onSubmit: (values) => { console.log(values); }, }); 

Formik automatically uses the schema to validate the form.

React Hook Form with Yup

React Hook Form supports external validation libraries through resolvers.

Install the required packages:

npm install yup @hookform/resolvers 

Create the same Yup schema:

import * as Yup from "yup"; const validationSchema = Yup.object({ fullName: Yup.string() .trim() .required("Full name is required"), email: Yup.string() .email("Invalid email address") .required("Email is required"), password: Yup.string() .min(8, "Minimum 8 characters required") .required("Password is required"), confirmPassword: Yup.string() .oneOf( [Yup.ref("password")], "Passwords do not match" ) .required("Please confirm your password"), }); 

Connect it with React Hook Form:

Related posts