How to create multi-step forms in React?
Tags: react, registration, multistep, form
Published: 2020-09-11 19:04:23



{% github sametweb/react-step-builder no-readme %}

---

> **This guide is using React Step Builder v2.0.11. If you are using the version v3+, please see the latest documentation on [package's NPM page](https://npmjs.com/package/react-step-builder).**

---

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](https://dev-to-uploads.s3.amazonaws.com/i/4kbly5a0votuv9c65whh.png)

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 the 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.

> _For detailed explanations, please refer to [the documentation](https://github.com/sametweb/react-step-builder#react-step-builder)._


#### 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.

```javascript
// 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;
```

```javascript
// 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;
```

```javascript
// 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.

```javascript
// 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:

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

If you realized, our `getState` method takes two parameters: The first one is the name of the input element, second is the default value. We pass an empty string that way we don't receive React's "uncontrolled/controlled component" warning in our console.

Now let's repeat the same changes in `Step2` and `FinalStep` components as well.

```javascript
// 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.

```javascript
// 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 (thanks to the second parameter we passed) 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.

```javascript
<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.

```javascript
<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 explain the `Navigation` component later in this post.

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. The 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:

```javascript
<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:

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

Now let's add one global `Navigation` component to render in every step using the `config` object. 

Create a `Navigation` component like this:

```
const Navigation = (props) => {
  return (
    <div>
	<button onClick={props.prev}>Previous</button>
	<button onClick={props.next}>Next</button>
    </div>
  );
};
```

Now let's create the `config` object.

```
const config = {
  navigation: {
    component: Navigation,
    location: "before", // or after
  }
};
```

Finally, let's pass this object to our `Steps` component.

```
<Steps config={config}>
  // your Step components
</Steps>
```

*Update v.2.0.7*
You can pass additional `before` or `after` properties to the config object. These properties accept a component identical to the `Navigation` component. As their name suggests, the component you pass to `before` / `after` property is rendered before/after the Step components.

NOTE: If you want to pass your own props to your step components, you may do so by simply passing props to `Step` components directly. Your step component will receive those props automatically.

> Here is the final result:
![react-step-builder demo](https://dev-to-uploads.s3.amazonaws.com/i/91vlubu8fv4yjdbe7n14.gif)


Here is a working example on codesandbox:

{% codesandbox react-step-builder-demo-j55cn %}


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