Formik - Building React Forms easier
Formik - Building React Forms easier

In this post you will learn what is Formik and why it is the best possible variant to create forms inside React.

Do we have a problem?

What problem do we have? Typically we have a form inside React application like this.

Basic form

It's a registration form with 3 fields. Then we create markup for our form and add useState for every property to store all fields inside component state.

<div>
  <div className="field">
    <input name="email" placeholder="Email" />
    <div className="error"></div>
  </div>
  <div className="field">
    <input name="username" placeholder="Username" />
    <div className="error"></div>
  </div>
  <div className="field">
    <input name="password" placeholder="Password" />
    <div className="error"></div>
  </div>
  <button type="submit">Submit</button>
</div>

If you additionally want to client validation it is quite a lot of logic to write on your own and it doesn't make a lot of sense. Essentially inside forms it is always the same. We want a change event, a blur event, some errors and state of our form. It all can be done by the library.

It makes sense to write it on your own only if you have a super simple form that you don't need to reuse.

Which brings us to formik.

Formik website

Inside React world we had one super popular solution previously which was redux-form. The main idea was that you have all your forms inside Redux and everything was reusable. But it is really slow to use Redux for forms if you have a lot of them because it is slower to update Redux state than just local state. This is why we have an alternative which is called Formik and this is just a local state and sugar to create local forms inside your component.

Basic Formik example

Let's implement Formik to our form now. Our first step will be to install a library.

npm install formik

As you saw above I have a basic React form which is pure HTML. We can use Formik in 2 different ways. First of all I want to show you how to use it with React Hooks.

import {useFormik} from 'formik'

const App = () => {
  const formik = useFormik({
    initialValues: {
      email: "",
      username: "",
      password: "",
    }
  })

  ...
}

So here we defined a Formik with all default values of our form.

import {useFormik} from 'formik'

const App = () => {
  const formik = useFormik({
    initialValues: {
      email: "",
      username: "",
      password: "",
    },
    onSubmit: (values) => {
      console.log("onSubmit", values);
    }
  })
  ...
}

Now we added onSubmit callback which will be called when our form is submitted. We also need to bind our inputs and form to formik.

<form onSubmit={formik.handleSubmit}>
  <div className="field">
    <input
      name="email"
      placeholder="Email"
      value={formik.values.email}
      onChange={formik.handleChange}
    />
    <div className="error"></div>
  </div>
  <div className="field">
    <input
      name="username"
      placeholder="Username"
      value={formik.values.username}
      onChange={formik.handleChange}
    />
    <div className="error"></div>
  </div>
  <div className="field">
    <input
      name="password"
      type="password"
      placeholder="Password"
      value={formik.values.password}
      onChange={formik.handleChange}
    />
    <div className="error"></div>
  </div>
  <button type="submit">Submit</button>
</div>

Here we provided formik.handleSubmit to our form. We also set value from formik in each input as well as onChange event.

As you can see in looks exactly like typical form with React but instead of creating our own state we use formik.

Now in browser when we change our inputs and submit a form we see our console.log with correct data of the form.

Adding validation

Now let's add some sugar to our formik. We want to show errors on blur so we need onBlur event and render errors of each field.

<form onSubmit={formik.handleSubmit}>
  <div className="field">
    <input
      name="email"
      placeholder="Email"
      value={formik.values.email}
      onChange={formik.handleChange}
      onBlur={formik.handleBlur}
    />
    <div className="error">
      {formik.errors.email && formik.touched.email && formik.errors.emai}
    </div>
  </div>
  <div className="field">
    <input
      name="username"
      placeholder="Username"
      value={formik.values.username}
      onChange={formik.handleChange}
      onBlur={formik.handleBlur}
    />
    <div className="error">
      {formik.errors.username && formik.touched.username && formik.errors.username}
    </div>
  </div>
  <div className="field">
    <input
      name="password"
      type="password"
      placeholder="Password"
      value={formik.values.password}
      onChange={formik.handleChange}
      onBlur={formik.handleBlur}
    />
    <div className="error">
      {formik.errors.password && formik.touched.password && formik.errors.password}
    </div>
  </div>
  <button type="submit">Submit</button>
</div>

We added onBlur event to each input from formik and rendered a specific error for each field if we changed the value of the field.

But we still didn't define any validation. The idea is that we can define a validate function inside formik which will be called every time when we change our form state.

  const formik = useFormik({
    ...
    validate: (values) => {
      const errors = {};
      if (!values.email) {
        errors.email = "Email is required";
      }
      if (!values.username) {
        errors.username = "Username is required";
      }
      if (!values.password) {
        errors.password = "Password is required";
      }
      return errors;
    },
  })

The idea of validate function is to return an object with errors which looks the same like the object with all fields. Here we just check each field for emptiness.

Validation errors

As you can see now we get nice errors out of the box but only when our fields are touched.

So formik generates errors in formik.errors and we render them in specific containers.

In formik we don't write validation logic in submit. We simply define our validation rules.

Validation schema

But actually we can do it even better by using additional package. We can install a package which is called Yup and it works nice with Formik.

npm install yup
import * as Yup from 'yup'

const formik = useFormik({
  ...
  validationSchema: Yup.object({
    email: Yup.string()
      .required("Email is required")
      .email("Invalid email adress"),
    password: Yup.string().required("Password is required"),
    username: Yup.string().required("Username is required"),
  }),
})

Here we describe our validation by using specific rules. We don't have any logic but only declarative rules. As you can see in browser it works exactly like validate function.

Adding formik magic

As you can see formik is really amazing and this is how I recommend you to use it. But if you want to use more sugar you need to use Formik in another way.

Our first step will be to move all properties from formik object to just separate constants.

const App = () => {
  const initialValues = {
    email: "",
    username: "",
    password: "",
  };
  const onSubmit = (values) => {
    console.log("onSubmit", values);
  };

  const validationSchema = Yup.object({
    email: Yup.string()
      .required("Email is required")
      .email("Invalid email adress"),
    password: Yup.string().required("Password is required"),
    username: Yup.string().required("Username is required"),
  });
  ...
}

Now we can use components from Formik to write even less code.

<Formik
  initialValues={initialValues}
  onSubmit={onSubmit}
  validationSchema={validationSchema}
>
  {() => (
    <Form>
      <div className="field">
        <Field name="email" placeholder="Email" />

        <div className="error">
          <ErrorMessage name="email" component="span" />
        </div>
      </div>
      <div className="field">
        <Field name="username" placeholder="Username" />
        <div className="error">
          <ErrorMessage name="username" component="span" />
        </div>
      </div>
      <div className="field">
        <Field name="password" placeholder="Password" type="password" />
        <div className="error">
          <ErrorMessage name="password" component="span" />
        </div>
      </div>
      <button type="submit">Submit</button>
    </Form>
  )}
</Formik>

Here we used Formik component as render props. We provided in Formik initialValues, onSubmit and validationSchema just like we did previously with hooks.

Also instead of our own inputs and error messages we used components from Formik. As you can see we don't need to provide value, onChange or anything else. We just give a name and Formik does the reset.

As you can see this approach is much cleaner to write code but we have less control to change things this is why I typically use hook approach.

And actually if you are interested to learn how to build real React project from start to the end make sure to check my React hooks course.

📚 Source code of what we've done