How to build an SVG circular progress component using React and React Hooks
Tags: react, tutorial
Published: 2020-05-04 18:10:05

---
title: How to build an SVG circular progress component using React and React Hooks
published: true
date: 2020-04-08 17:30:46 UTC
tags: react,tutorial
canonical_url: https://blog.logrocket.com/how-to-build-an-svg-circular-progress-component-using-react-and-react-hooks/
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/hmxm14e9d67e4xi1i7g5.png
---
**Written by [Uzochukwu Eddie Odozi](https://blog.logrocket.com/author/uzochukwuodozi/)**✏️

Progress bars are used to indicate activities like file uploads and downloads, page loading, user counts, and more on desktop or mobile devices. This visual representation can go a long way toward enhancing the user experience of your app.

In this tutorial, we’ll demonstrate how to create a simple, customizable, easy-to-use circular progress bar component from Scalable Vector Graphics (SVGs) using React. We’ll do so using no external dependencies.

Here’s what the circular progress component will look like:

![Circular Progress Component](https://i1.wp.com/blog.logrocket.com/wp-content/uploads/2020/04/circular-progress-component-complete.gif?resize=720%2C404&ssl=1)

You can reference the full source code for this tutorial in the [GitHub repo](https://github.com/uzochukwueddie/react-svg-circular-progress).

Let’s dive in!

[![LogRocket Free Trial Banner](https://i0.wp.com/blog.logrocket.com/wp-content/uploads/2017/03/f760c-1gpjapknnuyhu8esa3z0jga.png?resize=1200%2C280&ssl=1)](https://logrocket.com/signup/)

## Getting started

Before we start, we must first create a React application. We’ll use `create-react-app` with npx to create our app. I’ll assume you have Node.js installed on your computer.

Open a terminal or command prompt, navigate to the directory where you want to add your project and type the following command.

`npx create-react-app react-progress-bar`

You can open the project with any IDE of your choice.

`create-react-app` creates a `src` directory. This is the directory that contains the entry component (`App.js`) of our application and where other components will be created. Delete the contents of the `index.css` file and add:

```jsx
body {
  margin: 0;
}
```

In the `App.css` file, delete all CSS styles except for the classes `App` and `App-header`. You can change both class names to be lowercase. Inside the `App.js` component file, delete the contents of the header element and change it to a div.

```jsx
<div className="app">
    <div className="app-header">
    </div>
</div>
```

`create-react-app` creates the component inside `App.js` as a functional component. You can use the default definition of the function or change it to an arrow function.

## Progress component setup

To create a progress component, create a folder called `progress` and add two files `ProgressBar.js` and `ProgressBar.css`. Inside the `ProgressBar.js` file, create an arrow function `ProgressBar` and export function as default. Set the parent element to `Fragment` (import from React) or empty tags.

### Basic SVG

Scalable Vector Graphics (SCGs) are used to define vector-based graphics for the web, according to <ins><a href="https://www.w3schools.com/graphics/svg_intro.asp" target="_blank" rel="noopener noreferrer">W3 Schools</a></ins>.

The first element to add to the progress bar component is the `<svg>` element tag, which defines a container for a coordinate system and viewport.

```jsx
import React from 'react';
import './ProgressBar.css';
const ProgressBar = () => {
    return (
        <>
            <svg>

            </svg>
        </>
    );
}
export default ProgressBar;
```

The `svg` element can accept numerous attributes; we’ll add `width` and `height`. The width and height of the SVG container will be dynamic, so we’ll add both as props.

```jsx
return (
    <>
        <svg className="svg" width={} height={}>

        </svg>
    </>
);
```

Inside the added `<svg>` element, place a `<circle>` tag to create a circle. In the `<circle>` element, declare the radius `r` of the circle and the x-coordinate (`cx`) and y-coordinate (`cy`) of its center.

Additionally, we’ll define the stroke (color) and stroke width of the circle. I’ll define two separate `<circle>` elements:

```jsx
<svg className="svg" width={} height={}>
    <circle
        className="svg-circle-bg"
        stroke={}
        cx={}
        cy={}
        r={}
        strokeWidth={}
    />
    <circle
        className="svg-circle"
        stroke={}
        cx={}
        cy={}
        r={}
        strokeWidth={}
    />
</svg>
```

The first circle element displays the inner circle while the second is placed on top of the first element to display the progress color based on the percentage calculated.

Next, add a `<text></text>` element, which draws a graphics element consisting of text. We’ll also add the attributes `x` and `y`, which represent the x and y starting points of the text.

```jsx
<svg className="svg" width={} height={}>
    ...
    ...
    <text className="svg-circle-text" x={}  y={}>
        ...
    </text>
</svg>
```

Add the below CSS styles into the `ProgressBar.css` file and import it into the component.

```jsx
.svg {
    display: block;
    margin: 20px auto;
    max-width: 100%;
}

.svg-circle-bg {
    fill: none;
}

.svg-circle {
    fill: none;
}
.svg-circle-text {
   font-size: 2rem;
    text-anchor: middle;
    fill: #fff;
    font-weight: bold;
}
```

As you can see, we don’t have much in the way of CSS styles. The progress bar elements will contain properties that will add some styles to the elements. Let’s take a closer look.

## Progress component props

The progress bar component takes in five props:

1. `size` — the full width and height of the SVG
2. `progress` — the circular progress value
3. `strokeWidth` — the width (thickness) of the circles
4. `circleOneStroke` — the stroke color of first circle
5. `circleTwoStroke` — the stroke color of the second circle

These properties will be passed in as props into the circular progress component when it is used. Other properties, such as `radius` and `circumference`, are calculated from the provided props.

Pass a props property into the arrow function and destructure the five properties.

```jsx
const ProgressBar = (props) => {
    const { 
        size, 
        progress, 
        strokeWidth, 
        circleOneStroke, 
        circleTwoStroke,
    } = props;
    ...
}
```

Next, calculate the radius and circumference of the circles. Add a new variable called `center` and set its value to half of the size passed in as props. This value will be used in the `cx` and `cy` coordinates of the center of circle.

```jsx
const center = size / 2;
```

The radius of the path is defined to be in the middle, so for the path to fit perfectly inside the viewBox, we need to subtract half the `strokeWidth` from half the size (diameter). The circumference of the circle is `2 * π * r`.

```jsx
const radius = size / 2 - strokeWidth / 2;
const circumference = 2 * Math.PI * radius;
```

Add the props and radius to the SVG and circles.

```jsx
<svg className="svg" width={size} height={size}>
    <circle
        className="svg-circle-bg"
        stroke={circleOneStroke}
        cx={center}
        cy={center}
        r={radius}
        strokeWidth={strokeWidth}
    />
    <circle
        className="svg-circle"
        stroke={circleTwoStroke}
        cx={center}
        cy={center}
        r={radius}
        strokeWidth={strokeWidth}
    />
    <text className="svg-circle-text" x={center}  y={center}>
        {progress}%
    </text>
</svg>
```

Go to the `App.js` file and import the `ProgressBar` component. Add the component inside the div element with class name `app-header`.

```jsx
const App = () => {
    return (
        <div className="app">
            <div className="app-header">
                <ProgressBar />
            </div>
        </div>
    );
}
```

Back to the `ProgressBar.js` file. The `ProgressBar` component needs to have the props defined inside its component.

```jsx
<ProgressBar 
    progress={50}
    size={500}
    strokeWidth={15}
    circleOneStroke='#7ea9e1'
    circleTwoStroke='#7ea9e1'
/>
```

The user can specify values for the properties. Later, the progress value will be updated from a button click and an input. The `circleTwoStroke` value will be randomly selected from an array of colors.

![Circular Progress Component Strokes](https://i2.wp.com/blog.logrocket.com/wp-content/uploads/2020/04/circular-progress-component-strokes.png?resize=720%2C378&ssl=1)

When using SVG, there are ways to control how strokes are rendered. Let’s take a look at `stroke-dasharray` and `stroke-dashoffset`.

`stroke-dasharray` enables you to control the length of the dash and the spacing between each dash. Basically, it defines the pattern of dashes and gaps that is used to paint the outline of the shape — in this case, the circles.

Rather than creating multiple dashes, we can create one big dash to go around the entire circle. We’ll do this using the circumference we calculated earlier. The `stroke-dashoffset` will determine the position from where the rendering starts.

The second circle will display the progress value between 0 and 100. Add the property below to the second circle

```jsx
strokeDasharray={circumference}
```

Notice that we are using `strokeDasharray` and not `stroke-dasharray`. In react, css properties that are separated by `-` are usually written in camelCase when used inside the component.

```jsx
...
<circle
    className="svg-circle"
    stroke={circleTwoStroke}
    cx={center}
    cy={center}
    r={radius}
    strokeWidth={strokeWidth}
    strokeDasharray={circumference}
/>
...
```

We’ll use three different React hooks: `useState`, `useEffect`, and `useRef`. `useState` updates the `stroke-dashoffset` based on the progress value passed as a prop and inside the  
`useEffect` hook. The `useRef` hook will be used to get a reference to the second circle and then  
add a CSS transition property to the circle.

Import the `useState`, `useEffect`, and `useRef` hooks from React.

```jsx
import React, { useEffect, useState, useRef } from 'react';
```

Create a new `useState` property inside the arrow function and set its default value to zero.

```jsx
const [offset, setOffset] = useState(0);
```

On the second circle, add a `ref` property and then create a new variable after the `useState` property.

```jsx
...
<circle
    ...
    ref={circleRef}
    ...
/>
...
```

```jsx
const circleRef = useRef(null);
```

The `circleRef` property will produce a reference to the second circle, and then we can update its style on the DOM.

Next, add a `useEffect` method

```jsx
useEffect(() => {

}, []);
```

Inside the `useEffect` hook, calculate the position of the progress by using this formula:

```jsx
((100 - progress) / 100) * circumference;
```

Recall that the circumference is already calculated and the progress is a prop value set by the user.

```jsx
useEffect(() => {
    const progressOffset = ((100 - progress) / 100) * circumference;
    setOffset(progressOffset);
}, [setOffset, circumference, progress, offset]);
```

The properties inside the array are dependencies and so must be added to the useEffect array.  
After calculating the progressOffset, the `setOffset` method is used to update the `offset`.

Add to the second circle:

```jsx
...
<circle
    ...
    strokeDashoffset={offset}
    ...
/>
...
```

It should look like the screenshots below.

**70 percent progress:**

![Circular Progress Component Showing 70 Percent Progress](https://i0.wp.com/blog.logrocket.com/wp-content/uploads/2020/04/circular-progress-component-70-percent.png?resize=720%2C378&ssl=1)

**30 percent progress:**

![Circular Progress Component Showing 30 Percent Progress](https://i2.wp.com/blog.logrocket.com/wp-content/uploads/2020/04/circular-progress-component-30-percent.png?resize=720%2C378&ssl=1)

To add some transition to the `stroke-dashoffset`, we’ll use `useRef`, which has been defined. The `useRef` hook gives us access to the `current` property of the element on the DOM, which enables us to access the style property. We’ll place this transition inside the `useEffect` hook so that it will be rendered as soon as the progress value changes.

Below the `setOffset` method and inside the `useEffect` hook, add:

```jsx
circleRef.current.style = 'transition: stroke-dashoffset 850ms ease-in-out;';
```

`circleRef` is the variable defined for `useRef`, and we have access to its current and style properties. To see the change, reload your browser and observe how the transition occurs.

We now have our progress bar component. Let’s add some prop-types to the component.

```jsx
import PropTypes from 'prop-types';
```

This places the prop-types definition right before the export default.

```jsx
ProgressBar.propTypes = {
    size: PropTypes.number.isRequired,
    progress: PropTypes.number.isRequired,
    strokeWidth: PropTypes.number.isRequired,
    circleOneStroke: PropTypes.string.isRequired,
    circleTwoStroke: PropTypes.string.isRequired
}
```

These properties are defined as required properties. You can add more properties to the component if you wish.

Your `ProgressBar` functional component should look like this:

```jsx
import React, { useEffect, useState, useRef } from 'react';

import PropTypes from 'prop-types';
import './ProgressBar.css';

const ProgressBar = props => {
    const [offset, setOffset] = useState(0);
    const circleRef = useRef(null);
    const { 
        size, 
        progress, 
        strokeWidth, 
        circleOneStroke, 
        circleTwoStroke,
    } = props;

    const center = size / 2;
    const radius = size / 2 - strokeWidth / 2;
    const circumference = 2 * Math.PI * radius;

    useEffect(() => {
        const progressOffset = ((100 - progress) / 100) * circumference;
        setOffset(progressOffset);
        circleRef.current.style = 'transition: stroke-dashoffset 850ms ease-in-out;';
    }, [setOffset, circumference, progress, offset]);

    return (
        <>
            <svg
                className="svg"
                width={size}
                height={size}
            >
                <circle
                    className="svg-circle-bg"
                    stroke={circleOneStroke}
                    cx={center}
                    cy={center}
                    r={radius}
                    strokeWidth={strokeWidth}
                />
                <circle
                    className="svg-circle"
                    ref={circleRef}
                    stroke={circleTwoStroke}
                    cx={center}
                    cy={center}
                    r={radius}
                    strokeWidth={strokeWidth}
                    strokeDasharray={circumference}
                    strokeDashoffset={offset}
                />
                <text 
                    x={`${center}`} 
                    y={`${center}`} 
                    className="svg-circle-text">
                        {progress}%
                </text>
            </svg>
        </>
    )
}

ProgressBar.propTypes = {
    size: PropTypes.number.isRequired,
    progress: PropTypes.number.isRequired,
    strokeWidth: PropTypes.number.isRequired,
    circleOneStroke: PropTypes.string.isRequired,
    circleTwoStroke: PropTypes.string.isRequired
}

export default ProgressBar;
```

## Generate random progress values

To see the transition applied to the progress, we’ll create an input field to enable the user to change progress values and a button to add random progress values.

Start by adding the below CSS styles to the `App.css` file.

```jsx
button {
  background: #428BCA;
  color: #fff;
  font-size: 20px;
  height: 60px;
  width: 150px;
  line-height: 60px;
  margin: 25px 25px;
  text-align: center;
  outline: none;
}

input { 
  border: 1px solid #666; 
  background: #333; 
  color: #fff !important; 
  height: 30px;
  width: 200px;
  outline: none !important; 
  text-align: center;
  font-size: 16px;
  font-weight: bold;
}

input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

input[type=number] {
  -moz-appearance: textfield;
}

h1 { 
  margin: 0;
  text-transform: uppercase;
  text-shadow: 0 0 0.5em #fff;
  font-size: 46px;
  margin-bottom: 20px;
}
```

The styles are basic for button, input, and h1 elements. Next, add some elements to the div with class name `app-header`.

```jsx
<h1>SVG Circle Progress</h1>
<ProgressBar 
    progress={50}
    size={500}
    strokeWidth={15}
    circleOneStroke='#7ea9e1'
    circleTwoStroke='#7ea9e1'
/>
<p>
    <input 
        type="number"
        name="percent" 
        placeholder="Add Progress Value"
        onChange={}
    />
</p>
<button>
    Random
</button>
```

This adds s `header` tag, `p` tag with input, and a button. Let’s add the `onChange method` to the input.

```jsx
...
...
<p>
    <input 
        type="number"
        name="percent" 
        placeholder="Add Progress Value"
        onChange={onChange}
    />
</p>
...
```

```jsx
const onChange = e => {

}
```

Inside the `onChange` method, the progress value and a random color will be selected and their  
properties updated. Import `useState` and create a `useState` property called `progress`.

```jsx
const [progress, setProgress] = useState(0);
```

Create a `useState` color property.

```jsx
const [color, setColor] = useState('');
```

Add an array of colors with hex codes. You can set whatever colors you wish.

```jsx
const colorArray = ['#7ea9e1', "#ed004f", "#00fcf0", "#d2fc00", "#7bff00", "#fa6900"];
```

A random color will be selected from the array and displayed on the circular progress component.

Update the `ProgressBar` component with the `progress` and `color` props.

```jsx
<ProgressBar 
    progress={progress}
    size={500}
    strokeWidth={15}
    circleOneStroke='#7ea9e1'
    circleTwoStroke={color}
/>
```

Add a method that gets a random color from the `colorArray`.

```jsx
const randomColor = () => {
    return colorArray[Math.floor(Math.random() * colorArray.length)];
}
```

Set the maximum value for the progress component to 100 and the minimum value to 0. If the  
input value is less than zero, the progress is set to zero. If it’s greater than 100,  
the progress is set to 100.

```jsx
if (e.target.value) {
    if (e.target.value > 100) {
        progress = 100;
    }
    if (e.target.value < 0) {
        progress = 0;
    }
    setProgress(progress);
}
```

The `setProgress` method will update the progress value. Add the `randomColor` method below the `setProgress` and update the color variable using `setColor`.

```jsx
...
const randomProgressColor = randomColor();
setColor(randomProgressColor);
```

If you try this out, you’ll discover it works but if the input field is empty, it still retains some old  
value. This is not the behavior we want. To fix this, I’ll add an else statement inside the `onChange` and set the progress value to zero.

```jsx
if (e.target.value) {
    ...
} else {
    setProgress(0);
}
```

This will set the progress value to zero whenever the input field is cleared or empty.

![Circular Progress Component With Random Values](https://i2.wp.com/blog.logrocket.com/wp-content/uploads/2020/04/circular-progress-component-random-values.gif?resize=720%2C404&ssl=1)

### Random button functionality

Add an `onClick` method on the button and create a function to randomly set the progress value.

```jsx
<button onClick={randomProgressValue}>
    Random
</button>
```

Create a method called `randomProgressValue`.

```jsx
const randomProgressValue = () => {
}
```

First, use `Math.random()` to get a random value between 0 and 100 and set its value with the `setProgress` method. The `randomColor` method is called and the color value is updated.

```jsx
const randomProgressValue = () => {
    const progressValue = Math.floor(Math.random() * 101);
    setProgress(progressValue);
    const randomProgressColor = randomColor();
    setColor(randomProgressColor);
}
```

Whenever the button is clicked, a random progress value is set and a random color is added using the `setColor` method.

Note that using a random colors array is optional. You can set any two colors you want for the `circleOneStroke` and `circleTwoStroke` props.

![Circular Progress Component With Random Button Functionality](https://i1.wp.com/blog.logrocket.com/wp-content/uploads/2020/04/circular-progress-component-random-button.gif?resize=720%2C404&ssl=1)

## Conclusion

You should now have a good understanding of how to create a custom circular progress bar using React hooks such as `useState`, `useEffect`, and `useRef`.

See the full source code for this tutorial in the [GitHub repo](https://github.com/uzochukwueddie/react-svg-circular-progress).

If you prefer to watch me as I code, you can check out this <ins><a href="https://www.youtube.com/watch?v=W0pYJms1MCw&amp;t=1s" target="_blank" rel="noopener noreferrer">YouTube video here.</a></ins>

* * * 
## Full visibility into production React apps 

Debugging React applications can be difficult, especially when users experience issues that are difficult to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, [try LogRocket.](https://www2.logrocket.com/react-performance-monitoring)

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/eq752g8qhbffxt3hp9t4.png)

[LogRocket](https://www2.logrocket.com/react-performance-monitoring) is like a DVR for web apps, recording literally everything that happens on your React app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your React apps — [start monitoring for free.](https://www2.logrocket.com/react-performance-monitoring)
* * *

The post [How to build an SVG circular progress component using React and React Hooks](https://blog.logrocket.com/how-to-build-an-svg-circular-progress-component-using-react-and-react-hooks/) appeared first on [LogRocket Blog](https://blog.logrocket.com).