Building Forms with Formik and React - Part 2
Tags: react, javascript, userinterface, webdev
Published: 2020-03-20 13:25:18


## About this post

In the previous post of this series, we built a form using React only. We used React's `state` to store the values held by each `<input>`, making them React-controlled components. The resulting form, which was functional for our purposes, included a lot of boilerplate. But, we can do much better!

We're going to introduce a library called Formik that should hopefully make building forms much less painful.

## Adding Formik to the Project

To add Formik to our project, we will use the `npm install` command, as we did for Bootstrap in the last post.

```sh
$ npm install --save formik
```

And make sure that `formik` is now in your list of dependencies.

```json
/* Part of package.json  */
"dependencies": {
  "@testing-library/jest-dom": "^4.2.4",
  "@testing-library/react": "^9.3.2",
  "@testing-library/user-event": "^7.1.2",
  "bootstrap": "^4.4.1",
  "formik": "^2.1.4",  /* Make sure you see this line */
  "react": "^16.12.0",
  "react-dom": "^16.12.0",
  "react-scripts": "3.4.0"
},
```

## Rewriting `LoginForm`

In the original version of `LoginForm`, we had a lot of boilerplate involved. We needed to set up `state` to handle current form state, form validation and error messages. In Formik, there is built-in support for handling state. Validation will still be specified by us, but Formik has a prop we can set for this validation function.

We're not going to duplicate the entire `LoginForm` class from the previous post, but we should touch on its interface. The component had the following methods, with summaries of what they did:

```js
// Class structure for LoginForm
class LoginForm extends React.Component {
  constructor(props) {
    /* In this method we initialized `state` for the form values,
       the field validity, and their errors. */
    ...
  }

  onSubmit(event) {
    /* Iterated over the form values and checked if they were valid */
    ...
  }

  validate(name, value) {
    /* Checked if a given value was valid, based on the field name */
    ...
  }

  onChange(event) {
    /* Because this is a controlled component, we need to update our
       form values whenever they change */
    ...
  }

  render() {
    /* The HTML for our component */
    ...
  }
}
```

By using Formik, we no longer need to do our own `onChange` updating. The validation step is handled by Formik, so we don't need to add that to our `onSubmit` method. Finally, Formik handles initial values, stores validity, and lets the user check errors via a `validate` method.

Rewriting our `LoginForm` using Formik will make building this form much less complicated, and much more routine.

### Import Formik

In order to use Formik, we need to import it. Here's what the top of `LoginForm.js` should look like.

```js
// Top of LoginForm.jsx
import React from 'react';
import { Formik, Form, Field, ErrorMessage } from "formik";

class LoginForm extends React.Component {
  ...
}
```

### Rewriting `render`

We're going to use our new `Formik` component to rewrite the `render` method of our `LoginForm`. The main `<form>` tag will be replaced by `<Formik>`. In turn `<Formik>` is passed a function that renders the `<Form>`. Note that the 'F' is capitalized, since this component is specific to Formik.

The `<Formik>` component requires a few props be set to be useable:

* `initialValues` - Determines the initial state of the form.
* `validate` - A function which validates the form, and updates any errors.
* `onSubmit` (optional) - A function we want to call after validation, but before final submit. This is likely where you would send your payload to the HTTP server.

Comments are given inline, pointing out important usage of `<Form>`, `<Field>`, and `<ErrorMessage>`.

```js
class LoginForm extends React.Component {
  ...
  render() {
    return (
      <div className="container">
        <div className="row justify-content-center">
          <div className="col-lg-6">
            <div className="col-lg-12">
              /* Add new <Formik> component with two new methods that we have
                 not written yet: `initialValues` and `validate` */
              <Formik
                initialValues={this.initialValues()}
                validate={this.validate.bind(this)}>
                {
                  props => (
                    /* Our <Form> component is our main container */
                    <Form>
                      <div className="form-group">
                        <label htmlFor="email">Email</label>
                        /* This <Field> handles state change for the <input> */
                        <Field
                          type="email"
                          name="email"
                          placeholder="Enter email"
                          className={`form-control ${props.errors.email ? "is-invalid" : ""}`}
                        />
                        /* Formik handles error messages for us with this component. */
                        <ErrorMessage
                          component="div"
                          name="email"
                          className="invalid-feedback"
                        />
                      </div>

                      /* The changes to the password field are similar */
                      <div className="form-group">
                        <label htmlFor="password">Password</label>
                        <Field
                          type="password"
                          name="password"
                          placeholder="Enter password"
                          className={`form-control ${props.errors.password ? "is-invalid" : ""}`}
                        />
                        <ErrorMessage
                          component="div"
                          name="password"
                          className="invalid-feedback"
                        />
                      </div>

                      <button type="submit" className="btn btn-primary btn-block">
                        Log in
                      </button>                      
                    </Form>
                  )
                }
              </Formik>
            </div>
          </div>
        </div>
      </div>      
    );
  }
}
```

### Adding `initialValues` and `validate`

The biggest change to our form is in the `render` method. We are close to done with our rewrite, but we still have a two more methods: `initialValues` and `validate`. Below are the implementations that should work for our needs:

```js
class LoginForm extends React.Component {
  initialValues() {
    return {
      email: "",
      password: ""
    }
  }

  validate(values) {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/i;
    let errors = {};

    if (values.email === "") {
      errors.email = "Email is missing";
    } else if (!emailRegex.test(values.email)) {
      errors.email = "Email is not in the expected email address standard format";
    }
    if (values.password === "") {
      errors.password = "Password is missing";
    } else if (values.password.length < 6) {
      errors.password = "Password must be 6 characters at minimum";
    }
    return errors;    
  }
  ...
}
```

The `initialValues` method returns a new JavaScript object with empty strings for `email` and `password`. The `validate` method has changed to taking a JavaScript object with the current form values. We handle not only the two previous validations from our React-only form, but also verify that these fields are not empty, letting the user know they are missing.

We're now ready to test our the refactored form.

## Testing it Out

After making those changes, we should have a working login page again.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/l3knrm268z3ouz4tf4t0.png)

When using this form, you'll notice that the error message for email appears immediately after switching from email to password. Also, we are checking for multiple validations,  not just required or email format.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/9zkfwajcphskg2f0e5xk.png)

Our new form has the same functionality as the previous React form, which means our refactor was successful! However, we can go further. In particular, the `validate` method can be refactored further. Formik has built in support for another library called Yup, which allows us to describe the fields of our form in a declarative way.

## Using Yup for Validation

Before we get into what Yup can do, let's first add it to our `package.json`.

```sh
$ npm install --save yup
```

Verify you have the correct package:

```json
/* Part of package.json */
"dependencies": {
  "@testing-library/jest-dom": "^4.2.4",
  "@testing-library/react": "^9.3.2",
  "@testing-library/user-event": "^7.1.2",
  "bootstrap": "^4.4.1",
  "formik": "^2.1.4",
  "react": "^16.12.0",
  "react-dom": "^16.12.0",
  "react-scripts": "3.4.0",
  "yup": "^0.28.1" /* Make sure you see this line */
}
```

Now let's import it into our project.

```js
// Top of LoginForm.jsx
import React from 'react';
import { Formik, Form, Field, ErrorMessage } from "formik";
import * as Yup from 'yup';  /* Add this line to */

class LoginForm extends React.Component {
  ...
}
```

The new `Yup` object we've imported has the ability to create JSON schemas via the `object` method. Let's add this code just above our `LoginForm`:

```js
import * as Yup from 'yup';

/* Add this new schema */
const loginFormSchema = Yup.object().shape({
  email: Yup.string()
            .email("Email is not in the expected email address standard format")
            .required("Email is missing"),
  password: Yup.string()
            .required("Password is required")
            .min(6, "Password must be 6 characters at minimum")
});

class LoginForm extends React.Component {
  ...
}
```

We're going to get rid of the `validate` prop of the `Formik` component, but we're going to add a `validateSchema` prop, to use the new schema:

```js
class LoginForm extends React.Component {
  ...
  render() {
    return (
      <div className="container">
        <div className="row justify-content-center">
          <div className="col-lg-6">
            <div className="col-lg-12">
              <Formik
                initialValues={this.initialValues()}
                /* New prop validateSchema */
                validateSchema={loginFormSchema}>
                ...
              </Formik>
            </div>
          </div>
        </div>
      </div>
    );
  }
}
```

Let's test our form, to confirm it still works properly.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/m9l1rcw823kv8tdjs29u.png)

Success! Our `LoginForm` component now uses Formik for it's `<input>` fields and Yup for defining the validations.

## Conclusion

React is a very useful framework for building interactive websites. However, because React must control all state on the page, we cannot use vanilla DOM elements. To provide a user experience that most customers expect of web forms, we can use Formik to handle common needs, including validation. We refactored our hand-written `validate` method to use Yup, which allows us to declaratively describe our form.