Multi-Stepper component in React

In this tutorial, we will see how to create a multi-stepper component in React that takes a list of components and generate each for each step.

Make the component extensible bypassing the navigation options to the components in the list so that they can move to the next step at will.

We can break down this component in two parts.

  • Create the UI for the steps
  • Add navigation to the steps

Create the UI for the steps

Based on the number of components we receive, we have to generate that many steps. And each steps will have grey color by default and the active ones will have a different color.

From one step to another, there will be a line to determine the next step.

All the steps before the active step will be considered as done thus we will have the active class for them as well.

Multi-Stepper component layout

import React, { useState } from "react";

const Stepper = ({ list }) => {
  const [currentStep, setCurrentStep] = useState(0);
  const stepsCount = list.length;
  const steps = [];

  for (let i = 0; i < stepsCount; i++) {
    steps.push(
      <div
        className={`steps ${currentStep >= i ? "active" : ""}`}
        key={i}
      >
        {i + 1}
      </div>
    );
  }


  return (
    <section className="stepper">
      <div className="steps-container">
        <div className="steps-wrapper">{steps}</div>
        <div
          className="progress-line"
          style={{ width: `${progressLineWidth}%` }}
        ></div>
      </div>
    </section>
  );
};

export default Stepper;
.App {
  font-family: sans-serif;
  text-align: center;
}

.steps-container {
  position: relative;
}

.progress-line {
  position: absolute;
  height: 2px;
  background: gray;
  width: 0;
  top: 50%;
  left: 0;
  z-index: -1;
  transition: all 0.25s ease;
}

.steps-wrapper {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.steps {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background-color: gray;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: all 0.2s ease;
  cursor: pointer;
}

.steps.active {
  background-color: skyblue;
}

Add navigation to the steps

The most important part of this component is to add navigation. We are using a state to determine the current step.

 const [currentStep, setCurrentStep] = useState(0);

And when any of the steps we are going to update the current step.

 for (let i = 0; i < stepsCount; i++) {
    steps.push(
      <div
        onClick={() => setCurrentStep(i)}
        className={`steps ${currentStep >= i ? "active" : ""}`}
        key={i}
      >
        {i + 1}
      </div>
    );
  }

Also, we are going to have the two navigation methods to move to the next or the previous step.

const onPrev = () => {
    if (currentStep !== 0) {
      setCurrentStep(currentStep - 1);
    }
  };

  const onNext = () => {
    if (currentStep !== list.length - 1) {
      setCurrentStep(currentStep + 1);
    }
  };

These methods will be passed to the components in the list so that they can use them and navigate at will.

return (
    <section className="stepper">
      <div className="steps-container">
        <div className="steps-wrapper">{steps}</div>
        <div
          className="progress-line"
          style={{ width: `${progressLineWidth}%` }}
        ></div>
      </div>
      <div>{React.cloneElement(list[currentStep], { onPrev, onNext })}</div>
    </section>
  );

React.cloneElement extends the components that are passed as objects and pass the new props to them.

Complete code for multi-stepper component in React

// IT will accept a list of components
// based on the size of the list
// it will generate steps

import React, { useState } from "react";

const Stepper = ({ list }) => {
  const [currentStep, setCurrentStep] = useState(0);
  const stepsCount = list.length;
  const steps = [];

  for (let i = 0; i < stepsCount; i++) {
    steps.push(
      <div
        onClick={() => setCurrentStep(i)}
        className={`steps ${currentStep >= i ? "active" : ""}`}
        key={i}
      >
        {i + 1}
      </div>
    );
  }

  const progressLineWidth = (100 / (list.length - 1)) * currentStep;

  const onPrev = () => {
    if (currentStep !== 0) {
      setCurrentStep(currentStep - 1);
    }
  };

  const onNext = () => {
    if (currentStep !== list.length - 1) {
      setCurrentStep(currentStep + 1);
    }
  };

  return (
    <section className="stepper">
      <div className="steps-container">
        <div className="steps-wrapper">{steps}</div>
        <div
          className="progress-line"
          style={{ width: `${progressLineWidth}%` }}
        ></div>
      </div>
      <div>{React.cloneElement(list[currentStep], { onPrev, onNext })}</div>
    </section>
  );
};

export default Stepper;