Nathan Sebhastian A senior software developer with experience in building fullstack JavaScript app with React and Express. You can find me online at sebhastian.com.

Building forms with Formik in React

11 min read 3353

Building Forms With Formik In React

Editor’s note: This article was updated January 28 2022 to update any outdated information and add the Using Formik’s handleChange section, Using Formik’s onSubmit section, and Using Formik’s setSubmitting section.

Building forms with React involves setting up state as the container for user data and props as the means to control how state is updated using user input. Validation can be done in between user inputs, and an arbitrary submit function is executed on form submit.

In this article, we’ll review how to use Formik in React to build forms, including:

Why should we use Formik in React?

Before we learn how to use Formik, let’s understand how forms are written in React without libraries and with minimal Bootstrap styling.

In the example below, we first initialize required state values in the constructor method. Because we have two required inputs — email and password — we initialize state for input values, input validity, and input errors:

constructor(props) {
  super(props);
  this.state = {
    formValues: {
      email: "",
      password: ""
    },
    formErrors: {
      email: "",
      password: ""
    },
    formValidity: {
      email: false,
      password: false
    },
    isSubmitting: false
  };
}

Next, we create the render the form’s method with input values derived from state:

render() {
  const { formValues, formErrors, isSubmitting } = this.state;
  return (
    <div className="container">
      <div className="row mb-5">
        <div className="col-lg-12 text-center">
          <h1 className="mt-5">Login Form</h1>
        </div>
      </div>
      <div className="row">
        <div className="col-lg-12">
          <form onSubmit={this.handleSubmit}>
            <div className="form-group">
              <label>Email address</label>
              <input
                type="email"
                name="email"
                className={`form-control ${
                  formErrors.email ? "is-invalid" : ""
                }`}
                placeholder="Enter email"
                onChange={this.handleChange}
                value={formValues.email}
              />
              <div className="invalid-feedback">{formErrors.email}</div>
            </div>
            <div className="form-group">
              <label>Password</label>
              <input
                type="password"
                name="password"
                className={`form-control ${
                  formErrors.password ? "is-invalid" : ""
                }`}
                placeholder="Password"
                onChange={this.handleChange}
                value={formValues.password}
              />
              <div className="invalid-feedback">{formErrors.password}</div>
            </div>
            <button
              type="submit"
              className="btn btn-primary btn-block"
              disabled={isSubmitting}
            >
              {isSubmitting ? "Please wait..." : "Submit"}
            </button>
          </form>
        </div>
      </div>
    </div>
  );
}

Now we must write the handleChange method to update the state with user inputs:

handleChange = ({ target }) => {
  const { formValues } = this.state;
  formValues[target.name] = target.value;
  this.setState({ formValues });
  this.handleValidation(target);
};

Anytime the state values are updated, we’ll run a validation method against user inputs. This is our handleValidation method:

handleValidation = target => {
  const { name, value } = target;
  const fieldValidationErrors = this.state.formErrors;
  const validity = this.state.formValidity;
  const isEmail = name === "email";
  const isPassword = name === "password";
  const emailTest = /^[^\[email protected]][email protected][^\[email protected]]+\.[^\[email protected]]{2,}$/i;
  validity[name] = value.length > 0;
  fieldValidationErrors[name] = validity[name]
    ? ""
    : `${name} is required and cannot be empty`;
  if (validity[name]) {
    if (isEmail) {
      validity[name] = emailTest.test(value);
      fieldValidationErrors[name] = validity[name]
        ? ""
        : `${name} should be a valid email address`;
    }
    if (isPassword) {
      validity[name] = value.length >= 3;
      fieldValidationErrors[name] = validity[name]
        ? ""
        : `${name} should be 3 characters minimum`;
    }
  }
  this.setState({
    formErrors: fieldValidationErrors,
    formValidity: validity
  });
};

The last part of this basic form is a handleSubmit method for the submission process. We need to check on formValidity values, and, if there are any false values, run the validation method again without submitting the form:

handleSubmit = event => {
  event.preventDefault();
  this.setState({ isSubmitting: true });
  const { formValues, formValidity } = this.state;
  if (Object.values(formValidity).every(Boolean)) {
    alert("Form is validated! Submitting the form...");
    this.setState({ isSubmitting: false });
  } else {
    for (let key in formValues) {
      let target = {
        name: key,
        value: formValues[key]
      };
      this.handleValidation(target);
    }
    this.setState({ isSubmitting: false });
  }
};

Now the form is ready for use. React only provides the “view” layer for your application, and that means it provides only the basic necessities in making form components. component, state, and props are like puzzle blocks that you have to piece together to build a working form.

We made a custom demo for .
No really. Click here to check it out.

As you can see, it’s quite a lot of code for a form with only two text boxes. Imagine how many state values you need to keep track of in a form with 10 inputs or more. Yikes!

Yes, creating forms with React is no fun; it’s very verbose and rigid. Building the form and creating validation methods are boring tasks. In each form, you’d need to do the following, at a minimum:

  1. Set up state for form values, form errors, and form validity
  2. Handling user inputs and updating state
  3. Creating validation functions
  4. Handling submission

Building forms the natural React way requires you to write every part of the process from setting up states to form submission. I have done countless React forms and I always find this part of building forms very boring and time-consuming. Fortunately, I’m not the only one feeling that way.

What is Formik?

Enter Formik. Jared Palmer authored the Formik library out of frustration when building React forms. He needed a way to standardize the input components and the flow of form submission. Formik helps you to write the three most annoying parts of building a form:

  1. Getting values in and out of form state
  2. Validation and error messages
  3. Handling form submission

Here is the same form again, but this time using Formik. This new form only uses four extra components from Formik library: <Formik />, <Form />, <Field />, and <ErrorMessage />.

To unlock Formik’s power, you can wrap your form inside the <Formik /> component:

<Formik>
  <Form>
    {/* the rest of the code here */}
  </Form>
</Formik>

Let’s see how Formik makes building forms easier compared to React’s natural way.

Getting values in and out of form state in Formik

Formik will set up state internally for storing user inputs through its initialValues prop, so you don’t need to initialize state from the constructor anymore.

To get values in and out of Formik internal state, you can use the <Field /> component to replace the regular HTML <input /> component. This component will do the magic of keeping Formik state and input value in sync, so you don’t need to pass value and onChange props into the <Field /> component:

<Formik
  initialValues={{ email: "", password: "" }}
  onSubmit={({ setSubmitting }) => {
    alert("Form is validated! Submitting the form...");
    setSubmitting(false);
  }}
>
  {() => (
    <Form>
      <div className="form-group">
        <label htmlFor="email">Email</label>
        <Field
          type="email"
          name="email"
          className="form-control"
        />
      </div>
      <div className="form-group">
        <label htmlFor="password">Password</label>
        <Field
          type="password"
          name="password"
          className="form-control"
        />
      </div>
    </Form>
  )}
</Formik>

Using Formik’s handleChange

With Formik, there’s no need to initialize state in constructor and create your own handleChange method anymore. It’s all taken care of.

Formik has its own handleChange method that you can use to update the state with user inputs, there is no need to implement your own handleChange method.

The handleChange method updates the form values based on the input’s name attribute that was changed.

The handleChange method is used with input elements. Field component internally updates the values without the need of implementing the handleChange method.

Let’s demonstrate with an example:

const SignupForm = () => {
  const formik = useFormik({
    initialValues: {
      email: "",
    },
    onSubmit: (values) => {
      alert(JSON.stringify(values, null, 2));
    },
  });
  return (
    <form onSubmit={formik.handleSubmit}>
      <label htmlFor="email">Email Address</label>
      <input
        id="email"
        name="email"
        type="email"
        onChange={formik.handleChange}
        value={formik.values.email}
      />

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

Here, we have an input with name set to email. See that we set an onChange event handler to it. The onChange event handler calls the handleChange method of the formik object.

This will update the formik object’s values object with the new value so that we can retrieve it from the values object in the onSubmit props.

Validation and error messages in Formik

Validation is very essential when building forms. If forms are not handled properly, it can lead to a lot of errors.

Forms must tell users which fields are required, the type of values allowed in certain fields. It also helps to give users a clear indication of what’s wrong with their input.

Formik provides a way to handle validation and display error messages. In the below sections, we will learn how to show validation messages in Formik and also how to show error messages in Formik.

How do I show validation messages in Formik?

Validation in Formik is executed automatically during specific events. All common events like after user input, on focus change, and on submit are covered, and you don’t need to worry about them. All you need to do is pass a function into Formik’s validate prop.

Compare this code between Formik validation and vanilla React validation:

// Formik validation code. Take values from Formik
validate={values => {
  let errors = {};
  if (values.email === "") {
    errors.email = "Email is required";
  } else if (!emailTest.test(values.email)) {
    errors.email = "Invalid email address format";
  }
  if (values.password === "") {
    errors.password = "Password is required";
  } else if (values.password.length < 3) {
    errors.password = "Password must be 3 characters at minimum";
  }
  return errors;
}}

// Vanilla React validation code. Take values given by handleChange
handleValidation = target => {
  const { name, value } = target;
  const fieldValidationErrors = this.state.formErrors;
  const validity = this.state.formValidity;
  const isEmail = name === "email";
  const isPassword = name === "password";
  const emailTest = /^[^\[email protected]][email protected][^\[email protected]]+\.[^\[email protected]]{2,}$/i;
  validity[name] = value.length > 0;
  fieldValidationErrors[name] = validity[name]
    ? ""
    : `${name} is required and cannot be empty`;
  if (validity[name]) {
    if (isEmail) {
      validity[name] = emailTest.test(value);
      fieldValidationErrors[name] = validity[name]
        ? ""
        : `${name} should be a valid email address`;
    }
    if (isPassword) {
      validity[name] = value.length >= 3;
      fieldValidationErrors[name] = validity[name]
        ? ""
        : `${name} should be 3 characters minimum`;
    }
  }
  this.setState({
    formErrors: fieldValidationErrors,
    formValidity: validity
  });
};

How do I show error messages in Formik?

With validation in place, now you need to output error messages. Formik’s <ErrorMessage /> component will automatically display error messages for the <Field /> component with the given name.

You can adjust what HTML tag will be displayed through the component prop. Since this example form is using Bootstrap’s style, you must add a className prop as well:

// Formik error message output
<Field
  type="email"
  name="email"
  className={`form-control ${
    touched.email && errors.email ? "is-invalid" : ""
  }`}
/>
<ErrorMessage
  component="div"
  name="email"
  className="invalid-feedback"
/>

// Vanilla React error message output
<input
  type="email"
  name="email"
  className={`form-control ${
    formErrors.email ? "is-invalid" : ""
  }`}
  placeholder="Enter email"
  onChange={this.handleChange}
  value={formValues.email}
/>
<div className="invalid-feedback">{formErrors.email}</div>

The code for error message is actually about the same, but there’s a lot less code in Formik’s validation than in vanilla React. Way to go, Formik!

How can I validate using Yup?

Although you can already feel the benefit of using Formik in the validation process, you can make it even easier by using an object schema validator.

An object schema validator is simply a library that allows you to define the blueprint of a JavaScript object and ensure that the object values match that blueprint through the validation process.

This is particularly useful in validating form data since it’s actually an object kept inside Formik’s values prop.

Now one such library is Yup, and Formik’s author loves Yup so much that he included a special prop that connects Yup with Formik called validationSchema.

This prop automatically transforms Yup’s validation errors into a pretty object whose keys match values and touched.

Here is an example of Formik using Yup as its validation schema. Notice how the validate prop is removed from the <Formik /> component.

With Yup’s object schema validator in place, you don’t have to manually write if conditions anymore. You can learn more about Yup and what kind of validation it can do by visiting its GitHub repo.

Form submission process in Formik

Formik’s <Form /> component will automatically run your validation method and cancel the submission process if there are any errors.

While you have to include the onSubmit prop to a regular <form /> element, Formik’s <Form /> wrapper will run the onSubmit prop function you passed into the <Formik /> component:

// Formik's submit code. Won't be executed if there are any errors.
onSubmit={({ setSubmitting }) => {
  alert("Form is validated!");
  setSubmitting(false);
}}

// Vanilla React submit code. Check on validity state then run validation manually.
handleSubmit = event => {
  event.preventDefault();
  this.setState({ isSubmitting: true });
  const { formValues, formValidity } = this.state;
  if (Object.values(formValidity).every(Boolean)) {
    alert("Form is validated!");
    this.setState({ isSubmitting: false });
  } else {
    for (let key in formValues) {
      let target = {
        name: key,
        value: formValues[key]
      };
      this.handleValidation(target);
    }
    this.setState({ isSubmitting: false });
  }
};

Formik requires only four lines of code for submission at a minimum, and you don’t need to keep track of the validity of form inputs. That’s pretty neat!

Using Formik’s onSubmit

Formik’s onSubmit prop is a function that takes in the values prop and returns a promise. You can use this to do something with the values prop before the submission process:

    // Formik's submit code.
    onSubmit={(values, { setSubmitting }) => {
      alert("Form is validated!");
      setSubmitting(false);
    }}

This function prop is quite similar to onsubmit in vanilla JavaScript. The onSubmit function is passed to your form’s values as the first argument and an object, which is an instance of FormikBag as the second argument:

onSubmit: (values: Values, formikBag: FormikBag) => void | Promise<any>

values contains the values of all the inputs in the form. The formikBag contains functions whose names start with set and reset, for example, setSubmitting and resetForm.

The onSubmit prop is useful because we can easily manipulate our form and its value from the arguments it is passed to. So, it gives us huge leverage over using HTML and form JavaScript APIs because it makes form manipulation easy.

Let’s see how we can use the onSubmit function with the Formik component:

// js
<Formik
  initialValues={{ name: "jared" }}
  onSubmit={(values, actions) => {
    setTimeout(() => {
      alert(JSON.stringify(values, null, 2));
      actions.setSubmitting(false);
    }, 1000);
  }}
>
  {(props) => (
    <form onSubmit={props.handleSubmit}>
      <input
        type="text"
        onChange={props.handleChange}
        onBlur={props.handleBlur}
        value={props.values.name}
        name="name"
      />
      {props.errors.name && <div id="feedback">{props.errors.name}</div>}
      <button type="submit">Submit</button>
    </form>
  )}
</Formik>

Here, we set initial values using the initialValues prop. Then, we use the onSubmit function to do something with the values prop before the submission process.

We have a form element that calls the handleSubmit function when submitted. This submission event fires when clicking the button type="submit". Then, the onSubmit function in the Formik component is called.

One thing to note is that the onSubmit function is not called when the form is submitted. This is because Formik’s <Form /> wrapper will automatically run your validation method and cancel the submission process if there are any errors.

Using Formik’s isSubmitting

This isSubmitting is a Boolean property set by Formik when a form goes through a submission process. You can use this to show a loading spinner or disable the submit button:

isSubmitting: boolean;

We can use this property to detect the state or the progress of the submission of the form. Let’s see an example of how to use this isSubmitting property:

const Example = () => (
  <div>
    <h1>Sign Up</h1>
    <Formik
      initialValues={{
        email: "",
      }}
      onSubmit={async (values) => {
        await sleep(500);
      }}
    >
      {({ isSubmitting }) => (
        <Form>
          {isSubmitting && <div>Loading...</div>}

          <label htmlFor="email">Email</label>
          <Field name="email" placeholder="[email protected]" type="email" />

          <button type="submit" disabled={isSubmitting}>
            Submit
          </button>
        </Form>
      )}
    </Formik>
  </div>
);

We see that we destructed the isSubmitting property from the Formik component’s render props. We use the isSubmitting property to disable the submit button and show a Loading… indicator to the user.

Utilizing this isSubmitting property is great because it allows us to develop a nice UI/UX for our users and to tell them when background processes run in the background.

Using Formik’s setSubmitting

The setSubmitting function sets the isSubmitting property of the Formik’s render props:

setSubmitting: (isSubmitting: boolean) =>
  void;

<Formik
  initialValues={{
    email: "",
  }}
  onSubmit={async (values, { setSubmitting }) => {
    // ...
  }}
></Formik>

When this setSubmitting function is called with Boolean true, then isSubmitting is set to true. If the setSubmitting function is called with Boolean false, then isSubmitting is set to false.

So, we can see that the setSubmitting function sets the isSubmitting property of the Formik’s render props.

Let’s see an example:

const Example = () => {
  return (
    <div>
      <h1>Sign Up</h1>
      <Formik
        initialValues={{
          email: "",
        }}
        onSubmit={async (values, { setSubmitting }) => {
          setSubmitting(true);
          await sleep(500);
          setSubmitting(false);
        }}
      >
        {({ isSubmitting }) => (
          <Form>
            {isSubmitting && <div>Loading...</div>}

            <label htmlFor="email">Email</label>
            <Field name="email" placeholder="[email protected]" type="email" />

            <button type="submit" disabled={isSubmitting}>
              Submit
            </button>
          </Form>
        )}
      </Formik>
    </div>
  );
};

Here, we call the setSubmitting function with Boolean true when the form is submitted. This sets the isSubmitting to true and the button will be disabled and we see a Loading... indicator.

Then, the thread sleeps for 5 seconds and the setSubmitting is called with Boolean value false, which sets the isSubmitting to false and the button becomes disabled. The Loading... indicator finally goes away.

The problem with redux-form

Sure, redux-form works great, but then you’d need to use Redux in the first place. What if you’re using MobX? What if a new, better library comes up in the future and you want to replace Redux with that?

On top of all that, does your React form actually affect the flow of your entire application in some way?

Think about it: Does the value of the username text box somehow matter to your application globally? If not, then it’s really not necessary to track its value using Redux. Even the prophet Dan Abramov said the same thing.

Another problem with redux-form is that you store form input values into Redux store. This means your application will call on Redux’s reducer on every keystroke to update the value of just one textbox. Not a good idea.

I love writing forms the Formik way, but if you prefer redux-form, then that’s fine, too.

Conclusion

Building forms is one of those things that React isn’t good at. Luckily, React has a community of developers that help each other and make the process of writing code easier.

Formik is definitely one of those open source libraries that’s a must-have if you are writing many forms in your React application.

It really speeds up your development process and reduces boilerplate code by abstracting away parts of your form through components like <Field /> and <Form />.

While a vanilla React form requires you to specify your own state values and methods, you can simply pass props to the <Formik /> component to do the same things: handle user inputs, validate inputs, and form submission.

If you’d like to learn more about Formik, head over to the documentation or watch the presentation below by its creator. Thanks for reading!

Full visibility into production React apps

Debugging React applications can be difficult, especially when users experience issues that are hard to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

LogRocket is like a DVR for web and mobile apps, recording literally everything that happens on your React app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your React apps — .

Nathan Sebhastian A senior software developer with experience in building fullstack JavaScript app with React and Express. You can find me online at sebhastian.com.

2 Replies to “Building forms with Formik in React”

  1. Hello, I think I found a mistake in your codesandbox:

    This line:
    onSubmit={({ setSubmitting }) => {
    Must be written like this:
    onSubmit={(values, { setSubmitting }) => {

    right?

  2. Hello, i think in the following paragraph
    Then, the thread sleeps for 5 seconds and the setSubmitting is called with Boolean value false, which sets the isSubmitting to false and the button becomes disabled.

    The last phrase should be “which sets the isSubmitting to false and the button becomes enabled”

Leave a Reply