Emmanuel Etukudo I am a full-stack developer with more than five years of experience, with a preference for JavaScript, Node.js, Go, React, Redux, and MongoDB.

Creating multi-step forms with React Stepzilla

6 min read 1684

react-stepzilla

A common challenge for web developers is collecting detailed user data without overwhelming the UI with long, complicated forms. Particularly with SPAs becoming more widely used, best practice for moving users through a process now requires breaking forms into small, digestible steps that won’t render a new page.

In this tutorial, we’ll demonstrate how you can enhance your UX with React Stepzilla, a multi-step wizard component designed for sequential data collection. With Stepzilla, you can render different types of React components, like data forms and HTML-to-text. To follow along with this React styling tutorial, you’ll need:

  • Familiarity with CSS, HTML, and JavaScript ES6
  • Node.js installed on your system
  • A web browser installed in your system, i.e., Chrome
  • A code editor installed on your development machine, i.e., VS Code
  • A basic understanding of React

Let’s get started!

Install Stepzilla

First, we’ll initialize a new React app and install Stepzilla by running the code below. If you’re using a Mac, navigate into your working directory via the terminal; for Windows users, access your directory via the command prompt:

npx create-react-app stepzilla-app

To start your app, run the command below:

cd stepzilla-app && npm start

Your app should look similar to the screenshot below:

New React App Install Screen

Next, we’ll install the React Stepzilla component by running the command below:

npm install --save react-stepzilla

Create a multi-step component with Stepzilla

Let’s write the logic for a multi-step form with three steps, About, Terms, and ConsentForm components. In the root directory of your project, create a new folder called components; inside, create a new file called multiStep.js.

Now, create a folder called steps. Navigate into it and create the following four files, About.js, Terms.js, ConsentForm.js and steps.js. Let’s write the code to populate these three steps.

Add the following code to About.js:

import react from 'react';

const About = () => {
    return (
        <div>
            <p>
                This simply dummy text of the printing and typesetting 
                industry. Lorem Ipsum has been the industry's standard dummy 
                text ever since the 1500s, when an unknown printer took a 
                galley of type and scrambled it to make a type specimen book. 
                It has survived not only five centuries, but also the leap into 
                electronic typesetting, remaining essentially unchanged. 
                It was popularised in the 1960s with the release of 
                Letraset sheets containing Lorem Ipsum passages, and more 
                recently with desktop publishing
            </p>
        </div>
    )
}
export default About;

Text sourced from https://www.lipsum.com

Add the code below to Terms.js:

import react from 'react';
const Terms = () => {
    return (
        <div>
            <p>
                A Terms and Conditions agreement is not legally required. However, having one
                comes with a number of important benefits for both you and your users/customers.
                The benefits include increasing your control over your business/platform,
                while helping your users understand your rules, requirements and restrictions.
            </p>
            <div class="form-group form-check">
                <input type="checkbox" class="form-check-input" id="exampleCheck1" />
                <label class="form-check-label" for="exampleCheck1">Accept</label>
            </div>
        </div>
    )
}
export default Terms;

Add the code below to ConsentForm.js:

import react from 'react';
const ConsentForm = () => {
    return (
        <div>
            <form>
                <div className="form-group">
                    <label htmlFor="exampleInputEmail1">Email address</label>
                    <input type="email" className="form-control" id="exampleInputEmail1" aria-describedby="emailHelp" />
                    <small id="emailHelp" className="form-text text-muted">We'll never share your email with anyone else.</small>
                </div>
                <div className="form-group">
                    <label htmlFor="exampleInputPassword1">Password</label>
                    <input type="password" className="form-control" id="exampleInputPassword1" />
                </div>
                <div className="form-group form-check">
                    <input type="checkbox" className="form-check-input" id="exampleCheck1" />
                    <label className="form-check-label" htmlFor="exampleCheck1">Check me out</label>
                </div>
            </form>
        </div>
    )
}
export default ConsentForm;

Now, we’ll create a constant called steps, which will hold the steps we created above. Add the code below to steps.js:

import About from './About';
import Terms from './Terms';
import ConsentForm from './ConsentForm';
export {About, Terms, ConsentForm};

Next, we’ll pass the steps constant to Stepzilla as the value for its steps option. In steps.js, add the following code:

import react from 'react';
import { About, Terms, ConsentForm } from './steps/steps';
import StepZilla from "react-stepzilla";
const steps =
    [
        { name: 'About', component: <About /> },
        { name: 'Terms', component: <Terms /> },
        { name: 'Consent', component: <ConsentForm /> },
    ]
const MultiSteps = () => {
    return (
        <div className='step-progress'>
            <StepZilla steps={steps} />
        </div>
    );
}
export default MultiSteps;

To display the multi-step component, open App.js and replace the existing code with the code below:

import MultiSteps from './components/MultiStep';
import './App.css';
function App() {
  return (
    <div className="App">
      <div className="stepsWrapper">
      <MultiSteps />
      </div>
    </div>
  );
}
export default App;

In the code above, we import the MultiSteps component into App.js. Now, your React app should look like the gif below:

Stepzilla Multistep Component React

Styling our multi-step form

To enhance our UI, let’s add some custom styling to our project. Because Stepzilla uses Bootstrap for styling, we’ll place the Bootstrap CDN at the header section of index.html in the public folder of our project:

&lt;!-- CSS only -->
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-KyZXEAg3QhqLMpG8r+8fhAXLRk2vvoC2f3B09zVXn8CA5QIVfZOJ3BCsw2P0p/We" crossorigin="anonymous">

Let’s add some custom styling to handle multi-step navigation, buttons, ol list color, padding, and margins. Replace the code in App.css with the code below:

.App {
  margin-top: 10%;
}
.stepsWrapper{
  width: 60%;
  margin: 0 auto;
}
ol.progtrckr {
  list-style-type: none;
  padding: 0;
}
ol.progtrckr li {
  display: inline-block;
  text-align: center;
  line-height: 4.5rem;
  cursor: pointer;
}
ol.progtrckr li span {
  padding: 0 1.5rem;
}
@media (max-width: 650px) {
  .progtrckr li span {
    display: none;
  }
}
.progtrckr em {
  display: none;
  font-weight: 700;
  padding-left: 1rem;
}
@media (max-width: 650px) {
  .progtrckr em {
    display: inline;
  }
}
ol.progtrckr li.progtrckr-todo {
  color: silver;
  border-bottom: 4px solid silver;
}
ol.progtrckr li.progtrckr-doing {
  color: black;
  border-bottom: 4px solid #CCCCCC;
}
ol.progtrckr li.progtrckr-done {
  color: black;
  border-bottom: 4px solid #0d6efd;
}
ol.progtrckr li:after {
  content: "\00a0\00a0";
}
ol.progtrckr li:before {
  position: relative;
  bottom: -3.7rem;
  float: left;
  left: 50%;
}
ol.progtrckr li.progtrckr-todo:before {
  content: "\039F";
  color: silver;
  background-color: white;
  width: 1.2em;
  line-height: 1.4em;
}
ol.progtrckr li.progtrckr-todo:hover:before {
  color: #ff4500;
}
ol.progtrckr li.progtrckr-doing:before {
  content: "\2022";
  color: white;
  background-color: #CCCCCC;
  width: 1.2em;
  line-height: 1.2em;
  border-radius: 1.2em;
}
ol.progtrckr li.progtrckr-doing:hover:before {
  color: #ff4500;
}
ol.progtrckr li.progtrckr-done:before {
  content: "\2713";
  color: white;
  background-color: #0d6efd;
  width: 1.2em;
  line-height: 1.2em;
  border-radius: 1.2em;
}
ol.progtrckr li.progtrckr-done:hover:before {
  color: #333;
}

Steps validation

The Stepzilla package is shipped with support for step validation using the isValidated utility function, which is available in both the class-based and Hooks component.

Let’s add basic validation to our ConsentForm component by updating Terms.js  with the code below:

import React from 'react';
class Terms extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            terms: this.props.terms,
        };
        this._validateOnDemand = true; // this flag enables onBlur validation as user fills forms
        this.validationCheck = this.validationCheck.bind(this);
        this.isValidated = this.isValidated.bind(this);
    }
    isValidated() {
        const userInput = this._grabUserInput(); // grab user entered vals
        const validateNewInput = this._validateData(userInput); // run the new input against the validator
        let isDataValid = false;
        // if full validation passes then save to store and pass as valid
        if (Object.keys(validateNewInput).every((k) => { return validateNewInput[k] === true })) {
            if (this.props.getStore().terms != userInput.terms) { // only update store of something changed
                this.props.updateStore({
                    ...userInput,
                    savedToCloud: false // use this to notify step4 that some changes took place and prompt the user to save again
                });  // Update store here (this is just an example, in reality you will do it via redux or flux)
            }
            isDataValid = true;
        }
        else {
            // if anything fails then update the UI validation state but NOT the UI Data State
            this.setState(Object.assign(userInput, validateNewInput, this._validationErrors(validateNewInput)));
        }
        return isDataValid;
    }
    validationCheck() {
        if (!this._validateOnDemand)
            return;
        const userInput = this._grabUserInput(); // grab user entered vals
        const validateNewInput = this._validateData(userInput); // run the new input against the validator
        this.setState(Object.assign(userInput, validateNewInput, this._validationErrors(validateNewInput)));
    }
    _validateData(data) {
        return {
            termsVal: (data.terms != false), // required: anything besides N/A
        }
    }
    _validationErrors(val) {
        const errMsgs = {
            termValMsg: val.termVal ? '' : 'Accept terms to continue',
        }
        return errMsgs;
    }
    _grabUserInput() {
        return {
            terms: this.refs.terms.value,
        };
    }
    render() {
        // explicit class assigning based on validation
        let notValidClasses = {};
        if (typeof this.state.termVal == 'undefined' || this.state.term) {
            notValidClasses.termCls = 'no-error col-md-8';
        }
        else {
            notValidClasses.termCls = 'invalid-feedback col-md-8';
            notValidClasses.termValGrpCls = 'val-err-tooltip';
        }
        return (
            <div className="step step3">
                <div className="row">
                    <form id="Form" className="form-horizontal">
                        <p>
                            A Terms and Conditions agreement is not legally required. However, having one
                            comes with a number of important benefits for both you and your users/customers.
                            The benefits include increasing your control over your business/platform,
                            while helping your users understand your rules, requirements and restrictions.
                        </p>
                        <div className="form-group form-check mb-3">
                            <input type="checkbox" ref="terms" class="form-check-input" id="exampleCheck1" />
                            <label class="form-check-label" for="exampleCheck1" onBlur={this.validationCheck} defaultValue={this.state.terms}>Accept</label>
                            <div className={notValidClasses.termValGrpCls}>{this.state.termValMsg}</div>
                        </div>
                    </form>
                </div>
            </div>
        )
    }
}
export default Terms;

Now, your app should look similar to the screenshot below:

Final Multistep Component React Stepzilla

Conclusion

React Stepzilla is a powerful wizard that makes it simple to build multi-step components in React. When we simplify complex forms by breaking them down into smaller steps that render on a single page, we drastically improve our UX by decluttering our UI and decreasing load times.

In this tutorial, we became familiar with React Stepzilla by creating an app that takes our users through three steps. Stepzilla is easy to use and customizable to suit your individual project’s needs. I hope you enjoyed this tutorial!

LogRocket: Full visibility into your production React apps

Debugging React applications can be difficult, especially when users experience issues that are hard to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

LogRocket combines session replay, product analytics, and error tracking – empowering software teams to create the ideal web and mobile product experience. What does that mean for you?

Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay problems as if they happened in your own browser to quickly understand what went wrong.

No more noisy alerting. Smart error tracking lets you triage and categorize issues, then learns from this. Get notified of impactful user issues, not false positives. Less alerts, way more useful signal.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.



Modernize how you debug your React apps — .

Emmanuel Etukudo I am a full-stack developer with more than five years of experience, with a preference for JavaScript, Node.js, Go, React, Redux, and MongoDB.

Leave a Reply