Nathan Sebhastian A senior software developer with experience in building full stack JavaScript app with React and Express https://sebhastian.com

Building better React forms with Formik

8 min read 2249

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.

Here is an example of a basic React form written without libraries and with minimal Bootstrap styling:

In the example below, we first initialize required state values in the constructor method. Since 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 method of the form 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 need to 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 = /^[^\s@]+@[^\s@]+\.[^\s@]{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.

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, making forms with React is no fun; it’s very verbose and rigid. Building the form and creating validation method 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.

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 />. In order 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

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

In order 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 have 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>

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

Validation and error messages

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 have 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 = /^[^\s@]+@[^\s@]+\.[^\s@]{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
  });
};

With validation in place, now you need to output error messages. Formik’s <ErrorMessage /> component will automatically display error message 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 will have to 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!

Even easier validation with Yup

Although you can already feel the benefit of using Formik in 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 will automatically transform 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 on 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

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 minimum, and you don’t need to keep track of the validity of form inputs. That’s pretty neat!

But what about 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 affects the flow of your entire application in some way?

Think about it: Does the value of the username textbox 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 are storing 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!


Plug: , a DVR for web apps

LogRocket Dashboard Free Trial Banner

 is a frontend logging tool that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.

.

Nathan Sebhastian A senior software developer with experience in building full stack JavaScript app with React and Express https://sebhastian.com

Leave a Reply