1

I am making a simple react application and included react-stepper-horizontal library and things are fine.

Working Example:

Edit next-dynamic-testing-issue (forked)

Appropriate Code related to stepper:

const Form = () => {
.
.
.

const [currentPage, setCurrentPage] = useState(1);
const sections = [
  { title: 'Basic Details', onClick: () => setCurrentPage(1) },
  { title: 'Employment Details', onClick: () => setCurrentPage(2) },
  { title: 'Review', onClick: () => setCurrentPage(3) },
];
    
<Stepper
  steps={sections}
  activeStep={currentPage}
  activeColor="red"
  defaultBarColor="red"
  completeColor="green"
  completeBarColor="green"
/>

.
.
.
}

Steps to reproduce issue:

-> There are totally three steps 1,2,3 and each have different sections as Basic Details, Employment Details and Review respectively.

-> Now if user enter any of the input field in Step 1 and goes to Step 2 and fill some input fields there and goes to Step 3 to review it and if he comes back to the Step 1 again then the active state is lost in Step 3.

-> So now issue is if we want to go to step 3 then we need to again go three steps to reach last Step 3.

Requirement:

-> If user once visited any step then if he comes to any previous step then all the steps that he visited previously needs to be in active.

Eg:

-> If user landed in Step 1, then using next button , he reaches the Step 3 and if he wish to come back to Step 1 to modify some inputs and again if he wants to go to Step 3 for review step then it should be possible by clicking on the Step 3 because he already visited that step.

Kindly help me to achieve the result of making the steps in active state upto which the user visits.. If user visits Step 3 and goes back to step 1 on click of the Step 1 circle then there should be possibility to come back to Step 3 again as he already visited the Step 3..

Any solution without any library also welcomed.

This is a big issue if we have more steps (eg 7 steps). So please kindly help me.. A big thanks in advance..

Undefined
  • 851
  • 5
  • 20
  • 48
  • Looking at the [docs](https://www.npmjs.com/package/react-stepper-horizontal) it seems that what you want to achieve is not supported. Also the [library](https://github.com/mu29/react-stepper) is no longer developed. This is why it's so important to check the libraries you'll be using before you add that on your projects. – bertdida Sep 11 '20 at 08:56
  • @bertdida, Thanks for your comment bro.. Yes you are right and I have also searched for the same and end up with no positive result.. Do you have any idea of customizing the code to achieve the result with the help of click event listener on the steps? – Undefined Sep 11 '20 at 10:01
  • Why don’t you ditch that lib and implement it yourself? It seems quite easy to implement. Any blocking issue? – hackape Sep 29 '20 at 17:32

1 Answers1

0

Here's a simple implementation of the <Stepper /> component in question. The key is to have a tracker that tracks the visited steps internally, persist that information across re-renders.

Demoboard Playground

const { useState, useEffect, useMemo } = React;
const cx = classNames;

function range(a, b) {
  return new Array(Math.abs(a - b) + 1).fill(a).map((v, i) => v + i);
}

function Stepper({ steps, activeStep, children }) {
  const count = steps.length;
  const listOfNum = useMemo(() => range(1, count), [count]);
  const tracker = useMemo(() => {
    let highestStep = 0;

    function hasVisited(step) {
      return highestStep >= step;
    }

    function addToBackLog(step) {
      if (step > highestStep) highestStep = step;
    }

    return {
      hasVisited,
      addToBackLog,
      getHighestStep() {
        return highestStep;
      },
    };
  }, []);

  tracker.addToBackLog(activeStep);

  const noop = () => {};

  const prevStep = steps[activeStep - 2];
  const currentStep = steps[activeStep - 1];
  const nextStep = steps[activeStep];

  return (
    <div>
      <div>
        {" "}
        {listOfNum.map((num, i) => {
          const isActive = activeStep == num;
          const isVisited = tracker.hasVisited(num);
          const isClickable = num <= tracker.getHighestStep() + 1 || isVisited;
          return (
            <div
              key={num}
              className={cx("circle", {
                active: isActive,
                visited: isVisited,
                clickable: isClickable,
              })}
              onClick={isClickable ? steps[i].onClick : noop}
            >
              {num}{" "}
            </div>
          );
        })}{" "}
      </div>{" "}
      <h2> {currentStep && currentStep.title} </h2> <div> {children} </div>{" "}
      <div className="footer">
        {" "}
        {prevStep ? (
          <button onClick={prevStep.onClick}> prev </button>
        ) : null}{" "}
        {nextStep ? <button onClick={nextStep.onClick}> next </button> : null}{" "}
      </div>{" "}
    </div>
  );
}

function App() {
  const [currentPage, setCurrentPage] = useState(1);
  const sections = [
    {
      title: "Un",
      onClick: () => setCurrentPage(1),
    },
    {
      title: "Deux",
      onClick: () => setCurrentPage(2),
    },
    {
      title: "Trois",
      onClick: () => setCurrentPage(3),
    },
    {
      title: "Quatre",
      onClick: () => setCurrentPage(4),
    },
    {
      title: "Cinq",
      onClick: () => setCurrentPage(5),
    },
  ];

  return (
    <Stepper steps={sections} activeStep={currentPage}>
      I 'm page {currentPage}{" "}
    </Stepper>
  );
}

ReactDOM.render(<App />, document.getElementById("root"));
body {
  color: #0f0035;
  padding-bottom: 2rem;
}

.circle {
  display: inline-flex;
  height: 2em;
  width: 2em;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background-color: lightgrey;
  margin: 0 0.5em;
  color: white;
  cursor: not-allowed;
}

.active {
  background-color: rgba(50, 50, 250) !important;
}

.visited {
  background-color: rgba(50, 50, 250, 0.5);
}

.clickable {
  cursor: pointer;
}

.footer {
  margin-top: 1em;
  display: flex;
  justify-content: space-between;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/classnames/2.2.6/index.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
hackape
  • 18,643
  • 2
  • 29
  • 57
  • Thanks for your help.. Is it possible to add a horizontal line between each steps which indicates green for active steps and red for the next inactive steps? You can see the same in the sandbox which I have provided in the question.. – Undefined Sep 30 '20 at 08:43
  • I am about to award bounty to your answer.. Could you please help me in placing a horizontal line between steps with right indication of active and inactive steps as like the given codesandbox link in question? Thanks.. – Undefined Oct 06 '20 at 15:30
  • I have awarded you bounty but still requesting you to provide complete solution with lines in between each step circles.. – Undefined Oct 07 '20 at 13:51
  • Ah okay. Could you please show me what effort have you tried? Where you got stuck? – hackape Oct 07 '20 at 17:05
  • I can help you unstuck, but I refuse to just write the code for you. It’s not my obligation, not even with bounty award. I thought you don’t have a good idea about how to solve the react problem, so I show you the idea. That’s what you originally ask about. And now you request me to also do the css part. Well, this is not how things work on stack overflow. You must at least show some effort on your part, or ask a proper question, you don’t simply ask people to write code for you. – hackape Oct 07 '20 at 17:14