DEV Community

Cover image for Crafting Forms in React: Vanilla vs. React Hook Form vs. Formik
Joseph Maina
Joseph Maina

Posted on

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!

Top comments (6)

Collapse
 
elsyng profile image
Ellis • Edited

With React and javascript, the <form> element or a form library is not necessary at all.

For instance we can remove <form> from the VanillaForm code, and we can just make it work just fine with React. "Form" is optional, and perhaps redundant clutter as such. Less is more.

The urge to use <form> reminds me of the push for "semantic html", which has its pros and cons.

Collapse
 
josephmaina profile image
Joseph Maina

You are right, Ellis. A form library is not necessary when building forms in a React app. You can do so much with React only.

I think 'the push for "semantic html"' is good, though. Using the form element conveys the meaning, as is the idea behind the use of semantic HTML.

What would you say are the cons of using semantic HTML?

Collapse
 
elsyng profile image
Ellis • Edited

Hi Joseph,

It's kind of you to ask, thanks. And this post was not about semantic html, so i don't want to hijack the main subject. So i should try to keep it as a small discussion :)

There are a number of posts on the web about the cons of semantic html, or why it is a failure. I read a good one recently, I've just looked for it, but i couldn't find it. So here are my humble thoughts about semantic html.

From semrush.com/blog/semantic-html5-gu...
Image description

No offence to anyone, but imho this is a pathetic attempt to define the structure of a (web) page or document. I think this is one very very specific and childishly narrow template of a huge number of possibilities for a web page. It doesn't represent a page to me, and i think to most people. And the terminology is plain ambiguous and thus incorrect. A header can be nav, a nav can be header, an header can be aside, a nav can be aside, what is an article, what is a section, what is a p, what's a main, what's the difference? It is not immediately obvious to me. The point is: if it needs describing, then it's a failure. It should make sense immediately, it should be clear and non-ambiguous, without the need for descriptions and definitions.

It's just extra clutter. I think the added value is less than the added clutter.

And as a developer, honestly, I look at the structure on the left, and it is much more quickly much clearer to me what that page is doing. I think semantic html doesn't really help the developer, or the reader.

When i raise these issues, people say: yeah, but it helps the accessibility tools. Maybe, maybe not, i don't know. A huge percentage of people who say this also have never used or seen an accessibility tool. But perhaps we should improve the accessibility tools to make sense of a page, rather than try to force this kind of half baked ideas (imho).

It's a bit similar to adding a lot of extra optimisation (=clutter) into software code, which should be instead handled by the optimisers (in the compilers, builders, browsers etc.)

In my experience, few developers and teams are really using semantic, and when they do, they do so just because someone else said so. They sprinkle some "semantic" words here and there. It's a bit like "the emperor's new clothes" as an idiom. (en.wikipedia.org/wiki/The_Emperor%...)

This, in my experience and from my perspective as a senior software developer.

Thread Thread
 
josephmaina profile image
Joseph Maina

Wow! Thanks for diving deeper into this topic. You have opened my eyes, and I will do more research on this topic. I have always assumed semantic HTML improves accessibility.

But I need to look at it from a different perspective. As you say, most of us developers have never interacted with accessibility tools but assume semantic HTML works with them.

(PS: What is your Twitter or GitHub @ if you don't mind sharing?)

Thread Thread
 
elsyng profile image
Ellis

The so-called "semantic html" might be standing in the way for a better approach for accessibility perhaps 😉

(I have very little (social) presence on twitter / github i'm afraid :)

Thread Thread
 
elsyng profile image
Ellis • Edited

I think the problem with the semantic html, in my eyes, as a developer, is:

On a page, basically we have lots of (square) boxes, which are containers, to group and contain other elements in a box. They could have called it a "box", but they decided to call it a "div". And if it is inline, it's a "span". The distinction between div and span is clear and needed very often, enough to justify calling them by two separate names.

In the earlier years of html, when web pages and techniques were much much simpler, people thought it would be useful to add more elements like p/i/b/em/etc. At the current modern age of html, things are much more complex, and we use complex and advanced definition sets and tools for styling etc. These elements like p/i/b/em/etc have become not only redundant, but they also clutter the naming space. With the hindsight, a simpler html with a much smaller number of elements would have been better. For many elements, the distinction they provide is too small/arbitrary/unclear/ambiguous to justify an individual name.

In other words, a box is a box. Pick a name, for example "div", and stick with it. Say, you wanted to call red cars "reds", fast cars "fasts", long cars "longs", I think that's wrong. So I believe we should use the "role" attribute, instead of giving div other names for each role.

A header and a footer is too similar to a div, with some minor differences (ie the "role"), which doesn't justify giving them a new name. The difference should be an attribute.

(Complete list of WAI ARIA roles)
<nav> should be <div role="navigation">,
<header> should be <div role="heading">,
<footer> should be <div role="contentinfo">,
just like
<i> should be <span class="whatever">.

And we'd still have the same or perhaps improved accessibility benefits: it's easier/better to add more roles, than to add more html elements.

I think. 🤷