DEV Community

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

Posted on • Originally published at blog.alexdevero.com

3 Ways to Build React Forms with Formik Pt.3

Building React forms can be lengthy and painful process. Not anymore with library called Formik. Formik provides a nice and easy way to build React forms. This tutorial will show you how to build React forms with HTML elements along with the useFormik() hook provided by Formik.

You can find this example on Codesandbox.

A quick intro

In the first part of this tutorial, we took a look at how to build React forms with custom HTML elements and form state and logic provided by Formik. In the second part, we switched to Formik components and build form with them. In this final part we will make another change, a small step back.

We will switch from Formik components back to HTML form elements. This also includes dropping the Form component provided by Formik we used as a wrapper in previous parts. We will replace this component with HTML form element. We will also use HTML form components for the rest of form content.

Removing Formik components also removes all functionality provided by Formik. However, this doesn't mean we can't use Formik to build React forms. Formik allows us to use its functionality through hooks. This is what we will do in this tutorial. We will build React form and connect it to Formik through useFormik() hook.

Formik and context

Switching to useFormik() hook can make things easier in some situations. However, there is a downside. When we remove the Form component we also remove Formik's context automatically created when we use this component. This means that we can no longer use the useFormikContext hook to access this context and Formik's methods.

If you are not using this specific hook, there is no reason to worry about anything. You can use Formik with useFormik() hook to build React forms just as with Form and useFormikContext().

Project dependencies

Let's quickly talk about dependencies. This tutorial will use only few of them. We will need some dependencies required to run our React app. These will be react, react-dom and react-scripts. These three will all be version 17.0.2. Next is the Formik library. This will be version 2.2.9.

The last dependency will be validation library called Yup. We will use this library to create a simple validation schema for our React form. The version of Yup will be 0.32.9. This is all we need.

Validation schema

Validation schema for the form we are going to build will be simple. We will need to validate only three form fields, name, email and password. All these fields will be required and their values will be strings. In case of email field, we will also want to ensure the value if in email format.

Thanks to Yup library, building this schema is quick and easy. All we have to do is to create new Yup object and define its shape. The shape is in the form of an object where each key or property is name of one field. The value is a validation rule for that field. This validation rule is composed of method provided by Yup.

For example, all field values must be strings and are required. We can create this rule by using Yup's string() and required() methods. We can also specify error message for each of these validation methods by passing the message as an argument to the specific method, required('Field is required').

To make the schema complete we need to add one more rule, for email.
Here, we will use Yup's email() method. This method will check that a specific field value is provided in email format. If not, Yup will detect an error. This is all we need for our validation schema.

// 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 useFormik() hook

As we discussed in the introduction part, we are going to build the React form with Formik's useFormik() hook. This hook allows us to do a couple of things. First, we can use it to setup Formik state for the form. This means specifying what fields we will need and their initial values.

This hook also allows us to specify any custom validation schema we want to use for our React form. For example, the schema we've built with Yup. We can also specify how we want to handle the onSubmit event form will fire when submitted. We can setup this through config object the useFormik() hook accepts as a parameter.

This is the input. Next is the output. The useFormik() hook returns methods and states. We can use these methods as input event handlers to connect individual form fields to Formik and its states. So, when field is focused and receives some input Formik will be able to register it and store that input inside its state.

Receiving and storing input is not enough. We also need a way to access these stored values. Otherwise, we can't render them in form fields as input values. This is not a problem. One of the states the useFormik() hook exposes is values state. This state contains values for all registered form fields.

We can use this state in combination with specific field name to receive value specific field should display. Two additional states we will use are errors and touched states. When any field in the form contains error, created by Yup validation, that error will end up in Formik's errors state.

The touched states registers if specific field has been touched, focused, since the form was rendered. We will use these two states to decide when to show error messages for fields with errors.

// Import dependencies:
import { memo } from 'react'
import { useFormik } 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 FormHook = memo(() => {
  // Call useFormik() hook with config
  // and assign it to formik variable:
  const formik = useFormik({
    // Initial values for each form field:
    initialValues: {
      name: '',
      email: '',
      password: ''
    },
    onSubmit: (values) => {
      // Logic to handle onSubmit event
      console.log(values)
    },
    // Schema for validating the form:
    validationSchema: formSchema
  })

  return (/* Form component */)
})

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

Building form inputs

The markup for each field will be simple. There will be label, input and p for error message. These elements will be wrapped inside a div element. Each input element will have type, name, value, onChange and onBlur attributes. Type will always be "text". Name will correspond to each field name.

To get the value for value attribute we will reference the values state returned by useFormik() hook, along with the field name. Note: we assigned all values returned by this hook to the formik variable. So, to access this state, we have to reference this variable.

We will do this also to access Formik's event handlers. Specifically handlers for onChange and onBlur events. These will be Formik's handleChange and handleBlur methods. The handleChange will ensure our form state is always up-to-date. The handleBlur will help us register if field has been touched.

The last piece are error messages. We've already defined the messages itself in the validation schema in the beginning. Now, we need to make sure they are visible at the right time. We can ensure this by checking two things. First, the field contains an error. Second, the field has been touched.

Only if these two conditions are true we want the error message to appear. To display the error message itself, we can use the errors state along with name of the specific field. If there is an error for this field, this will return the message as a string. We will get all this data from props.

import { memo } from 'react'

export const Input = memo((props) => (
  <div>
    <label htmlFor={props.name}>{props.label}</label>
    <input
      type={props.type}
      name={props.name}
      value={props.value}
      onChange={props.handleChange}
      onBlur={props.handleBlur}
    />
    {props.hasError && props.isTouched && <p>{props.errorMessage}</p>}
  </div>
))
Enter fullscreen mode Exit fullscreen mode

Note: Formik automatically sets all fields to touched when form is submitted. So, even if nobody touches any field, and tries to submit the form, all error messages will be displayed because Formik will set all fields to touched. This means that the formik.errors.someField && formik.touched.someField will be true && true.

Building the form

We are almost done. Last step to make is creating the form wrapper, adding some submit button and all input fields. Two form element will require two attributes, onSubmit and noValidate. We will set the first to Formik's handleSubmit method. This method will trigger the onSubmit method we defined in useFormik() hook.

The noValidate attribute will disable native form validation. We want to do this because we want the Formik and Yup take care of it. About the content of the form. We could write the code for all input field components. However, we don't have to. We basically need to know only the field name.

Knowing this, we can determine what value should each field render. We can also use the field name to check for errors and render correct error message. We can also easily determine what type the input should be, whether it should be text, email or password. We can create a small and handy object to make this even easier.

Thanks to this, we have to write much less code and make the whole form much shorter. How can we get each field name? We can take the Formik's values state, iterate over its keys, and render input component for each key, using current field name to use correct data.

<form onSubmit={formik.handleSubmit} noValidate>
  {Object.keys(formik.values).map((fieldName) => (
    <Input
      key={fieldName}
      value={formik.values[fieldName]}
      errorMessage={formik.errors[fieldName]}
      handleBlur={formik.handleBlur}
      handleChange={formik.handleChange}
      hasError={formik.errors[fieldName]}
      isTouched={formik.touched[fieldName]}
      label={fieldName[0].toUpperCase() + fieldName.substring(1)}
      name={fieldName}
      type={inputTypes[fieldName]}
    />
  ))}

  <div>
    <button type="submit">Submit</button>
  </div>
</form>
Enter fullscreen mode Exit fullscreen mode

Putting the form together

We are almost done. There is one more thing we have to do. We have to put together all the code we created so far and we will have a working form powered by Formik. Well, almost. Our form also needs some logic to handle the onSubmit event and send the data somewhere, but that is up to you.

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

// Import Input component:
import { Input } from './input'

// 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'),
})

// Object with input types:
const inputTypes = {
  name: 'text',
  email: 'email',
  password: 'password',
}

// Create the form component:
export const FormHook = memo(() => {
  const formik = useFormik({
    initialValues: {
      name: '',
      email: '',
      password: '',
    },
    onSubmit: (values) => {
      console.log(values)
    },
    validationSchema: formSchema,
  })

  // Render the form:
  return (
    <form onSubmit={formik.handleSubmit} noValidate>
      {Object.keys(formik.values).map((fieldName) => (
        <Input
          key={fieldName}
          value={formik.values[fieldName]}
          errorMessage={formik.errors[fieldName]}
          handleBlur={formik.handleBlur}
          handleChange={formik.handleChange}
          hasError={formik.errors[fieldName]}
          isTouched={formik.touched[fieldName]}
          label={fieldName[0].toUpperCase() + fieldName.substring(1)}
          name={fieldName}
          type={inputTypes[fieldName]}
        />
      ))}

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

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

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

As you can see, building React forms doesn't have to be lengthy and painful process. With libraries like Formik it can be actually easy. I hope that this tutorial helped you learn how to build React forms using HTML elements and the useFormik() hook.

Discussion (0)