DEV Community

Rita Bradley
Rita Bradley

Posted on

Creating A Stepper Component with `useState`

Introduction

We've all seen a stepper before right? No, not the fitness machine that simulates walking up a flight of stairs. A stepper is a piece of UI that shows you where you are in a list of tasks, questions, steps, etc,. They provide a wizard-like workflow and can are sometimes used as navigation. They're also pretty easy to create with React and the useState hook.

Full disclosure, I didn't come up with the idea of this project on my own. I'm still refreshing my React knowledge with Jonas Schmedtmann's React course. This is a practice project from the course. Last week I wrote a post detailing how I created a developer profile card from the same course. If you're interested learning how I did it, be sure to check it out!

What I've been doing with these projects is pausing the video, and attempting to complete them on my own. I find this helps me find out what I already know while forcing me to challenge myself to figure out the things I don't. This is a recommended approach if you struggle with imposter syndrome like I do. Exploring your own solutions to tutorials is a great way to test and solidify your knowledge. Let's get started.

Prerequisites

In this post, I'm assuming you have a working knowledge of JavaScript and its array methods. I'm also assuming you know some React basics like JSX, how to create components, and how to pass props. In React, props (short for properties) are a way of passing data from parent components to child components. They make our components more versatile and reusable by allowing them to behave differently based on the input they receive. If you need a refresher on any of this, I recommended checking out the following resources:

Getting Started with the Step Progress Component

A screenshot of a step progress user interface component against a pink and purple radial gradient background

Let's dive in and create our own Step Progress Component with React and useState.

Setting up the Project

First, we need to set up a new React project. If you don't already have one, you can create a new one using Create React App.

npx create-react-app step-progress-component
Enter fullscreen mode Exit fullscreen mode

I normally use Vite for creating React projects, but since this tutorial called for CRA, I stuck with it.

Once you've created your React project, navigate to the project directory and start your local development server.

cd step-progress-component
npm start
Enter fullscreen mode Exit fullscreen mode

You should now have a new React project running on http://localhost:3000.

Remove boilerplate code

Create React App gives us a lot of code/files that we may not always need. In this case, we don't need App.css, App.test.js, reportWebVitals.js, setupTests.js, nor logo.svg. In index.js, remove and trace of reportWebVitals. In App.js remove the imports for App.css and logo.svg. Then remove everything between the first div, and delete className="App".

Jonas provided the CSS for this project, so we'll be replacing the code in index.css with that. All I added was the .active styling for buttons, and extra styling to the body to make it pretty.

* {
    box-sizing: border-box;
}

body {
    font-family: sans-serif;
    color: #333;
    min-height: 100vh;
    display: grid;
    place-content: center;
    background-image: radial-gradient(circle, #7950f2, #995ef5, #b36df8, #ca7dfb, #df8eff);
}

.steps {
    width: 600px;
    background-color: #f7f7f7;
    border-radius: 7px;
    padding: 25px 100px;
    margin: 100px auto;
}

.numbers {
    display: flex;
    justify-content: space-between;
}

.numbers > div {
    height: 40px;
    aspect-ratio: 1;
    background-color: #e7e7e7;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 18px;
}

.numbers .active,
button.active {
    background-color: #7950f2;
    color: #fff;
}

.message {
    text-align: center;
    font-size: 20px;
    margin: 40px 0;
    font-weight: bold;

    display: flex;
    flex-direction: column;
    align-items: center;
}

.buttons {
    display: flex;
    justify-content: space-between;
}

.buttons button {
    border: none;
    cursor: pointer;
    padding: 10px 15px;
    border-radius: 100px;
    font-size: 14px;
    font-weight: bold;

    display: flex;
    align-items: center;
    gap: 10px;
}

.buttons button span {
    font-size: 16px;
    line-height: 1;
}

h3 {
    margin: 0;
    text-transform: uppercase;
}
Enter fullscreen mode Exit fullscreen mode

Creating the Step component

Firstly, let's dive into our Step Component. This component accepts two props, styleClass and number.

export default function Step({ styleClass, number }) {
 return <div className={styleClass}>{number}</div>;
}
Enter fullscreen mode Exit fullscreen mode

The styleClass prop allows us to apply conditional styling to our component depending on what step is active, and the number prop passes a number to the component based on the current step.

This component is quite simple, but it plays a key role in our application by rendering each step in the progress bar.

Crafting the StepText Component

The StepText component is responsible for displaying the text associated with each step in our progress bar. We pass a single text prop to this component, which we then render within an h3 tag.

export default function StepText({ text }) {
 return <h3>{text}</h3>;
}
Enter fullscreen mode Exit fullscreen mode

This component is a fantastic example of the principle of "composition" in React, allowing us to keep our components small, focused, and reusable.

Button, button, who's got the button?

Next, we'll move onto the Button component. This component will render our 'Next' and 'Previous' buttons, and will handle user interactions to move between steps.

export default function Button({ children, onClick, styleClass }) {
  return (
    <button className={styleClass} onClick={onClick}>
      <span>{children}</span>
    </button>
  );
}
Enter fullscreen mode Exit fullscreen mode

The onClick prop that we pass to our button allows us to handle user clicks and thus manipulate the active step in our progress bar. The children prop allows us to pass the button text as children of the Button component.

Bringing it all together

In App.js, we need to import all three components.

I recommend placing individual components in a components folder in the src directory. This makes for better organization in the long run. Since this is a smaller project, we can get away with not doing this.

Adding Interactivity with useState

With our components ready, let's move on to creating an interactive stepper. For this, we'll be using React's useState hook. Hooks are a relatively new addition to React that allows us to use state and other React features in our functional components. useState is one of these hooks and it allows us to add state to our functional components. State can be thought of as a way to keep track of information in your application that can change over time and affect your app's behavior.

In our App.js, we'll start by importing useState from React and our three components: Step, StepText, and Button.

import { useState } from 'react';
import Step from './Step';
import StepText from './StepText';
import Button from './Button';
Enter fullscreen mode Exit fullscreen mode

Then, let's define the text for each step of our progress bar. We'll store this in an array called stepText.

const stepText = ['Learn React ⚛️', 'Apply to jobs 💼', 'Get a job 🎉'];
Enter fullscreen mode Exit fullscreen mode

Creating the State for Our Stepper

Next, we're going to create our step state. We initialize it to 1 since we want to start at the first step. We use setStep to change the state.

const [step, setStep] = useState(1);
Enter fullscreen mode Exit fullscreen mode

Handling Step Changes

After that, we'll create two functions handleNext and handlePrevious. These functions will allow us to go forward or backward in our progress bar when the next and previous buttons are clicked.

const handleNext = () => {
  if (step < stepText.length) {
    setStep(step + 1);
  }
};

const handlePrevious = () => {
  if (step > 1) {
    setStep(step - 1);
  }
};

Enter fullscreen mode Exit fullscreen mode

Rendering Our Components

Finally, we'll render our components. We create a div for each step and conditionally apply the 'active' class to the current step. We also render the text for the current step and the previous and next buttons, again applying the 'active' class conditionally.

return (
  <div className="steps">
    <div className="numbers">
      {stepText.map((_, index) => (
        <Step
          styleClass={step === index + 1 ? "active" : ""}
          key={`Step ${index + 1}`}
          number={index + 1}
        />
      ))}
    </div>
    <div className="message">
      <StepText text={stepText[step - 1]} />
    </div>

    <div className="buttons">
      <Button styleClass={step > 1 ? "active" : ""} onClick={handlePrevious}>
        Previous
      </Button>
      <Button
        styleClass={step < stepText.length ? "active" : ""}
        onClick={handleNext}
      >
        Next
      </Button>
    </div>
  </div>
);
Enter fullscreen mode Exit fullscreen mode

And that's it! We've built a functional Step Progress Component using React and the useState hook. The full code for App.js looks like this:

import { useState } from "react";
import Step from "./components/Step";
import StepText from "./components/StepText";
import Button from "./components/Button";

const stepText = ["Learn React ⚛️", "Apply to jobs 💼", "Get a job 🎉"];

function App() {
  const [step, setStep] = useState(1);

  const handleNext = () => {
    if (step < stepText.length) {
      setStep(step + 1);
    }
    console.log(step);
  };

  const handlePrevious = () => {
    if (step > 1) {
      setStep(step - 1);
    }
    console.log(step);
  };

  return (
    <div className="steps">
      <div className="numbers">
        {stepText.map((_, index) => (
          <Step
            styleClass={step === index + 1 ? "active" : ""}
            key={`Step ${index + 1}`}
            number={index + 1}
          />
        ))}
      </div>
      <div className="message">
        <StepText text={stepText[step - 1]} />
      </div>

      <div className="buttons">
        <Button styleClass={step > 1 ? "active" : ""} onClick={handlePrevious}>
          Previous
        </Button>
        <Button
          styleClass={step < stepText.length ? "active" : ""}
          onClick={handleNext}
        >
          Next
        </Button>
      </div>
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Conclusion

Learning React is all about understanding its core concepts, and useState is definitely one of them. By building this Step Progress Component, not only did we gain a better understanding of how useState works, but we also learned how to structure our code using components and how to pass props to make them more reusable. This is a great step forward in becoming a more proficient React developer. Keep practicing and you'll surely get there!

If you want to see the stepper in action, check out my pen!

Top comments (0)