Crafting Forms in React: Vanilla vs. React Hook Form vs. Formik

josephmaina

Joseph Maina

Posted on April 12, 2024

Crafting Forms in React: Vanilla vs. React Hook Form vs. Formik

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.

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.

You may want look at what we shall end up with in this repo on GitHub and try the code on this sandbox.

Crafting Forms in React: the Vanilla Way

The structure of the newsletter signup form, in pure React, is standard HTML as you know it.

Easy peasy.



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>
  );
}


Enter fullscreen mode Exit fullscreen mode

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 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.



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>
  )
}


Enter fullscreen mode Exit fullscreen mode

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).

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 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 is one of the most popular libraries for building forms in React apps with over 39k stars on GitHub. The library has no external dependencies according to Bundle Phobia.

React Hook Form is built on top of 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:



npm install react-hook-form


Enter fullscreen mode Exit fullscreen mode

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.



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>
  )
}


Enter fullscreen mode Exit fullscreen mode

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.

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 are used to build the form when using React Hook Form.

To convert the previous newsletter signup form 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


// 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>


Enter fullscreen mode Exit fullscreen mode
  • To a React Hook Form text input field


<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>


Enter fullscreen mode Exit fullscreen mode

In this piece of code:

  • <input /> is a normal HTML tag 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. 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.



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


Enter fullscreen mode Exit fullscreen mode

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.



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


Enter fullscreen mode Exit fullscreen mode

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 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.

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 to connect the form components.

More specifically, Formik provides the following components:

  • <Formik /> is the parent component. It uses React 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

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.



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>
  );
}


Enter fullscreen mode Exit fullscreen mode

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 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.



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>
  );
}


Enter fullscreen mode Exit fullscreen mode

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. You may also choose to use a data validation library like Yup, Zod, Joi and others. Formik supports Yup by default.
  • 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 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 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 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. 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!

💖 💪 🙅 🚩
josephmaina
Joseph Maina

Posted on April 12, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related