DEV Community

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

Posted on • Originally published at blog.alexdevero.com

3 Ways to Build React Forms with Formik Pt.2

The Formik library helps to build React forms faster with its state management and component. This tutorial will help you learn how to build a React form using components provided by Formik library. You will also learn how to create a simple validation schema for forms with Yup library.

A quick intro

This short series is about three ways to build React forms with Formik. In the first part, we've taken a look at the first way. We focused on build React forms with Formik using only the bare essentials Formik provides. In this part, we will lean much more towards Formik and its components.

Generally speaking, we can use Formik to build React forms with two approaches. The first one is with React components. The second is by using Formik hooks. We've already, partially, explored the first approach in the first part. In this part, we will take a look at this approach one more time, now using Formik components instead of custom.

A word on dependencies

This tutorial will use minimal number of dependencies. First, we will need the react, react-dom and react-scrips. These three will help as get the React app from the ground. You can either install these dependencies by yourself or use the create-react-app app to setup everything for you.

When you have the React app ready, you two additional dependencies. The first one will be Formik, library that will power our form. The second dependency will be Yup. This is a validation library that will help as create validation schema for our form. We will talk about this in the next section, "Validation schema".

In this tutorial, we will use react and react-dom version 17.0.2. The react-scrips will be version 4.0.3. Formik will be version 2.2.9. Version of Yup will be 0.32.9. When you install these dependencies you are ready to go.

Validation schema

For this part, we will use the same validation schema as we used in the previous part. This validation schema will contain three form fields, "name", "email" and "password". All these fields will be strings and all will be required. For the "email" field, we will want to check that any value user provides is in email format.

We could build this schema, and necessary validation logic by ourselves and connect it with Formik. We don't have to. Instead, we can use available validation libraries to do this work for us. One of these libraries is [Yup]. Thanks to this library, we can create validation schema objects Formik can use to validate all fields on our forms.

A nice thing on Yup is that it provides various methods we can use to create validation schema that fits our needs. For example, we can use method string() to specify that some field value must be a string. We can then make it required by using required() method. To make sure something is in email format?

Yup provides method email() that checks if the value passed into the input is in email format or not. There are many other methods, and customizations, ready to use. For this tutorial, we will stick with these three, string(), required() and email(). Yup also allows to define error messages for each field.

In a fact, we can define error message for each of Yup's validation methods. What this means is that we can display different message for the same field. What message will be visible will depend on current error. Creating these messages is easy. All we need is to pass these messages as strings to specific Yup method.

For example, we can define that field is required and specify a simple message for this condition: required('Field "X" is required'). We will use this feature in our schema as well and define different message for different validations. Nonetheless, the validation will remain simple.

// Import Yup:
import * as Yup from 'yup'

// Create validation schema for form
// with three fields: "name", "email" and "password":
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

Using Formik and its components

The schema is ready. Now, let's build React form using mainly Formik components. The upside of this approach is that we will not have to specify as many input element attributes as we had to in the previous part. This is because all form components are provided by Formik and as such are automatically connected to Formik parent instance (context).

Formik components we will need will be three, Formik, Form and Field. The Form and Field will be replacements for HTML form and input elements. The Formik component will create a parent instance and context of our Formik form. This will be the wrapper of the whole form, this includes the Form component.

Formik component

The Formik component has multiple attributes we can use to setup and customize Formik functionality. For the purpose of this tutorial we will need three: initialValues, onSubmit and validationSchema. The initialValues attribute is on object that allows as to define all form fields and their initial values.

For our form, we will specify properties of initialValues (form fields) to be "name", "email" and "password". All initial values will be empty strings. As a value for the validationSchema attribute we will use the validation schema we created with Yup. The value of onSubmit will be a function Formik will use when form is submitted.

For this tutorial, we will create arrow function with console.log() to log values provided to the form. In your case, this is the place where you can add any logic you want to execute when someone submits the form. There is one more thing we will need from Formik. We will need access to errors and touched objects.

These two are Formik's states that keep tract of fields that contain any errors and fields that have been touched, or focused. We can expose this data from Formik very easily. This is because Formik component uses render-prop pattern, and allows its children to be a function that returns some React component.

This rendered component will be our form. What we can do is to tell Formik component to expose some data by passing them as arguments to the function it renders. This will allow use to use this exposed data anywhere in the form. Data we will pass are the errors and touched objects. We will pass them using object destructuring.

// Import dependencies:
import { memo } from 'react'
import { Formik, Form, Field } from 'formik'

// Create the form component:
export const FormFormik = memo(() => {
  return (
    <Formik
      initialValues={{ name: '', email: '', password: '' }}
      onSubmit={(values) => {
        console.log(values)
      }}
      validationSchema={formSchema}
    >
      {({ errors, touched }) => (
        <Form>{/* The rest of the form content */}</Form>
      )}
    </Formik>
  )
})

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

Field components and error messages

Each form field will be composed of three parts: label, field and error message. We will create the label and error message with label and p HTML element. This is because Formik doesn't render labels nor provide a dedicated component for it. It renders only input placeholders if you tell it to do so.

So, if you want to use input placeholders instead of labels you can ignore the label elements. Instead of label, you can add placeholder attribute for each field with appropriate text. Another two attributes we will need will be type and name. The type is the same as input type attribute that specifies the type of input.

The name is also the same as input name attribute. Aside to that, it also allows Formik to connect field with correct value in form states. This includes initialValues, errors and touched. This means that value of name for each field has to match corresponding property in initialValues, errors and touched and also in validation schema.

So, if our schema contains rules for fields "name", "email" and "password", values for name attributes has to be one of these, name, email and password. That's all we need, or Formik needs, for the Field. No need for additional attributes or handlers. Last piece are error messages.

We will render error messages as plain text wrapped in p elements. The important thing here is the rendering condition for each message. We want to display errors only when there are any and when user really interacted with the form. We want to avoid showing errors in an empty form that was just loaded.

To ensure this, we will use the errors and touched objects. For each field, we will first check if there are any errors for that field. We will also check if a field has been touched. Only when field has an error and was touched we will show an error. We will get these information by using the value of name attribute.

Last thing. We will need a button to submit the form. This can be a regular HTML button element with type set to submit. When clicked, this will trigger Formik's onSubmit method. This is the method you pass as the value to onSubmit attribute of Formik component.

// ... previous code
<Form>
  <div>
    <label htmlFor="name">Name</label>

    {/* Create field component - renders input element */}
    <Field type="text" name="name" />

    {/* Show error if field contains error and was touched */}
    {errors.name && touched.name && <p>{errors.name}</p>}
  </div>

  <div>
    <label htmlFor="email">Email</label>

    {/* Create field component - renders input element */}
    <Field type="email" name="email" />

    {/* Show error if field contains error and was touched */}
    {errors.email && touched.email && <p>{errors.email}</p>}
  </div>

  <div>
    <label htmlFor="password">Password</label>

    {/* Create field component - renders input element */}
    <Field type="password" name="password" />

    {/* Show error if field contains error and was touched */}
    {errors.password && touched.password && <p>{errors.password}</p>}
  </div>

  <div>
    <button type="submit">Submit</button>
  </div>
</Form>
// ... rest of the code
Enter fullscreen mode Exit fullscreen mode

Putting it together

The Formik component is ready and Field components for each field, with error message, are ready as well. The thing that remains is to take the code we've created so far and put it together. This will give us working React form powered by Formik components, and validated by Yup.

// Import dependencies:
import { memo } from 'react'
import { Formik, Form, Field } 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 FormFormik = memo(() => {
  return (
    <Formik
      initialValues={{ name: '', email: '', password: '' }}
      onSubmit={(values) => {
        console.log(values)
      }}
      validationSchema={formSchema}
    >
      {({ errors, touched }) => (
        <Form>
          <div>
            <label htmlFor="name">Name</label>

            {/* Create field component - renders input element */}
            <Field type="text" name="name" />

            {/* Show error if field contains error and was touched */}
            {errors.name && touched.name && <p>{errors.name}</p>}
          </div>

          <div>
            <label htmlFor="email">Email</label>

            {/* Create field component - renders input element */}
            <Field type="email" name="email" />

            {/* Show error if field contains error and was touched */}
            {errors.email && touched.email && <p>{errors.email}</p>}
          </div>

          <div>
            <label htmlFor="password">Password</label>

            {/* Create field component - renders input element */}
            <Field type="password" name="password" />

            {/* Show error if field contains error and was touched */}
            {errors.password && touched.password && <p>{errors.password}</p>}
          </div>

          <div>
            <button type="submit">Submit</button>
          </div>

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

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

Conclusion: 3 ways to build React forms with Formik pt.2

This was the alternative of the first approach of using Formik to build React forms. This approach, using mainly Formik components, can reduce HTML markup you would otherwise need. With some validation library such as Yup, you can also remove a lot of code you would otherwise need for validation logic. I hope that this tutorial helped you learn how to do both.

Discussion (0)