Use Formik+Yup to create a multi-step React form part 3: build multi-step form

Yingqi Chen
5 min readAug 3, 2020

Finally, we came to the last part about the “multi-step” part, after we talk about how to build a simple form with Formik and how to validate with Yup and display errors. I already wrote an article about how to build a pure React form a few weeks ago, actually, the general idea of implementation would be very similar, so I would just explain the difference here. If you want to know more details, feel free to check out that article or leave me a comment!

To start first, here are the three articles I mention just in case you want to know more details, because I am not going to repeat too much about what I already wrote in those three articles:

  1. Create a multi-step form in React
  2. Use Formik+Yup to create a multi-step React form part 1: build a simple form
  3. Use Formik+Yup to create a multi-step React form part 2: validate with Yup and display errors

We have two main parts in a multi-step form

So, in my React sign up form, it consists of two parts mainly: the parent/ component and the child component. The parent component would contain a lot of the logic where you can pass to all the children components while the children components will do the job to display components of different steps.I would go straight to explain how do I implement these two parts here, but if you want to know more details about why, please visit my old article.

Implement the Parent component

Since I already explain how a Formik form and Yup validation works, here I post the parent component directly:

import React, { useState } from "react";
import { Formik, Form } from "formik";
import CompanyInfo from "../components/signupComponents/ CompanyInfo";
import UserInfo from "../components/signupComponents/userInfo";
import AfterSubmit from "../components/signupComponents/afterSubmit";
import * as Yup from "yup"
const SignupSchema = Yup.object().shape({
companyName: Yup.string()
.required('A company name is required'),
companyAddress: Yup.string()
.required('A company address is required'),
name: Yup.string()
.required('A name is required'),
phone: Yup.string()
.required('A phone is required')
.matches(/^[0-9]{10}$/, 'Must be exactly a 10-digit Number.')
});
const renderStep = (step, values, errors, touched, handleChange, handleSubmit, next, back, signupSuccess, handleBlur) => {
switch (step) {
case 1:
return (
<CompanyInfo
values={values}
errors={errors}
touched={touched}
handleChange={handleChange}
next={next}
handleBlur = {handleBlur}
/>);
case 2:
return (
<UserInfo
values={values}
errors={errors}
touched={touched}
handleChange={handleChange}
back={back}
handleSubmit={handleSubmit}
handleBlur = {handleBlur}
/>);
case 3:
return <AfterSubmit signupSuccess={signupSuccess} />;
default:
return <CompanyInfo errors={errors} touched={touched} />;
}
};
const MultiStep = () => {
const [step, setStep] = useState(1);
const [signupSuccess, setSignupSuccess] = useState(false)
const formData = {
companyName: "",
companyAddress: "",
name: "",
phone:"",
}
const next = () => {
// update state.step by adding to previous state
setStep(s=>s+1)
}

// process to previous step
const back = () => {
// update state.step by minus 1 from previous state
setStep(s=>s-1)
}
return (
<>
<Formik
enableReinitialize
initialValues={{ ...formData }}
validationSchema={SignupSchema}
>
{({ values, errors, touched, handleChange,handleSubmit, handleBlur}) => (
<Form className={classes.form}>
{renderStep(step, values, errors, touched, handleChange, handleSubmit, next, back, signupSuccess)}
</Form>
)}
</Formik>
</>
);
};
export default MultiStep

As you can see, the CompanyInfo , UserInfo , and AfterSubmit are the children components, which would be rendered according to the state step in the parent component. next and back methods are used to set the value of step so that we can control which component to render.

Difference between pure React form and Formik form

We can see here we don’t have to make our own handleChange and handleSubmit because they are provided by Formik and we can extract them directly. Also, Formik gives us a lot of useful tools like errors and touched , which are handy objects that we can use for displaying errors blocks combined with the Yup validation schema(if that doesn't make sense to you, you can refer to this article.)

Implement the Children component

Once you set up the parent component, the child component is not too difficult, what you need to do is just get the props that are passed from the parent and then display them. Here is a classic child component will look like:

import React from "react"const UserInfo = props =>{
const { values, handleChange, handleSubmit, back,errors, touched, handleBlur} = props
const nameHasError = errors.name && touched.name
const phoneHasError = errors.phone && touched.phone
return(
<>
<input
error = {nameHasError}
label={nameHasError ? "Error" : "name"}
helperText={nameHasError ? errors.name : null}
onBlur={handleBlur}
variant="outlined"
defaultValue={values.name}
onChange={handleChange}
name="name"
/><br />
<input
error = {phoneHasError}
label={phoneHasError ? "Error" : "phone"}
helperText={phoneHasError ? errors.phone : null}
onBlur={handleBlur}
variant="outlined"
values={values.phone}
defaultValue={values.phone}
onChange={handleChange}
name="phone"
/> <br />
<button
onClick={back}
>Back
</button>
<button
disabled={(!touched.name || !touched.phone) || (phoneHasError || nameHasError)}
onClick={handleSubmit}
>Submit
</button>
</>
)
}
export default UserInfo;

The key to be part of a multi-form is that you need two buttons to control the step value so that you can control the whole form go to next step or go back to the previous step. Since all the values are preserve in the parent component, when you go back and forth, the information would not lost.

Validation and errors (the Formik way)

As you can see, since Formik already provides some handy methods, the child would just need to extract them. And I really love it when I handle the errors, it is much easier!

In the parent component, we pass a SignupSchema to Formik. And all the inputs will be validated with that schema and errors will be stored in the errors object. touched is also very handy where you can decide to display the error or not because errors exist when the form is firstly loaded, and you don’t want the user sees a bunch of errors when firstly arrives at your website. Make sure you use handleBlur though! That’s the key to use touched , or the touchedstatus can’t be detected.

Handle Changes and Submit (the Formik way)

And the built-in handleChange function will change the initialValue you pass to Formik according to the input’s name or id. handleSubmit will automatically submit the values object, the modified initialValue.

More useful methods (the Formik way)

There are a lot of useful methods like setFieldValue , which helps you manually set the values, also setFieldTouched , which helps imperatively set the field touched. I would recommend to go to the documentation to check the props part. Formik is an amazing tool!

Thanks for reading!

--

--

Yingqi Chen

Software engineer and a blockchain noob. Excited about the new world!!LinkedIn:https://www.linkedin.com/in/yingqi-chen/