How to use Formik to make forms in React

ยท

7 min read

If you're a React developer, you know that creating forms can be a huge hassle: you have to set up state for each form field, then add value and onChange props to each input, not to mention state for errors and error messages for each field. Phew!

Luckily, there's a better way to do this - we can use a library called Formik to manage our React forms. With Formik, the same form page can be created with drastically fewer lines of code, since Formik

  • manages all of the state for us and
  • takes care of validation and error messages, with the help of Yup.

In this series, we're going to be creating this registration form for a developer only social network.

The next post will be all about adding validation with Yup, and this one will mainly focus on using Formik to create a barebones form.

Creating the form skeleton and the styles

Since this isn't a HTML and CSS tutorial, I won't be focusing much on this part of the build, though you're welcome to take a closer look at it. Here is the JSX for the form container and the header text:

<div className="form-container">
        <h1 className="greeting heading">
          Hey there{" "}
          <span role="img" aria-label="wave emoji">
            ๐Ÿ‘‹๐Ÿผ
          </span>
        </h1>

        <h2 className="heading cta">Let's get you on board!</h2>
</div>

I'll put the styles at the end of the post, since they're a bit long and will distract from the main focus of the article if I put them here. Speaking of which, let's dive right in to making the form!

The Formik context

This component is a context provider that manages the state for the form inputs and their errors. Its children - the Form, Field and ErrorMessage components - use this context under the hood to link to a certain field's state. We use it like so:

<Formik
          initialValues={{
            firstName: "",
            email: "",
            group: "",
            bio: "",
            github: "",
            username: "",
            password: "",
            passwordTwo: "",
            tnc: false
          }}

          onSubmit={values => {
            console.log(values);
            alert("Registered successfully");
          }}
>
</Formik>

There are two things to note here.

  1. The initialValues prop takes in an object that defines the initial values (surprise, surprise ๐Ÿ˜) for each of your fields. Note the use of double curly braces, 1 pair for the JavaScript expression in JSX and another for the object.

  2. The onSubmit prop takes in a function that will be called when the form is submitted. An object containing all of the values of your fields will be passed to this function. Here, I've just console.logged them, but this could just as easily be an axios or fetch request. A really great thing about Formik is that you don't need to use e.preventDefault() to prevent the page from refreshing - Formik does that automagically!

The Form component

To be honest, there really isn't much to this component, which is nested inside the Formik provider. For all intents and purposes, we can treat it just like the form element in HTML.

<Form classname="form" autoComplete="off">
</Form>

As you can see, it can take the same props as the attributes of the form element. Apart from autoComplete, you can also use autoValidate, etc. Inside it, we'll add all the form fields.

Field and ErrorMessage

<div>
    <label htmlFor="firstName">First name*</label>
    <Field name="firstName" />
    <p className="error">
        <ErrorMessage name="firstName" />
    </p>
</div>

The Field component, which returns an input element, is fairly straightforward and it takes one prop called name which links it to the state we set up in the initialValues prop in the Formik component. Therefore, it is essential that you use the same identifier in both places.

The ErrorMessage component is quite self-explanatory. It only comes into play when we add validation (which we'll do in the next post in this series!) but it is very easy to understand. Just like Field, it has a name prop, where we pass in one of our field names from the initialValues. What's more interesting is why it's nested inside a p tag. This is because it returns a string, not JSX, and this is rendered to the DOM as a text node, which can't be styled using CSS. So, we have to put it inside an HTML element that can be styled; I've used a p here, but we can also use span or other text tags.

One more thing: I've put the label, Field and ErrorMessage inside a div just to make it easier to style. I find that this really helps when we use things like CSS grid to position the fields.

Radio buttons

The code for the email field is exactly the same, but the next field consists of radio buttons, which are actually quite easy to implement in Formik.

<div className="left-align">
  <label htmlFor="group">I am a*</label>

  <label className="radio-label">
    <Field
      className="radio"
      type="radio"
      name="group"
      value="frontend"
    />
    Front-end engineer
  </label>

  <label className="radio-label">
    <Field
      className="radio"
      type="radio"
      name="group"
      value="backend"
    />
    Back-end engineer
  </label>

  <label className="radio-label">
    <Field
      className="radio"
      type="radio"
      name="group"
      value="fullstack"
    />
    Fullstack engineer
  </label>
</div>

Each radio button option is placed inside its own label. It uses the same Field component, but we've got a few more props this time.

  • className -> This is used to style the field
  • name -> This is the same as in the other fields, and links the buttons to the state. It has to be the same for each radio button in a group.
  • type -> This tells Formik to return radio buttons instead of input fields. It can also take the value of "checkbox", as we'll see later.
  • value -> This is just like the value attribute in HTML. It specifies what value the field's state should take when an option is selected.

Textareas

<div>
  <label htmlFor="bio">Tell us a bit about yourself</label>
  <Field
    component="textarea"
    name="bio"
    placeholder="Your bio..."
  />
  <p className="error">
    <ErrorMessage name="bio" />
  </p>
</div>

The component prop tells the Field component which HTML element to return - in this case, a textarea. Also notice the placeholder prop, which can be used in any Field except radio buttons and checkboxes.

Checkboxes

Since the GitHub, username and password fields are just normal inputs, there's nothing to much to look at there. It's the same thing as the name and email fields, albeit the name props are different. Instead, let's add a checkbox asking the user to agree to the terms and conditions.

When we wrote the initial value for the T&C field, it was a boolean value and not a string, as you may have noticed. This is because there are only two values it can take: either the user accepts the T&C or they don't. The syntax is very similar to that of a radio button:

<div className="tnc">
  <label htmlFor="tnc">
    <Field className="checkbox" type="checkbox" name="tnc" />I
    accept the terms and conditions
  </label>
  <p className="error">
    <ErrorMessage name="tnc" />
  </p>
</div>

It has the name and type props, but it doesn't have a value prop, because it can only either be true or false.

And finally, we have the submit button, which is exactly the same as normal HTML.

<button type="submit">Let's get started!</button>

At this point, your form should look exactly the same as in the CodeSandbox embed, sans the validation of course. Yay! We just built a form - and a good-looking one at that, if I do say so myself ๐Ÿ˜† - with Formik, with much less effort and repetition than if we had made it ourselves just with JSX.

If you found this helpful, share it with devs who might need it and stay tuned for the next post in this series, where we'll use Yup to add validation to the form!

Oh, and as promised, here are the styles for this project:

@import url("https://fonts.googleapis.com/css2?family=Roboto+Slab&display=swap");
@import url("https://fonts.googleapis.com/css2?family=Roboto&display=swap");

* {
  box-sizing: border-box;
}

.App {
  font-family: sans-serif;
}

.form-container {
  max-width: 400px;
  margin: 50px auto;
  border-top: 5px solid #31e6ff;
  box-shadow: 0px 10px 15px -3px rgba(26, 26, 26, 0.25);
  padding: 10px;
}

.heading {
  font-family: "Roboto Slab", sans-serif;
  text-align: center;
  margin: 5px 0px;
}

.cta {
  margin-bottom: 15px;
}

.form {
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
}

input {
  height: 50px;
  width: 300px;
  padding: 5px 10px;
  border: none;
  margin: 5px 0px;
  border-radius: 5px;
  background-color: #f2f2f2;
}

input:focus,
textarea:focus {
  outline: none;
  border-radius: 5px 5px 0px 0px;
  border-bottom: 3px solid #31e6ff;
}

label {
  font-family: Roboto, sans-serif;
  font-size: 12px;
  display: block;
}

textarea {
  font-family: "Roboto", sans-serif;
  min-width: 300px;
  max-width: 300px;
  max-height: 200px;
  height: 50px;
  padding: 15px;
  border: none;
  margin: 5px 0px;
  border-radius: 5px;
  background-color: #f2f2f2;
}

.left-align {
  width: 300px;
}

.radio {
  height: 15px;
  width: 15px;
  margin-right: 10px;
}

.radio-label {
  padding: 5px;
  display: flex;
  align-items: center;
}

.radio-label:last-of-type {
  margin-bottom: 15px;
}

.checkbox {
  height: 15px;
  width: 15px;
  border-radius: 3px;
  margin: 0px 10px 15px 10px;
}

.error {
  min-height: 15px;
  color: #cc0000;
  font-size: 12px;
  margin: 0px;
  margin-bottom: 15px;
}

.submit {
  background: #31e6ff;
  border: none;
  padding: 10px 20px;
  border-radius: 5px;
  color: #ffffff;
  font-family: "Roboto", sans-serif;
  font-size: 18px;
  margin: 20px 0px;
  transition: all 200ms;
}

.submit:hover {
  cursor: pointer;
  background-color: #00ccff;
  box-shadow: 0px 6px 10px -1px rgba(26, 26, 26, 0.25);
}

All this does is import a few fonts, style the form container for its shadow and top border, and then style the form fields.

TTFN, Hari!

ย