Crafting Forms in React: Vanilla vs. React Hook Form vs. Formik
Tags: react, webdev, typescript, frontend
Published: 2024-04-12 18:00:00

When you are building apps using React, one day you will require user interaction by way of using forms. Be it for user signup, user login, newsletter subscription, or any other *form* of interaction.

Building forms in React has its own challenges. There are so many moving parts:

1. **Form state**. Forms have fields for users to *type* data into. How do you get values in and out of the form fields? How do you handle changes in each field?
2. **Data validation**. How do you ensure that data typed into the form fields is in the expected format?
3. **Form submission**. If you already have valid data in the form fields, how do you get it to a data store, the database or elsewhere? Once data is submitted, what happens?

And for your forms to work, as you expect them to, then all these things have to be put in check.

In this article, we shall explore how that can be done: **one**, by writing all the code ourselves to control the forms; and, **two**, by relying on external libraries to simplify building forms.

## Newsletter Signup Form: An Overview

Let's *wet our feet* by building a newsletter signup form.

![The newsletter signup form we are building.](https://raw.githubusercontent.com/JosephMainaDev/react-form-libraries/main/public/newslettersignupform.png)

The newsletter signup form has only a few fields with simple data validation requirements.

|    Field     |   type     |  Required? |            Data Validation    |
|--------------|------------|------------|-------------------------------|
| First name   |  text      | required   | max length of 20 characters   |
| Title        | select     | required   |                               |
| Email        |  email     | required   | standard email pattern        |
| Message      | textarea   | optional   |                               |

The form has two `input` elements (text and email), one `select` element (title of the user) and an optional message `textarea` element. The required data is validated using [standard HTML validation rules](https://developer.mozilla.org/en-US/docs/Learn/Forms/Form_validation#using_built-in_form_validation).

You may want look at what we shall end up with in [this repo](https://github.com/JosephMainaDev/react-form-libraries) on GitHub and [try the code on this sandbox](https://codesandbox.io/p/github/JosephMainaDev/react-form-libraries).

## Crafting Forms in React: the Vanilla Way

The *structure* of the newsletter signup form, in pure React, is [standard HTML](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form) as you know it.

Easy peasy.

```jsx
export default function VanillaForm() {

  return (
    <div>
      <form id="newsletter-form" action="#" method="post">
        <label htmlFor="name">Name:</label>
        <input type="text" id="name" name="name" minlength="3" maxlength="20" required />

        <label htmlFor="title">Title:</label>
        <select id="title" name="title">
          <option value="">Please choose a title</option>
          <option value="sir">Sir</option>
          <option value="madam">Madam</option>
        </select>

        <label htmlFor="email">Email:</label>
        <input type="email" id="email" name="email" pattern="/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i" required />

        <label htmlFor="message">Message:</label>
        <textarea id="message" name="message" rows="5"></textarea>

        <button type="submit">Submit</button>
      </form>
    </div>
  );
}
```

Nothing fancy here. We are using the `input`, `select` and `textarea` form elements. Specify the type of the input field (`type="text"`), give the field a name (`name="email"`) for reference and pass in validation rules.

Notice how [standard HTML validation rules](https://developer.mozilla.org/en-US/docs/Learn/Forms/Form_validation#using_built-in_form_validation) have been used. These rules will keep check of the data we type into the fields.

### Keeping track of the form fields

To keep track of the state of each form field and, listen to changes in the fields, we can rely on React `useState` hook.

```jsx
export default function VanillaForm() {
  // track the state of the form fields
  const [formState, setFormState] = useState({
    name: "",
    email: "",
    title: null,
    message: ""
  });

  // handle form field changes
  const handleChange = (event) => {
    const { name, value } = event.target;
    setFormState((prevState) => ({
      ...prevState,
      [name]: value,
    }));
  };

  return (
    <form>
      {/* form elements here*/}
    </form>
  )
}
```

The `formState` object stores all our form state while the `setFormState` method *mutates* the state whenever a change happens in a field. (You may want to look at the code for the completed vanilla form in [this repo on GitHub](https://github.com/JosephMainaDev/react-form-libraries)).

For a simple form like the one we are building, doing this doesn't seem like much. But, imagine working with tens or hundreds of forms in an application. Doing this *manual* work will quickly turn into a mess. And will give you a headache (or chest pain).

Engineers know this struggle is real. And have come up with solutions in the form of libraries that you can rely on to manage the challenges.

For the rest of this article we turn our attention to [two of the most popular libraries](https://npmtrends.com/formik-vs-react-final-form-vs-react-hook-form-vs-redux-form) used to create forms in React apps and see how they solve the challenge.

## Crafting Forms in React: Hook It Up

The first solution to the challenge of creating forms in React apps is the React Hook Form library.

[React Hook Form](https://react-hook-form.com/get-started) is one of the most popular libraries for building forms in React apps with over [39k stars on GitHub](https://github.com/react-hook-form/react-hook-form). The library has no external dependencies according to [Bundle Phobia](https://bundlephobia.com/package/react-hook-form@7.51.2).

React Hook Form is built on top of [React hooks](https://react.dev/reference/react/hooks) and is designed to be used as a hook. That means there are no components for you to import to build a form. Instead, you call the custom hook (`useForm`) and apply methods from the hook on an normal HTML form element.

To get started, install the package in your project:

```shell
npm install react-hook-form
```

We are building the same newsletter signup form in the previous step but now utilizing the goodies that React Hook Form comes with.

After installing the package, import the custom hook `useForm` and call it inside your component.

```jsx
import { useForm } from "react-hook-form"

function NewsletterSignupForm() {
  // call the `useForm` hook
  const {
    register,
    formState: { errors },
    handleSubmit,
  } = useForm({ initialValues: { name: "", email: "", message: "" } });

  return (
    <form>
      {/* HTML form elements */}
    </form>
  )
}
```

Notice how the `useForm` hook is called in the form component.

The `useForm` hook is called with one *optional* object argument and returns methods that you use to work with your form.

The object argument can have several properties including:

- `defaultValues`: which specifies the default values for the form fields, and,
- `resolver`: a method for validating the data we put in the form fields

The hook then gives us methods for working with the form. Here is an overview of the methods we shall use in this section:

- `regiter`: a method for registering input fields within the form to enable React Hook Form to track their values and manage form state
- `formState`: an object that holds the current state of the form including validation errors
- `handleSubmit`: a method used as an event handler for form submissions

To learn more about the `useForm` hook please refer to [the documentation](https://react-hook-form.com/docs/useform).

### Newsletter Signup Form Using React Hook Form

Our newsletter signup form has a name input field, email input field, a title select field and a message textarea. The [generic HTML form elements](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form) are used to build the form when using React Hook Form.

To convert the [previous newsletter signup form](#crafting-forms-in-react-the-vanilla-way) to use the React Hook Form library:

1. Call the `register` method on each form field and pass the `name` of the field and any validation requirements
2. Include an optional `error` message to show when the data is invalid

As example, let's turn the `name` input element into a React Hook Form element.

- **From** a regular HTML text input field

```jsx
// vanilla React text input field
<form>
  <label htmlFor="name">Name:</label>
  <input type="text" id="name" name="name" required />
  {/* other form elements go here */}
</form>
```

- **To** a React Hook Form text input field

```jsx
<form>
  <label htmlFor="name">Name:</label>
  <input {...register("name", { required: true, maxLength: 20 })} />
  {errors.name && <p>This field is required</p>}
  {errors.name?.type === "maxLength" && (
    <p>Name cannot exceed 20 characters</p>
  )}
  {/* other form elements go here */}
</form>
```

In this piece of code:

- `<input />` is a normal [HTML tag](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input) as we know it. We are using it for the name text input.
- `register` is a method given to us by the `useForm` hook. The method notifies React Hook Form to keep track of the name input field, watch for changes and validate the data in the field. The `register` method has the following arguments:

  - `name` of the input field which should be unique within this form (for React Hook Form to identify the field)
  - Validation rules (if any). In this form we are using the [standard HTML validation rules](https://developer.mozilla.org/en-US/docs/Learn/Forms/Form_validation#using_built-in_form_validation). In this input element, `name` is **required** and should be **20 characters** max.

If the data in the input field is not valid, React Hook Form will record the errors in the `formState` object. We can access the errors and notify the user to make corrections.

```jsx
{errors.name?.type === "maxLength" && (
  <p>Name cannot exceed 20 characters</p>
)}
```

This code checks whether the `name` property exists in the errors object. If present, it checks whether its type matches our validation rules (a max length of 20 characters) so that we can use it to report to the user to make corrections. If the error type doesn't match, then we are happy our name is short enough 😄!

Once that is done, React Hook Form will be aware that this field should be tracked and, should only allow submission of the data (to a dabatase or wherever) if it's valid.

### Submitting the Form Using React Hook Form

Talking of form submission.

React Hook Form gives us a method (`handleSubmit`) to handle form submission. You pass in a function to be called to submit your form and that is it.

Let's do that here.

```jsx
function NewsletterSignupForm() {
  // the rest of the component
  const onSubmit = (data) => console.log(data);
  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      {/* rest of the form */}
    </form>
}
```

We are simulating (read pretending) form submission by logging the content to the console. You pass in a function (`onSubmit`) to be called by `handleSubmit` and that is it. `handleSubmit` will ensure that form submission only succeeds if our form has no errors. So you can be sure that no invalid data will reach the database.

### Putting it all together

It's now your turn. Try to convert the other fields from vanilla React form fields use the React Hook Form library.

I hope you are having fun doing it.

Now turn to [my impelementation](https://github.com/JosephMainaDev/react-form-libraries) and compare with what you have.

## Crafting Forms in React: Formik Your Way

Lastly, let's look at the other popular library known as [Formik](https://formik.org/docs/tutorial).

Formik has a slightly different approach for building forms in React apps. Unlike the React Hook Form library which gives you a custom hook for working with forms, Formik provides a set of custom React *components* that you use to build forms.

Formik relys on [React Context]((https://react.dev/learn/passing-data-deeply-with-context)) to connect the form components.

More specifically, Formik provides the following components:

- `<Formik />` is the parent component. It uses [React Context](https://react.dev/learn/passing-data-deeply-with-context) to keep track of the form's state and exposes methods used to control the form fields.
- `<Form />` is a wrapper for the standard HTML `<form />` element. It hooks into Formik's `handleSubmit`, `handleReset` and other methods.
- `<Field />` defaults to an HTML `<input />` element. It uses the `name` attribute to match an input element to Formik's state.
- `<ErrorMessage />` is a component that renders an error message from a form field if the field has been visited and has invalid data. For instance, if you type an ivalid email address in an email input field and then click outside the email input field, an error is registered for that field and will be shown by the `<ErrorMessage />` component.

![Invalid email address in the input field](https://raw.githubusercontent.com/JosephMainaDev/react-form-libraries/main/public/invalid-email.png)

To build our earlier newsletter signup form using Formik, first install the package as a dependency in your project:

`npm install formik --save`

And import the components to build the form.

```jsx
import { Formik, Form, Field, ErrorMessage } from "formik";

export default function NewsletterSignupForm() {
  // other form logic
  return (
    <Formik>
      {() => (
        <Form>
          <label htmlFor="name">Name:</label>
          <Field type="text" id="name" name="name" placeholder="First Last" />
          <ErrorMessage name="name" component="div" />

          {/* other form fields go here */}

          <button type="submit">
            Submit
          </button>
        </Form>
      )}
    </Formik>
  );
}
```

### What's going on here?

Let's zoom in on this piece of code to see what's happening.

- The `<Formik />` component [uses the render props](https://javascript.plainenglish.io/reacts-render-props-pattern-a-comprehensive-guide-f9d5f07be179) pattern to render the other components needed to build our form. `<Formik />` accepts several props that we shall look at in the next section.
- Notice how the other components are passed to the parent `<Formik />` component (as render props).

  - The `<Form />` component returns a normal HTML form element.
  - By default, the `<Field />` component returns an input element specified by the `type` prop (here, `type="text"` for the name field).
  - Formik keeps track of errors in our form fields and you can use the `<ErrorMessage />` component to show the errors to the user.

You use these components to build complex forms.

This is only the `name` field of the newsletter signup form. As an exercise, try adding the `email` field to the newsletter signup form using the Formik form components.

### Tracking Form State Using Formik

A form library is only useful if it can simplify the process of keeping track of the form's state, validating the data we type into the form fields and submitting the form. Let's see how Formik does that.

```jsx
const validate = (values) => {
  const errors = {};

  if (!values.name) {
    errors.name = "Name is required";
  } else if (values.name.length > 20) {
    errors.name = "Name cannot exceed 20 characters";
  }
  // other validation rules go here

  return errors;
};

const handleSubmit = (data) => console.log(data)

export default function NewsletterSignupForm() {
  return (
    <Formik
      initialValues={{ name: "", email: "", message: "" }}
      validate={validate}
      onSubmit={handleSubmit}
    >
      {() => (
        <Form>
          {/* other form fields */}
        </Form>
      )}
    </Formik>
  );
}
```

A lot is going on here.

First, the `<Formik />` component accepts several `props`.

- `initialValues` holds initial data for the form fields if any.
- `validate` function for validating the data in the form. Here, we are using a custom validation method using the [standard HTML validation rules](https://developer.mozilla.org/en-US/docs/Learn/Forms/Form_validation#using_built-in_form_validation). You may also choose to use a data validation library like [Yup, Zod, Joi](https://npmtrends.com/joi-vs-yup-vs-zod) and others. Formik supports [Yup by default](https://formik.org/docs/guides/validation#validationschema).
- `onSubmit` function that holds the logic to submit the form data (to wherever). The function will be called by Formik when all the data in the form is valid.

And that is it. That is all that is required to build a form using Formik. By relying on the custom components offered by Formik, you can build complex forms easily.

Here is the [full code](https://github.com/JosephMainaDev/react-form-libraries) for the newsletter signup form built with Formik.

## Which to choose

It is possible to build a working form with only React and no external libraries are required. However, if the form has several fields, it becomes hectic writing all the form logic.

The choice between React Hook Form and Formik is largely on personal preference. I prefer to use React Hook Form just because it has no external dependencies and is a bit more popular than Formik. You can also use any data validation library (unlike Formik that only supports Yup).

If you choose to go with React Hook Form, you need to remember that it only gives you a hook and methods to work with forms. It is upto to you to build form components.

On the other hand, Formik gives you components that you can mix and match to have fully working forms. Formik has [builtin support for Yup](https://github.com/jquense/yup) for data validation.

## Explore further

You need not go with the most popular choices. In the JavaScript world, there is never a shortage of alternatives!

1. [React Final Form](https://final-form.org/docs/react-final-form/) is another library you can try. It is built on top of Final Form but has not received updates in like forever.
2. If you are using Ant Design components to build your app, they have a nice solution for [creating forms](https://ant.design/components/form). Be sure to check it out.

Let me know in the comments which other solutions you have found. And what works for you.

Thank you for reading 🙏 Catch you on the next one!