Tuesday, 29 October, 2019 UTC


Summary

Writing a React form without any additional dependencies isn’t much of a challenge itself. The same goes for applying client-side data validation. What might be troublesome is keeping all your forms consistent across a bigger project. If every developer in your team has a slightly different approach, it may turn out to be messy. With Formik, we can keep all of our forms in check and apply a foreseeable approach to data validation. In this article, we aim to learn its basics and advantages. We also get to know the Yup library and how we can use it to write a validation schema.
The advantages of using Formik
The thing that we want to avoid is letting our library do too much. So much even, that we don’t get what happens underneath. Thankfully, with Formik, everything is straightforward. It helps us get values in and out of a form state, validate it, and handle the form submission. The above things are not difficult to do without the help of Formik, but it helps us organize things better. It makes testing and refactoring way easier.
You might think of using Redux for that, but keeping every piece of the state of your form in it is not necessarily a good idea. Let’s start by creating a form without Formik:
import React, { Component } from 'react';
import isFormValid from './isFormValid';
import register from './register';
import styles from './styles.module.scss';

class RegistrationForm extends Component {
  defaultFormState = {
    email: '',
    password: '',
    confirmPassword: ''
  }
  state = {
    form: {
      ...this.defaultFormState
    }
  }
  handleSubmit = (event) => {
    event.preventDefault();
    const form = this.state.form;
    if(isFormValid(form)) {
      register(form);
      this.setState({
        form: {
          ...this.defaultFormState
        },
        areErrorsFound: false
      })
    } else {
      this.setState({
        areErrorsFound: true
      })
    }
  }
  handleChange = (event) => {
    const name = event.target.name;
    const value = event.target.value;
    this.setState({
      form: {
        ...this.state.form,
        [name]: value
      }
    })
  }
  render() {
    const {
      areErrorsFound,
      form
    } = this.state;
    return (
      <form
        onSubmit={this.handleSubmit}
        className={areErrorsFound ? styles.invalidForm : ''}
      >
        <input
          value={form.email}
          onChange={this.handleChange}
          name="email"
          type="email"
          placeholder="Email"
        />
        <input
          value={form.password}
          onChange={this.handleChange}
          name="password"
          type="password"
          placeholder="Password"
        />
        <input
          value={form.confirmPassword}
          onChange={this.handleChange}
          name="confirmPassword"
          type="password"
          placeholder="Confirm password"
        />
        <button>
          Submit
        </button>
      </form>
    )
  }
}

export default RegistrationForm;
function isFormValid(form) {
  return form.password &&
    form.confirmPassword &&
    form.email &&
    form.password === form.confirmPassword &&
    /^.+@.+\..+$/.test(form.email);
}
.invalidForm {
  input {
    color: red;
  }
}
The regular expression that we use for an email is quite simple. While we might want to implement a more sophisticated way to do that, we shouldn’t overdo it. Even though popular email providers can have quite strict rules when it comes to email format, the RFC specification makes them quite flexible.
If you want to know more about regex in general, check out the regex course.
There are a few main things to notice above: I’ve written the 
isFormValid
 function to return a simple boolean. You might want to approach it differently and return a list of errors. Since it is a subjective thing, other developers might want to write it differently.
Another thing is the fact that the above code is a very simple example, yet it needed quite a lot of code. Handling different approaches, like validating on every change instead of a submit, would require us to write even more. Having a lot of code means that you need to test it thoroughly, and nobody is going to test it for you. The fact that every form in your – possibly big – application might differ does not help.

How Formik helps

Let’s rewrite the above form to use Formik.
import React, { Component } from 'react';
import { Formik } from 'formik';
import register from './register';
import validateForm from './validateForm';

class RegistrationForm extends Component {
  defaultFormState = {
    email: '',
    password: '',
    confirmPassword: ''
  }
  handleSubmit = (form) => {
    register(form);
  }
  render() {
    return (
      <Formik
        onSubmit={this.handleSubmit}
        initialValues={this.defaultFormState}
        validate={validateForm}
      >
        {({
          handleSubmit,
          values,
          handleChange,
          handleBlur
        }) => (
          <form
            onSubmit={handleSubmit}
          >
            <input
              value={values.email}
              onChange={handleChange}
              onBlur={handleBlur}
              name="email"
              type="email"
              placeholder="Email"
            />
            <input
              value={values.password}
              onChange={handleChange}
              onBlur={handleBlur}
              name="password"
              type="password"
              placeholder="Password"
            />
            <input
              value={values.confirmPassword}
              onChange={handleChange}
              onBlur={handleBlur}
              name="confirmPassword"
              type="password"
              placeholder="Confirm password"
            />
            <button>
              Submit
            </button>
          </form>
        )}
      </Formik>
    )
  }
}

export default RegistrationForm;
function validateForm (form) {
  const errors = {};
  if (!form.email) {
    errors.email = 'Required';
  }
  if (!form.password) {
    errors.password = 'Required';
  }
  if (!form.confirmPassword) {
    errors.confirmPassword = 'Required';
  }
  if (form.password !== form.confirmPassword) {
    errors.confirmPassword = 'Password does not match';
  }
  if (!/^.+@.+\..+$/.test(form.email)) {
    errors.confirmPassword = 'Invalid email';
  }
  return errors;
}
In the code above, Formik did quite a bit work for us by handling the state of the form. It also standardized the way we validate data: now, our validation function returns an object. It is structured in a way that lets us know which input is an issue when validating. The form does not submit if there are any errors.
Even though Formik simplifies things a bit in the example above, there is still room for improvement. We have access to a set of components that can help us reduce the boilerplate and delegate even more of the work.
import React, { Component } from 'react';
import { Formik, Form, Field } from 'formik';
import register from './register';
import validateForm from "./validateForm";

class RegistrationForm extends Component {
  defaultFormState = {
    email: '',
    password: '',
    confirmPassword: ''
  }
  handleSubmit = (form) => {
    register(form);
  }
  render() {
    return (
      <Formik
        onSubmit={this.handleSubmit}
        initialValues={this.defaultFormState}
        validate={validateForm}
      >
        {() => (
          <Form>
            <Field
              name="email"
              type="email"
              placeholder="Email"
            />
            <Field
              name="password"
              type="password"
              placeholder="Password"
            />
            <Field
              name="confirmPassword"
              type="password"
              placeholder="Confirm password"
            />
            <button>
              Submit
            </button>
          </Form>
        )}
      </Formik>
    )
  }
}

export default RegistrationForm;
Diving deeper into form validation
Simply preventing submission of a form is not the only thing that Formik helps with in terms of validation. An important part of it is displaying the errors that occur.
{({
  errors
}) => (
  <Form>
    <Field
      name="email"
      type="email"
      placeholder="Email"
    />
    <div>{errors.email}</div>
    <Field
      name="password"
      type="password"
      placeholder="Password"
    />
    <div>{errors.password}</div>
    <Field
      name="confirmPassword"
      type="password"
      placeholder="Confirm password"
    />
    <div>{errors.confirmPassword}</div>
    <button>
      Submit
    </button>
  </Form>
)}
The approach above is rather conventional. Therefore, Formik has a helper component for that also called ErrorMessage.
{() => (
  <Form>
    <Field
      name="email"
      type="email"
      placeholder="Email"
    />
    <ErrorMessage
      name="email"
      component="div"
    />
    <Field
      name="password"
      type="password"
      placeholder="Password"
    />
    <ErrorMessage
      name="password"
      component="div"
    />
    <Field
      name="confirmPassword"
      type="password"
      placeholder="Confirm password"
    />
    <ErrorMessage
      name="confirmPassword"
      component="div"
    />
    <button>
      Submit
    </button>
  </Form>
)}
All the form validation that we use above is straightforward, yet we need to write quite a bit of code for it to work. Another neat approach that we can implement is a validation schema. Instead of writing a function that validates the data, we can define an object that contains all the information needed for validation.
To create a validation schema, we need Yup. It is quite rich with different functions that aim to help you create a schema that you need. With it, we can describe our form in a way that Formik understands.
import * as Yup from 'yup';

const validationSchema = Yup.object().shape({
  email: Yup.string()
    .email('Invalid email')
    .required('Required'),
  password: Yup.string()
    .required('Required'),
  confirmPassword: Yup.string()
    .required('Required')
    .oneOf([Yup.ref('password')], 'Password does not match')
});
<Formik
  onSubmit={this.handleSubmit}
  initialValues={this.defaultFormState}
  validationSchema={validationSchema}
>
  {() => (
    <Form>
      <Field
        name="email"
        type="email"
        placeholder="Email"
      />
      <ErrorMessage
        name="email"
        component="div"
      />
      <Field
        name="password"
        type="password"
        placeholder="Password"
      />
      <ErrorMessage
        name="password"
        component="div"
      />
      <Field
        name="confirmPassword"
        type="password"
        placeholder="Confirm password"
      />
      <ErrorMessage
        name="confirmPassword"
        component="div"
      />
      <button>
        Submit
      </button>
    </Form>
  )}
</Formik>
Our validation schema is pretty straightforward. We define all fields as required and use a predefined regular expression for the email. The more exciting part is the 
confirmPassword
 field. To force it to be identical to the 
password
 field, we use the 
oneOf
 function to define a set of possible values. To get the current password, we use the 
ref
 function.
The Yup library contains many different functions that you can use. For a full list, check out the API documentation.
Summary
Here, we described how can we validate data on the front end, which is just a beginning. It is crucial to validate all the data on the backend also, even more strictly. You can never trust the data coming from your users because nothing can stop them from making a request to your API bypassing your interface. For example, they can do it with tools like Postman.
We’ve come quite a long way to refactor our React form with Formik. The most important thing that we’ve acquired is consistency. Thanks to that, every form that we do in our application works on the same principles, which make it easier to refactor, debug, and test. Also, there are quite a few tests written both for the Formik and Yup libraries unburdening you from that responsibility.
The post Writing React forms with Formik. Validating data using Yup appeared first on Marcin Wanago Blog - JavaScript, both frontend and backend.