React Step Builder

How to create multi-step forms in React?

Alt Text

Creating a multi-step registration form was a challenge I faced a while back, which inspired me to create the react-step-builder package. In this post, I will make a quick demo on how to create a multi-step form using the package.

Let me briefly explain what the package does.

react-step-builder

It provides two wrapper components: Steps and Step.

Steps is a wrapper component for Step component(s), which takes your step components, combines their state in one location, and serves helper methods for moving between them without losing the previously collected data.

Let’s start with the demo which, I believe, will make it easier to understand the problem that the package is intended to solve.

Not every feature/method of the package is explored in this demo. Please refer to the documentation for that.

1. Create a new project and install the package

$ npx create-react-app rsb-demo

$ npm install react-step-builder

2. Have your step components ready

For the sake of simplicity, I will provide 3 sample components here. In the first and second components, we will ask our user to provide some info and, in the third step, render that info on the screen. Of course, in a real-life application, you probably will want to submit that data to some API of sorts. Also, you might have as many/big step components as you’d like.

At this point, step components will have zero functionality. We will empower them later with the provided methods without worrying about creating our form handlers and such.

// Step1.js
import React from "react";

function Step1(props) {
  return (
    <div>
      <p>Name: <input name="name" /></p>
      <p>Surname: <input name="surname" /></p>
    </div>
  );
}

export default Step1;
// Step2.js
import React from "react";

function Step2(props) {
  return (
    <div>
      <p>Email: <input name="email" /></p>
      <p>Phone: <input name="Phone" /></p>
    </div>
  );
}

export default Step2;
// FinalStep.js
import React from "react";

function FinalStep(props) {
  return (
    <div>
      <p>Name:</p>
      <p>Surname:</p> 
      <p>Email:</p>
      <p>Phone:</p>
    </div>
  );
}

export default FinalStep;

3. Build your multi-step form

In your App.js file, import the wrapper components, and pass your newly created step components in.

// App.js
import React from "react";

import { Steps, Step } from "react-step-builder";
import Step1 from "./Step1";
import Step2 from "./Step2";
import FinalStep from "./FinalStep";

function App() {
  return (
    <div className="App">
      <Steps>
        <Step component={Step1} />
        <Step component={Step2} />
        <Step component={FinalStep} />
      </Steps>
    </div>
  );
}

export default App;

At this point, your step components will receive helper methods and properties in their props. We will utilize them to give our multi-step form some functionality.

4. Connect the form elements to the global state

Let’s go back to our Step1 component and update our form elements and provide the state value for the value property and the handler method for the onChange event.

When you create an input like this: <input name="foo" />, the value for this element is saved in your global state with the foo key. So make sure you are giving unique names for each form element. That’s what we will provide for the value property in our input elements.

Now let’s access to our global state and update our input elements as such:

<input name="name" value={props.getState('name')} /></p>
<input name="surname" value={props.getState('surname')} /></p>

We are repeating the same changes in Step2 and FinalStep components as well.

// Step2.js
<input name="email" value={props.getState('email')} /></p>
<input name="phone" value={props.getState('phone')} /></p>

There is no form element in the FinalStep component, we are just accessing the state data that has been entered by the user previously.

// FinalStep.js
<p>Name: {props.state.name}</p>
<p>Surname: {props.state.surname}</p>
<p>Email: {props.state.email}</p>
<p>Phone: {props.state.phone}</p>

At this point, you might ask “why did we access the state with the props.getState('name') method earlier but with props.state.name in the last one. The answer is simple: this.props.name is undefined until your user starts typing in the field. However, props.getState('name') returns an empty string even if the user hasn’t typed anything in the input yet. That way your form element gets its default value as an empty string so that you don’t encounter the controlled/uncontrolled component error from React.

Now it is time to add onChange handlers so that our form saves user inputs into our global state.

Let’s update our step components and give them a handler method for the onChange event.

<input name="name" value={props.getState('name')} onChange={props.handleChange} /></p>
<input name="surname" value={props.getState('surname')} onChange={props.handleChange} /></p>

We did onChange={props.handleChange} to all of our form elements. It will make sure that our form values are saved with the correct key to our global state properly.

NOTE: You may also manipulate your global state with props.setState(key, value) method. It can be used for cases where synthetic React events (e.g. onChange) are not available. For example, clicking on an image or text and updating the state with the onClick method.

Our steps are ready now. Let’s work on previous and next buttons so we can take a look around.

5. Utilize previous and next functionality

Every step will have props.next() and props.prev() methods for moving between steps. I will follow the first instinct and create Next and Previous buttons accepting those methods in their onClick events.

<button onClick={props.prev}>Previous</button>
<button onClick={props.next}>Next</button>

You may add these buttons to every single step component individually or, to improve maintainability, you may also create a Navigation component. I will go ahead and keep things simple in this post but happy to help in the comments if you would like to go with the Navigation component approach.

Now as the last step, let’s talk about built-in methods of the individual steps.

6. Disable/conditionally render the navigation buttons

As it probably popped in your head, what if we don’t want to show the Previous button in the first step component or the Next button in the last step component since there is no previous/next step in the first/last steps. These below-mentioned helper methods are very practical to solve this problem.

// From the documentation
props.step.isFirst() // Returns true if it's the first step, otherwise false
props.step.isLast() // Returns true if it's the last step, otherwise false
props.step.hasNext() // Returns true if there is a next step available, otherwise false
props.step.hasPrev() // Returns true if there is a previous step available, otherwise false

If you would like to use the disable approach, you may do something like this:

<button disabled={props.step.isFirst()} onClick={props.prev}>Previous</button>
<button disabled={props.step.isLast()} onClick={props.next}>Next</button>

And this is the conditional rendering approach:

{props.step.hasPrev() && <button onClick={props.prev}>Previous</button>}
{props.step.hasNext() && <button onClick={props.next}>Next</button>}

Here is the final result:
react-step-builder demo

And here is a working example on codesandbox.

Please refer to the documentation as it provides a detailed explanation of each method and its use.

1 thought on “How to create multi-step forms in React?

Leave a Reply

Your email address will not be published. Required fields are marked *