I made a React progress bar AND SVG circle! #nailedIt

Houston Breedlove
4 min readJul 24, 2018

--

I was recently tasked with building a progress bar to allow the users of the application I work on to visually see the progress of uploading a file. I had never made one before but after learning how simple they are, I also took an additional step forward and attempted a progress circle using the same thought process. Below I’ll show you the basic idea of what I came up with.

I want to keep this as basic as possible so this is the super stripped down version of what I came up with:

react-progress.jsx

import React, { Component } from 'react';
import styles from './react-progress.css';
const ReactProgress = ({ percentage }) => {
return (
<div className='ReactProgress_wrapper'>
<div
className='ReactProgress_filler'
style={{ width: `${percentage}%` }}
/>
</div>
);
};
export default ReactProgress;

react-progress.css

.ReactProgress_wrapper {
background: #F5F5F5;
position: relative;
height: 0.5rem;
width: 100%;
}
.ReactProgress_filler {
background: #7FFFD4;
height: 100%;
transition: width 0.8s ease-in;
}

What’s going on?: Even if you’re new to React, it is pretty easy to see what is going here.

Think of ReactProgress_wrapper as the background AND housing unit to ReactProgress_filler. It’s getting a light grey background and is going to house the filler with the position: relative set. Also, we have the height set and the width will take up the length of whatever we decide to put it in later. 👍

ReactProgress_filler is going to be the part of the loader that will indicate progress to our user! For this example, I gave it a blue background. It will take up the height of the wrapper and smoothly transition to new percentages when updates happen.

The magic happens, however, from the passing of the percentage prop on the ReactProgress component. Every time a new percentage is passed to the ReactProgress component, the percentage prop will be passed to the ReactProgress_filler div’s width!

As you can tell, this was a pretty quick and easy learn so I thought I’d go a step further and try to create a progress circle with my new knowledge!

react-progress-circle.jsx

import React, { Component } from 'react';
import styles from './react-progress-circle.css';
const ReactProgressCircle = ({ percentage, size }) => {
let appliedRadius;
let appliedStroke;
switch (size) {
case 'xs':
appliedRadius = 10;
appliedStroke = 1;
break;
case 'sm':
appliedRadius = 25;
appliedStroke = 2.5;
break;
case 'med':
appliedRadius = 50;
appliedStroke = 5;
break;
case 'lg':
appliedRadius = 75;
appliedStroke = 7.5;
break;
case 'xl':
appliedRadius = 100;
appliedStroke = 10;
break;
default:
appliedRadius = 50;
appliedStroke = 5;
}
const normalizedRadius = appliedRadius - appliedStroke * 2;
const circumference = normalizedRadius * 2 * Math.PI;
const strokeDashoffset =
circumference - (percentage / 100) * circumference;
return (
<div id="react-progress-circle">
<svg height={appliedRadius * 2} width={appliedRadius * 2}>
<circle
className='ReactProgressCircle_circleBackground'
strokeWidth={appliedStroke}
style={{ strokeDashoffset }}
r={normalizedRadius}
cx={appliedRadius}
cy={appliedRadius}
/>
<circle
className='ReactProgressCircle_circle'
strokeWidth={appliedStroke}
strokeDasharray={circumference + ' ' + circumference}
style={{ strokeDashoffset }}
r={normalizedRadius}
cx={appliedRadius}
cy={appliedRadius}
/>
</svg>
</div>
);
};
export default ReactProgressCircle;

react-progress-circle.css

.ReactProgressCircle_circle {
fill: transparent;
stroke: #7FFFD4;
}
.ReactProgressCircle_circleBackground {
fill: transparent;
stroke: #F5F5F5;
transition: stroke-dashoffset 0.8s;
transform: rotate(-90deg);
transform-origin: 50% 50%;
}

What’s going on?:

The basics of what was discussed on the regular loading bar are the same here. The overlying concept is in the two SVG <circle /> elements. We have one, underlying circle that is basically the background for the other one.

I’m not going to get too in depth on what the attributes to the circle each do but basically what is happening is I’ve provided a size prop for the component user to take advantage of and not have to set radius and stroke on their own. After selecting either “xs”, “sm”, “med”, “lg”, or “xl”, the circumference of the circle is set.

(Totally had to look up how to get the circumference of a circle btw…) 🙃

The strokeDashoffset is what sets the percentage that will be recognizable by the user.

Where I’m going to continue from here:

Feels like it should be pretty easy by this point to make an animated loading spinner right?!

RIGHT!

--

--