Skip to content
Advertisement

Progress circle bar in React JS

I am creating a progress circle bar to use as a timer along with sliders, where each slide will have its own bar

I could achieve it, however I couldn’t synchronize the bars with themselves.

This is what is happening:

enter image description here

As you see it is not working properly. I need the bar to fill 100% of the circle, then proceed to the next dot and repeat the process, however I couldn’t synchronize them.

If I only have one dot it seems to work fine, though: enter image description here

I am using two setTimeout, one to decrease the property stroke-dashoffset of circle through the controll of percentage, this one makes the bar to be filled; the second setTimeout is precisely to make the circle appears around the following dot. I have the variable timer which controls the interval of the changes and it is in the second setTimeout. I believe the problem may lie on the time between the two setTimeout, but it is just a guess.

I am working with hooks, however I couldn’t make it work that way on Codepen, because of that I created a codepen with a class component, but it is even worse, I don’t know why, since it follows the same logics.

https://codepen.io/WegisSilveira/pen/poyPVWq

At any rate, here is my code with hooks. The css is the same as the one on codepen:

import React, { Fragment } from 'react'

import './ProgressBar.css'


const ProgressBar = props => {

let [sqSize, setSqSize] = React.useState(30)
let [percentage, setPercentage] = React.useState(0)
let [strokeWidth, setStrokeWidth] = React.useState(3)

let [trigger, setTrigger] = React.useState(false)
let [barIndex, setBarIndex] = React.useState(0)

let bars = Array(props.bar).fill(1)
let timer = 3000


const barTriggerHandler = () => {
    setTrigger(!trigger)
}


if (trigger) {
    setTimeout(() => {
        percentage < 99 ? setPercentage(percentage + 1) : setPercentage(0)
    }, timer / 100);

    setTimeout(() => {
        console.log(percentage)
        barIndex < bars.length - 1 ? setBarIndex(barIndex + 1) : setBarIndex(0)
    }, timer);
    
}

// SVG centers the stroke width on the radius, subtract out so circle fits in square
const radius = (sqSize - strokeWidth) / 2;
// Enclose cicle in a circumscribing square
const viewBox = `0 0 ${sqSize} ${sqSize}`;
// Arc length at 100% coverage is the circle circumference
const dashArray = radius * Math.PI * 2;
// Scale 100% coverage overlay with the actual percent
const dashOffset = dashArray - dashArray * percentage / 100;
// console.log(dashOffset)

return (
    <Fragment>
        { bars.map((bar, i) => {
            return <svg
                        key={i}

                        width={sqSize}
                        height={sqSize}
                        viewBox={viewBox}

                        onClick={() => barTriggerHandler()}
                    >
                        { i === barIndex ?  
                            <Fragment>
                                <circle
                                    className="circle-progress"
                                    cx={sqSize / 2}
                                    cy={sqSize / 2}
                                    r={radius}
                                    strokeWidth={`${strokeWidth}px`}
                                    // Start progress marker at 12 O'Clock
                                    transform={`rotate(-90 ${sqSize / 2} ${sqSize / 2})`}
                                    style={{
                                        strokeDasharray: dashArray,
                                        strokeDashoffset: dashOffset
                                    }} 
                                /> 
                            </Fragment>
                        : null }
                        <circle
                            className="circle-center"
                            cx="50%"
                            cy="50%"
                            r="3"
                        /> 
                        
                    </svg>
        }) }
    </Fragment>
);
}

export default ProgressBar

I am using the elements svg and circle to create that bar, maybe the problem is here, since until yesterday I didn’t even know these tags and I am a little lost.

If anyone could help me I would appreciate a lot. This is driving me crazy already.

P.S. I took the example to create this bar from this pen: https://codepen.io/bbrady/pen/ozrjKE?editors=1010

Advertisement

Answer

Instead of having two setTimeout I am using only one. I removed the second one, which was used to add 1 to the index of the array containing the dots and put this step inside the first setTimeout, now rather then checking the time to make the adition I just check if the circle is 99% filled, if this is the case the circle go to the next dot.

This was the first logic:

if (trigger) {
    setTimeout(() => {
        percentage < 99 ? setPercentage(percentage + 1) : setPercentage(0)
    }, timer / 100);

    setTimeout(() => {
        barIndex < bars.length - 1 ? setBarIndex(barIndex + 1) : setBarIndex(0)
    }, timer);
    
}

Now I’m doing this:

if (trigger) {
    setTimeout(() => {
        percentage < 99 ? setPercentage(percentage + 1) : setPercentage(0)

        if (percentage === 99) {
             barIndex < bars.length - 1 ? setBarIndex(barIndex + 1) : setBarIndex(0)
        }

    }, timer / 100);        
}

Since both steps are inside the same setTimeout, there is no longer any interval conflict.

User contributions licensed under: CC BY-SA
3 People found this is helpful
Advertisement