Tear-Free Forms with React and Formik

joshtronic

It’s not often that you write a web app that doesn’t contain at least one form. More often than naught, your entire app is just a series of forms. Each of these forms requires state management, event handlers, and, oftentimes, some sort of client-side data validation. Formik aims to do all of that in a small package and, as they claim, “without the tears”.

Getting Started

To get started with formik we need to add it to our project via npm or yarn:

$ npm install --save formik
# or
$ yarn add formik

After the dependency has been added, be sure to import it into your project where you’re going to use it. For this article we’re going to use both Formik and the Form helper component:

import { Formik, Form } from "formik";

The Form component is a wrapper for the standard form element that automatically wires up the onSubmit handler attached to the Formik component, saving us even more time.

The Formik Component

The real magic of Formik happens in the Formik component. This component will be used to wrap your form up and exposes state values and handlers via the render props.

Said component can take properties to set up our default values, validate the submitted values and handle our submission.

Here’s how a Formik component will look for a basic login form that we’ll be creating later on:

<Formik
  // Sets up our default values
  initialValues={{ email: "", password: "" }}

  // Validates our data
  validate={values => {
    const errors = {};

    if (!values.email) errors.email = "Required";

    if (
      !/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(values.email)
    ) {
      errors.email = "You must supply a valid email address";
    }

    if (values.password.length < 8) {
      errors.password = "Passwords must be at least 8 characters";
    }

    if (values.email === values.password) {
      errors.password =
        "Your password shouldn't be the same as your email";
    }

    return errors;
  }}

  // Handles our submission
  onSubmit={(values, { setSubmitting }) => {
    // This is where you could wire up axios or superagent
    console.log("Submitted Values:", values);
    // Simulates the delay of a real request
    setTimeout(() => setSubmitting(false), 3 * 1000);
  }}
>
  {props => (
    <div>This is where your Form and form elements will go!</div>
  )}
</Formik>

It’s worth nothing, if you’re familiar with object schema validators like yup or joi you can skip the validate property and pass in a validationSchema instead.

The Form Component

As mentioned, the Form component is a drop-in replacement for a form element that automagically wires up things like the onSubmit handler.

Use it to wrap your form’s inputs like you normally would:

<Formik>
  {props => (
    <Form>
      <label>My Input</label>
      <input type="text" />
    </Form>
  )}
</Formik>

The State of the Inputs

Out of the box, Formik’s render property exposes event handlers to manage changes to your form inputs, whether or not they have been “touched”, their values and any errors.

For the form as a whole, you will be able to tell if the form is in the process of being validated or submitted and an event handler that lets you easily reset the form.

props.values and props.errors are objects that have properties that correspond with your form fields.

props.handleChange and props.handleBlur can be passed to onChange and onBlur properties to track changes and whether or not an input has been “touched”.

This “touched” value comes in handy when you only want to show errors after a user has interacted with an element, instead of at the time of page load (which is preferred IMHO).

props.dirty gets set to true when the form has been modified by the user.

State values props.isValidating and props.isSubmitting let you know what stage of the process the user is in. Perfect for displaying a loader or disabling the form or individual buttons.

Bringing It All Together

Here’s what a simple login form with email and password looks like once it’s been formally Formiked:

<Formik
  // Sets up our default values
  initialValues={{ email: "", password: "" }}

  // Validates our data
  validate={values => {
    const errors = {};

    if (!values.email) errors.email = "Required";

    if (
      !/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(values.email)
    ) {
      errors.email = "You must supply a valid email address";
    }

    if (values.password.length < 8) {
      errors.password = "Passwords must be at least 8 characters";
    }

    if (values.email === values.password) {
      errors.password =
        "Your password shouldn't be the same as your email";
    }

    return errors;
  }}

  // Handles our submission
  onSubmit={(values, { setSubmitting }) => {
    // This is where you could wire up axios or superagent
    console.log("Submitted Values:", values);
    // Simulates the delay of a real request
    setTimeout(() => setSubmitting(false), 3 * 1000);
  }}
>
  {props => (
    <Form>
      <label htmlFor="email">Email</label>
      <div>
        <input
          name="email"
          type="email"
          placeholder="Enter your account email"
          value={props.values.email}
          onChange={props.handleChange}
          onBlur={props.handleBlur}
          style={{
            borderColor:
              props.errors.email && props.touched.email && "red"
          }}
        />
        {props.errors.email && props.touched.email && (
          <div style={{ color: "red" }}>{props.errors.email}</div>
        )}
      </div>
      <label htmlFor="password">Password</label>
      <div>
        <input
          name="password"
          type="password"
          placeholder="Enter your account password"
          value={props.values.password}
          onChange={props.handleChange}
          onBlur={props.handleBlur}
          style={{
            borderColor:
              props.errors.password && props.touched.password && "red"
          }}
        />
        {props.errors.password && props.touched.password && (
          <div style={{ color: "red" }}>{props.errors.password}</div>
        )}
      </div>
      <input
        type="submit"
        value="Submit"
        disabled={props.isSubmitting}
      />
      &nbsp;
      <input
        type="reset"
        value="Reset"
        onClick={props.handleReset}
        disabled={!props.dirty || props.isSubmitting}
      />
    </Form>
  )}
</Formik>

Conclusion

Formik provides a familiar, render prop-based approach to building forms.

Most of the redundant boilerplate and statement management logic you usually end up writing is taken care of nicely while still providing enough power under the hood to do some pretty complex state management on your forms.

If you’d like to give the code from this article a test spin, you can check it out over on CodeSandbox.

Cheers!

  Tweet It

🕵 Search Results

🔎 Searching...

Sponsored by #native_company# — Learn More
#native_title# #native_desc#
#native_cta#