Create progress bar in react

Learn how to create simple progress bar in react.

We will create simple progress bar which will show the progress in the range of 1 to 100.

our component will use some advance CSS techniques.

We will use few extra packages which will help us in creating an efficient component.

  • prop-types: To make sure we receive proper props.
  • classnames: With this package we can use CSS classes as javascript objects, but we will have to name our CSS file as filename.module.css.

Progress bar component structure.

Following is the folder structure of our component.
React progress bar folder structure

Our component will be dynamic which will display different sizes of the bar based on the props passed.

This is our full list of props that we expect in our component.

/* Custom color for progress bar */
color: PropTypes.string,
size: PropTypes.oneOf(["xs", "sm", "md", "lg", "xl"]).isRequired,
/* starts progress bar */
visible: PropTypes.bool,
/* provide a function to perform actions when progress reaches 100%  */
onCompleteHandler: PropTypes.func,
/* Any custom class to modify the appearance for future requ. */
className: PropTypes.string,
/* percentage to increment the progress bar */
percentage: PropTypes.number,
children: PropTypes.node

Now as you got better idea of how our component will behave by referring to these props, let us start creating it.

First import all the packages and set up the component.

import React, { PureComponent } from "react";
import cx from "classnames";
import PropTypes from "prop-types";
import styles from "./index.module.css";

class ProgressBar extends PureComponent {
   //other code will go here
}

export default ProgressBar;

We could have created a functional component but it would have kept updating the component even after the same percentage value is passed again and again which would be highly inefficient.

One way to optimize this is to check if the prev props and current is different then only update the component, but for this we had to create a class component and use shouldComponentUpdate().

The good thing about react is that it automatically does this when we create a pure component, which shallow checks the previous and current props and updates the component only when there is difference.

Now lets validate the props at the beginning before proceeding any further.

static propTypes = {
    /* Custom color for progress bar */
    color: PropTypes.string,
    size: PropTypes.oneOf(["xs", "sm", "md", "lg", "xl"]).isRequired,
    /* starts progress bar */
    visible: PropTypes.bool,
    /* provide a function to perform actions when progress reaches 100%  */
    onCompleteHandler: PropTypes.func,
    /* Any custom class to modify the appearance for future requ. */
    className: PropTypes.string,
    /* percentage to increment the progress bar */
    percentage: PropTypes.number,
    children: PropTypes.node
  };

  static defaultProps = {
    visible: false,
    size: "xs",
    className: "",
    progress: 0
  };

Our component is composed of two different parts

  • A wrapper which will contain the progress, we will keep its size dynamic which can be extended.
  • A progress bar inside the wrapper who’s width will be based on the percentage passed.
render() {
    const _class = cx(
      this.props.className,
      styles.progress,
      styles[this.props.size]
    );

    const _style = {
      ...(this.props.color && {
        backgroundColor: this.props.color
      }),
      width: `${this.props.percentage < 100 ? this.props.percentage : 100}%`
    };

    return (
      <div className={_class}>
        <div style={_style} className={styles.progress_bar}></div>
        {this.props.children}
      </div>
    );
  }

Now once our progress is completed we will need to notify the parent by calling the callback function onCompleteHandler we have received in our prop.

 componentDidUpdate() {
    const { percentage, onCompleteHandler } = this.props;
    if (percentage === 100) {
      onCompleteHandler();
    }
  }

Complete code of progress bar in react.

import React, { PureComponent } from "react";
import cx from "classnames";
import PropTypes from "prop-types";
import styles from "./index.module.css";

class ProgressBar extends PureComponent {
  static propTypes = {
    /* Custom color for progress bar */
    color: PropTypes.string,
    size: PropTypes.oneOf(["xs", "sm", "md", "lg", "xl"]).isRequired,
    /* starts progress bar */
    visible: PropTypes.bool,
    /* provide a function to perform actions when progress reaches 100%  */
    onCompleteHandler: PropTypes.func,
    /* Any custom class to modify the appearance for future requ. */
    className: PropTypes.string,
    /* percentage to increment the progress bar */
    percentage: PropTypes.number,
    children: PropTypes.node
  };

  static defaultProps = {
    visible: false,
    size: "xs",
    className: "",
    progress: 0
  };

  componentDidUpdate() {
    const { percentage, onCompleteHandler } = this.props;
    if (percentage === 100) {
      onCompleteHandler();
    }
  }

  render() {
    const _class = cx(
      this.props.className,
      styles.progress,
      styles[this.props.size]
    );

    const _style = {
      ...(this.props.color && {
        backgroundColor: this.props.color
      }),
      width: `${this.props.percentage < 100 ? this.props.percentage : 100}%`
    };

    return (
      <div className={_class}>
        <div style={_style} className={styles.progress_bar}></div>
        {this.props.children}
      </div>
    );
  }
}

export default ProgressBar;

Complete CSS code of our component.

We will be creating CSS variables in which we will be pre defining the size and then reuse this variable to create different sizes.

//index.module.css
:root {
  --base-size: 4px;
  --size-multiplier: 3;
}

.progress {
  background: #eee;
  position: relative;
  overflow: hidden;
  transition: width 0.3s ease-in;
  border-radius: 25px;
}

.progress .progress_bar {
  background: linear-gradient(45deg, #1d9241, #77de7b);
  position: absolute;
  top: 0;
  left: 0;
  height: 100%;
  will-change: width;
  transition: width 0.3s;
  border-radius: 25px;
}

.xs {
  height: var(--base-size);
}

.sm {
  height: calc(var(--base-size) * var(--size-multiplier));
}

.md {
  height: calc(2 * var(--base-size) * var(--size-multiplier));
}

.lg {
  height: calc(3 * var(--base-size) * var(--size-multiplier));
}

.xl {
  height: calc(4 * var(--base-size) * var(--size-multiplier));
}

Input

import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import ProgressBar from "./Components/ProgressBar";
import * as serviceWorker from "./serviceWorker";

ReactDOM.render(
  <div className="abc">
    <ProgressBar size="lg" visible={true} percentage={80} />
  </div>,
  document.getElementById("root")
);

Output

React progress bar