DEV Community

Cover image for 3 Ways to Build React Forms with Formik Pt.1
Alex Devero
Alex Devero

Posted on • Originally published at blog.alexdevero.com

3 Ways to Build React Forms with Formik Pt.1

Formik is one of the most popular libraries for building forms. It helps developers do a lot of things with much few lines of code. Some of these things are form state management, validation and error handling. This tutorial will show you three ways in which you can use formik to build React forms.

The pain of building forms

Building React forms may sound easy, but it is not. There are a lot of things involved. Usually the easiest part is to put together the form. Then come the hard parts. There is a form state management and handling values filled in the form. This also includes preparing the initial state of the form.

When you have this, you need functions to handle those fields to keep the form state up-to-date. Next comes validation. You have to ensure all those values are really legit, or valid. This may require some validation logic including regex. As a part of the validation, you also have to ensure you have valid values for all required fields.

What if some value is invalid, or required and missing? When this happens, you have to find out what value is invalid and display correct error message for that field. That's not the end. You also have to ensure that when the value is valid the error message disappears. Only when you have all this covered you can proceed to submitting the form.

Building React forms with Formik

If this all sounds like a pain to you, you are not alone. For one of these React developer this pain was a motivation to come up with a solution. This solution he came up with was Formik. The idea is to make building React forms easier for developers by doing most of the heavy lifting for them.

This includes the usual things involved in building forms, the form state management, input validation, showing error messages when necessary and also handling form submission. At this moment, there are at least three ways to use Formik to build forms. Below, we will take a look at each of these ways.

Creating simple form validation schema

Formik supports multiple ways to validate forms. One way is to write the validation logic by yourself. Formik will then use that logic and handle error messages. Another option is to use some validation library. In this tutorial, we will choose the second option and use validation library called Yup.

What this library does is it helps you create validation schema for your form. This schema is basically an object that contains validation rules for individual fields in your form. Formik as able to use this schema, built with Yup, to validate all fields defined in the schema against their specific rules.

Another nice feature of Yup schema is the option to specify error message for each rule. You do this by passing some text as an argument to rules functions. For example, when field is required, you use required() function. To specify the error message for this rule you pass the message as an argument: required('This field is required.').

All forms we will use in this tutorial will have three fields: name, email and password. All these fields will be required. We will specify all this in the form schema created with Yup. For the email field, we will also specify that it has to match email format.

import * as Yup from 'yup'

const formSchema = Yup.object().shape({
  name: Yup.string().required('First name is required'),
  email: Yup.string().email('Invalid email').required('Email is required'),
  password: Yup.string().required('Password is required'),
})
Enter fullscreen mode Exit fullscreen mode

Custom forms with Formik

The first option is to use Formik as a wrapper for your React forms. You will create the form and components for all fields you need. This also includes error messages for those fields. Formik will take care of form state, validation and error handling. This option will require only one component provided by Formik called Formik.

The Formik component

This Formik component will serve as a wrapper for the whole form. This doesn't mean it will replace the form element wrapping your form. If you are using the form element, it will stay. The Formik component will wrap this element as well. The Formik component has a couple of attributes that will be handy.

These attributes are initialValues, onSubmit and validationSchema. The initialValues attribute specifies object to define initial values of all fields and creates form state. The onSubmit attribute allows you to specify handler function for onSubmit event. The validationSchema attribute allows to specify validation schema to use.

Initial values for all fields, name, email and password, will be empty strings. For now, to handle onSubmit event, we will now use arrow function and simple console.log() to log submitted values. Formik component uses render-prop pattern that helps share code between React component. Don't worry you don't need to know how this works.

All you need to know is that Formik expects its direct children to be a function that returns some React component. In this case, that returned component will be the form element and its content. Because we will work with custom form elements, we will need to expose some data from Formik component so we can work with them.

We can get this data by using object destructuring in the function that returns the form element. The data we will need are values, errors, touched, handleBlur, handleChange and handleSubmit. The values is an object that contains current values for each form field. We will use this to specify values for input value attribute.

The errors is also an object. If there are any errors in the form, invalid or missing fields, you will find it inside this object. The touched is an object that tells which form field has been touched and which not. Touched field means that someone interacted with that field, it was focused.

The handleBlur and handleChange are handlers for inputs onBlur and onChange events. These two events will allow Formik track changes in values, update form state, update "touched" status and also run validations when fields lose focus. The handleSubmit is a handler for form onSubmit event.

We will use this handler for onSubmit attribute of form element to trigger Formik's handleSubmit function when the form is submitted.

// Import dependencies:
import { memo } from 'react'
import { Formik } from 'formik'
import * as Yup from 'yup'

// Create form validation schema:
const formSchema = Yup.object().shape({
  name: Yup.string().required('First name is required'),
  email: Yup.string().email('Invalid email').required('Email is required'),
  password: Yup.string().required('Password is required'),
})

// Create the form component:
export const FormCustom = memo(() => {
  return (
    <Formik
      initialValues={{ name: '', email: '', password: '' }}
      onSubmit={(values) => {
        console.log(values)
      }}
      validationSchema={formSchema}
    >
      {({
        values,
        errors,
        touched,
        handleBlur,
        handleChange,
        handleSubmit,
      }) => <form></form>}
    </Formik>
  )
})

FormCustom.displayName = 'FormCustom'
Enter fullscreen mode Exit fullscreen mode

The form content

The next step is putting together the content of the form, individual fields. This will be quick and easy. Each field will be composed of div element used as a wrapper. Inside this wrapper will be label and input elements. There will be also p element with an error message. To make sure everything works we will need two things.

First, we will need to use correct value for input name attributes. Formik uses this attribute, or id, to connect each field with correct property in the form state. We set the initial values object to have properties name, email and password. This means that we will have to use the same values for each name attribute, or id, or both.

The second thing are the onChange and onBlur input events handler functions. We need to connect Formik state with each input. This will allow Formik to track changes of values and blur events and update values, touched and errors accordingly. Last thing are the value input attributes for each input field.

Those inputs should be controlled by Formik state. This will allow to display current values in Formik state as values of corresponding input fields. To do this, we will use values object and its specific property to get latest correct value for each input field.

// ... Previous code
export const FormCustom = memo(() => {
  return (
    <Formik
      initialValues={{ name: '', email: '', password: '' }}
      onSubmit={(values) => {
        console.log(values)
      }}
      validationSchema={formSchema}
    >
      {({
        values,
        errors,
        touched,
        handleBlur,
        handleChange,
        handleSubmit,
      }) => (
        <form onSubmit={handleSubmit} noValidate>
          <div>
            <label htmlFor="name">Name</label>
            <input
              type="text"
              name="name"
              value={values.name}
              onChange={handleChange}
              onBlur={handleBlur}
            />
          </div>

          <div>
            <label htmlFor="email">Email</label>
            <input
              type="email"
              name="email"
              value={values.email}
              onChange={handleChange}
              onBlur={handleBlur}
            />
          </div>

          <div>
            <label htmlFor="password">Password</label>
            <input
              type="password"
              name="password"
              value={values.password}
              onChange={handleChange}
              onBlur={handleBlur}
            />
          </div>

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

FormCustom.displayName = 'FormCustom'
Enter fullscreen mode Exit fullscreen mode

Simple error messages

We have Formik and form state. We also have form content. The last thing that remains are error messages. One part of this is already covered by validation schema we created with Yup and Formik. The second part are error messages. We have to tell Formik where we want to show them and under what condition. Both these things will be easy.

To address the first, we will put each error message under corresponding field. We will use p elements to show the text we defined for each rule in our Yup validation schema. The condition for when to display each message will following: We want to show the error when field is empty or invalid, but only after it has been touched.

This will help prevent the errors popping up when someone just opens the form. And, don't worry. Formik automatically sets all fields to touched when the form is submitted. So, if someone tries to submit empty form, error messages for invalid fields will correctly pop up because Formik will set all fields as touched and there are some errors.

Since we specified error messages in validation schema we only have to ensure that Formik displays correct error message for each field. We will do this by using the errors object and correct property (field name). We will use the same property with touched object to check if specific field has been touched.

// ... Previous code
export const FormCustom = memo(() => {
  return (
    <Formik
      initialValues={{ name: '', email: '', password: '' }}
      onSubmit={(values) => {
        console.log(values)
      }}
      validationSchema={formSchema}
    >
      {({
        values,
        errors,
        touched,
        handleBlur,
        handleChange,
        handleSubmit,
      }) => (
        <form onSubmit={handleSubmit} noValidate>
          <div>
            <label htmlFor="name">Name</label>
            <input
              type="text"
              name="name"
              value={values.name}
              onChange={handleChange}
              onBlur={handleBlur}
            />
            {/* Add error message for "Name" field */}
            {errors.name && touched.name && <p>{errors.name}</p>}
          </div>

          <div>
            <label htmlFor="email">Email</label>
            <input
              type="email"
              name="email"
              value={values.email}
              onChange={handleChange}
              onBlur={handleBlur}
            />

            {/* Add error message for "Email" field */}
            {errors.email && touched.email && <p>{errors.email}</p>}
          </div>

          <div>
            <label htmlFor="password">Password</label>
            <input
              type="password"
              name="password"
              value={values.password}
              onChange={handleChange}
              onBlur={handleBlur}
            />

            {/* Add error message for "Password" field */}
            {errors.password && touched.password && <p>{errors.password}</p>}
          </div>

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

FormCustom.displayName = 'FormCustom'
Enter fullscreen mode Exit fullscreen mode

Putting it together

Since Formik, form content and error messages are done your custom React form is complete as well. Below is the complete code for the whole form. The last thing that needs some work is what to do when the form is submitted. You handle this in Formik's onSubmit attribute and its handler function. Replace that console.log() with anything you need.

// Import dependencies:
import { memo } from 'react'
import { Formik } from 'formik'
import * as Yup from 'yup'

// Create form validation schema:
const formSchema = Yup.object().shape({
  name: Yup.string().required('First name is required'),
  email: Yup.string().email('Invalid email').required('Email is required'),
  password: Yup.string().required('Password is required'),
})

// Create the form component:
export const FormCustom = memo(() => {
  return (
    <Formik
      initialValues={{ name: '', email: '', password: '' }}
      onSubmit={(values) => {
        console.log(values)
      }}
      validationSchema={formSchema}
    >
      {({
        values,
        errors,
        touched,
        handleBlur,
        handleChange,
        handleSubmit,
      }) => (
        <form onSubmit={handleSubmit} noValidate>
          <div>
            <label htmlFor="name">Name</label>
            <input
              type="text"
              name="name"
              value={values.name}
              onChange={handleChange}
              onBlur={handleBlur}
            />
            {errors.name && touched.name && <p>{errors.name}</p>}
          </div>

          <div>
            <label htmlFor="email">Email</label>
            <input
              type="email"
              name="email"
              value={values.email}
              onChange={handleChange}
              onBlur={handleBlur}
            />
            {errors.email && touched.email && <p>{errors.email}</p>}
          </div>

          <div>
            <label htmlFor="password">Password</label>
            <input
              type="password"
              name="password"
              value={values.password}
              onChange={handleChange}
              onBlur={handleBlur}
            />
            {errors.password && touched.password && <p>{errors.password}</p>}
          </div>

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

FormCustom.displayName = 'FormCustom'
Enter fullscreen mode Exit fullscreen mode

Conclusion: 3 Ways to build React forms with Formik pt.1

Formik library makes it much easier to build and work with React forms. This tutorial showed you how to make the Formik component to work with custom HTML elements. This can help you use Formik to handle only things such as state management and validation while letting you do the rest as you want.

Discussion (0)