Multistep form in React using formik and yup

·

6 min read

As a front-end developer working on a project by myself for the first time, I came across many multistep forms with validation. I could go through the simple ones, but when I came across a form with fifty input fields divided into three sections and requiring validation, I realized I needed to find a more effective approach to handle my forms. In this blog post, I will be explaining extensively how I manage multistep forms in react using formik for handling inputs and yup for validating our inputs.

First step: Creating app and installing dependencies

In this scenario, we will be creating a sign-up component that has a three-step form including a preview page from scratch. The first step is to create react app and install the required dependencies.

//install react with typescript
npx create-react-app my-app --template typescript 
yarn create react-app my-app --template typescript

//install react without typescript
npx create-react-app my-app

//start your application
cd my-app
npm start

You can copy the block of code above into your terminal to create a react app either with or without typescript.

//install formik 
npm install formik --save
yarn add formik

//install yup
npm i yup

The above block of code can be used to add formik and yup to your application and we are ready to create our multi-step form 😎.

Second step: Creating the files for each step

The second step involves us creating the files for each step, before that, we have to create a parent component that mothers each of the three steps in our form. Do not forget to import this component into your App.js file.

import React from "react";
import StepOne from "./StepOne"
import StepTwo from "./StepTwo"
import Preview from "./Preview"

const step = ["details page", "password page", "preview page"]

function _renderStepContent(step) {
  switch (step) {
    case 0:
      return <StepOne />;
    case 1:
      return <StepTwo />;
    case 2:
      return <Preview />;
    default:
      return <div>Not Found</div>;
  }
}
const Index = () => {
  return (
    <div>This is the mother component tot he three steps</div>
  )
}
export default Index

Now that we have created the three components representing the three steps, we have imported them into the parent component (displayed in the figure above), and we also incorporated the switch statement holding the three components to handle the render of each step. We also declared a step to keep track of which step we are in at any given time. The next step is to render our steps in the parent component.

Third step: Introduction of Formik and Yup

We proceed to write the initial value for formik and yup validation schema which will help handle user interaction. We will create a file for our validation schema to maintain a clean component.

import * as Yup from "yup";

export const validationSchema = [
  Yup.object().shape({
    firstName: Yup.string()
      .required("First name is required")
      .max(20, "Name field should be less than 20 characters"),
    lastName: Yup.string()
      .required("Last name is required")
      .max(20, "Name field should be less than 20 characters"),
    jobTitle: Yup.string().required("Job title is required"),
    phoneNumber: Yup.string()
      .required("This field is compulsory")
      .min(5, "Phone Number field should be more than 5 characters")
  }),

  Yup.object().shape({
    password: Yup.string()
      .required()
      .matches(
        /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*])(?=.{8,})/,
        "Must Contain 8 Characters, One Uppercase, One Lowercase, One Number and One Special Case Character"
      ),
    passwordConfirm: Yup.string()
      .required("Required")
      .oneOf([Yup.ref("password")], "Password doesn’t match, try again.")
  })
];

Now that we have written our schema, we made it an array so it takes effect differently on different steps using the index/ active step, should we have 10 steps that require validation, we will make sure to write a schema with 10 arrays, each index carrying the validation for the properties that tallies with the index of the component in our active step. We will proceed to use this along with formik in the parent component.

import React from "react";
import { Formik } from "formik";

import StepOne from "./StepOne";
import StepTwo from "./StepTwo";
import Preview from "./Preview";
import { validationSchema } from "./validationSchema";

const steps = ["details page", "password page", "preview page"];

function _renderStepContent(step) {
  switch (step) {
    case 0:
      return <StepOne />;
    case 1:
      return <StepTwo />;
    case 2:
      return <Preview />;
    default:
      return <div>Not Found</div>;
  }
}

const Index = () => {
  const [activeStep, setActiveStep] = React.useState(0);
  const currentValidationSchema = validationSchema[activeStep];
  const isLastStep = activeStep === steps.length - 1;

  const handleBack = () => {
    setActiveStep((step) => step - 1);
  };

  function handleSubmit(values, actions) {
    if (isLastStep) {
      alert(values);
    } else {
      setActiveStep((prev) => prev + 1);
      actions.setTouched({});
      actions.setSubmitting(false);
    }
  }

  const initialValue = {
    firstName: "",
    lastName: "",
    email: "",
    jobTitle: "",
    phoneNumber: "",
    password: "",
    passwordConfirm: ""
  };

  return (
    <div>
      <h1>Sign up here</h1>
      <div>
        <Formik
          initialValues={initialValue}
          onSubmit={handleSubmit}
          validateOnChange={false}
        >
          {({ isSubmitting, handleSubmit }) => (
            <form onSubmit={handleSubmit}>
              {_renderStepContent(activeStep)}
              <div display="flex">
                {activeStep !== 0 ? (
                  <grid>
                    <div>
                      <button type="button" onClick={handleBack}>
                        Previous
                      </button>
                    </div>
                    <div>
                      <button type="submit">Continue</button>
                    </div>
                  </grid>
                ) : (
                  <div ml={4} position="absolute" left="550px">
                    <button type="submit">Continue</button>
                  </div>
                )}
              </div>
            </form>
          )}
        </Formik>
      </div>
    </div>
  );
};
export default Index;

From the above block of code, we have successfully created a skeleton for our multistep form that allows us to navigate between steps without losing information filled in the previous step. We will now proceed now create a reusable formik input area that allows us just pass props of name, placeholder, label, and type and it will automatically incorporate the validation schema and the initial value state we already defined.

Step four: Filling our steps with required input fields

import { useField } from "formik";

export const MyTextField = ({ label, ...props }) => {
  const [field, meta, helpers] = useField(props);
  return (
    <div style={{ marginBottom: "10px" }}>
      <label>
        {label}
        <input {...field} {...props} />
      </label>
      {meta.touched && meta.error ? (
        <div className="error" style={{ fontSize: "10px", color: "red" }}>
          {meta.error}
        </div>
      ) : null}
    </div>
  );
}; //component from formik docs

We can then proceed to use this reusable input field in our components and pass the necessary props.

import { MyTextField } from "../TextField";

const StepOne = () => {
  return (
    <div
      style={{
        display: "flex",
        flexDirection: "column"
      }}
    >
      <MyTextField type="text" name="firstName" placeholder="First name" />
      <MyTextField type="text" name="lastName" placeholder="Last name" />
      <MyTextField type="email" name="email" placeholder="Email" />
      <MyTextField type="text" name="phoneNumber" placeholder="Phone number" />
    </div>
  );
};
export default StepOne;
import { MyTextField } from "../TextField";

const StepTwo = () => {
  return (
    <div>
      <MyTextField type="password" name="password" placeholder="Password" />
      <MyTextField
        type="password"
        name="passwordConfirm"
        placeholder="Confirm password"
      />
    </div>
  );
};

export default StepTwo;
import { useField } from "formik";

const Preview = (props) => {
  const [meta] = useField(props.name);
  const { value } = meta;
  return (
    <div>
      <h3>Confirm sign up details</h3>
      <div>
        <div style={{ display: "flex", justifyContent: "space-between" }}>
          <p>First name</p>
          <p>{value?.firstName}</p>
        </div>
        <div style={{ display: "flex", justifyContent: "space-between" }}>
          <p>Last name</p>
          <p>{value?.lastName}</p>
        </div>
        <div style={{ display: "flex", justifyContent: "space-between" }}>
          <p>Email address</p>
          <p>{value?.email}</p>
        </div>
        <div style={{ display: "flex", justifyContent: "space-between" }}>
          <p>Phone number</p>
          <p>{value?.phoneNumber}</p>
        </div>
      </div>
    </div>
  );
};
export default Preview;

The useField formik hook is available for use inside every component which is rendered inside a formik parent. It carries the value input by users and a lot of other details that can be found in formik docs. In the block of code above, we extracted values from the useField and used them in our preview component.

We have completed the multistep form with three steps as discussed using ReactJS, formik, and yup. Below is a link to the code sandbox repository for full code.