React JS progress circle bar is a simple and effective way to visualize

Currently, I am in the process of developing a progress circle bar that will function as a timer alongside sliders. Each slide is intended to have its own corresponding progress bar.

While I have managed to create the bars individually, I am facing challenges with synchronizing them effectively.

The issue is clearly illustrated by the following GIF:

As evident from the demonstration, the synchronization is not functioning as intended. My goal is for each bar to fill 100% of the circle, then transition to the next dot and repeat the cycle seamlessly. However, achieving this seamless synchronization has proven to be quite difficult.

In scenarios where only one dot is present, the functionality appears to work correctly, as shown here:

To provide some insight into my approach, I am utilizing two setTimeout functions. One is responsible for decreasing the 'stroke-dashoffset' property of the 'circle' element through the manipulation of 'percentage', thereby filling the bar. The second setTimeout function is specifically designed to display the circle surrounding the subsequent dot. A variable named 'timer' regulates the timing of these changes and is embedded within the second setTimeout function. While I suspect that the issue may stem from the time interval between the two setTimeout functions, this is merely speculation on my part.

Although I initially attempted to implement hooks, I encountered difficulties when attempting to replicate the functionality on Codepen. Consequently, I resorted to creating a code snippet featuring a class component; however, the performance was even more erratic than expected. The underlying reasons for this discrepancy remain unclear to me, particularly since both implementations adhere to similar principles.

For reference, you can view the class component code snippet on Codepen via the following link: https://codepen.io/WegisSilveira/pen/poyPVWq.

Subsequently, provided below is my code incorporating hooks. The CSS remains consistent with the styling utilized in the aforementioned Codepen sample:

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

Given that the progress circle embellishment relies heavily on the utilization of 'svg' and 'circle' elements, it is plausible that the problem stems from this particular setup. Prior to embarking on this endeavor, I had limited familiarity with these tags, which has inevitably contributed to my current predicament.

If anyone possesses the expertise necessary to assist me in resolving this dilemma, your guidance would be immensely appreciated. At this juncture, this challenge is causing considerable frustration, and any assistance rendered would be invaluable.

P.S. The inspiration for creating this form of progress bar was drawn from the pen available at the following link: https://codepen.io/bbrady/pen/ozrjKE?editors=1010

Answer №1

By consolidating two separate setTimeout functions into one, I have streamlined the process. The unnecessary second timeout, which previously incremented a value in an array of dots, has been eliminated. Now, within the single setTimeout, I increment the percentage filled in the circle and check if it has reached 99%. If so, the circle moves to the next dot.

Initially, the logic looked like this:

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

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

The revised approach is as follows:

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

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

    }, timer / 100);        
}

Combining both steps within the same setTimeout resolves any interval conflicts that may have arisen previously.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Enhance your website with a dynamic jQuery gallery featuring stunning zoom-in

I am currently working on a mobile website project and I'm in need of a gallery feature that allows users to zoom in on images and swipe through them using touch gestures. After some research, I haven't been able to find a suitable solution in j ...

Looking for an API to retrieve random quotes and images from a website?

Greetings, my name is Antika. I recently embarked on a coding journey and have been focusing on learning HTML/CSS along with the basics of JS. Level of Expertise: Beginner During my exploration, I stumbled upon this intriguing website - . It stands out a ...

The appropriate method for transferring a prototype to an object

From my perspective, prototypes work like this: let Animal = function() { this.bark = "woof"; } Animal.prototype.barkLoud = function() { return this.bark.toUpperCase(); } let x = new Animal(); x.barkLoud() = "WOOF"; I f ...

Height specification in Tailwind Grid

Hi there! I'm currently working on implementing a grid system in NextJS using Tailwind CSS. However, I've hit a roadblock when it comes to automatically sizing the grids to fit the parent element. Let me illustrate my issue with two images below ...

Different Approaches to Authentication in Next.js

I've been working on establishing a secure authentication process for my Next.js project, but I'm feeling quite lost at the moment. Despite referencing the examples in the Next.js repository, I still have numerous queries regarding a comprehensiv ...

The Recharts Line chart fails to display newly added data points when data is updated

My app features a straightforward tool that enables users to monitor their weight changes over time. Despite successfully receiving new data in the props, the chart does not update and display the new point. Recharts Component: import React from 'rea ...

What sets Observables (Rx.js) apart from ES2015 generators?

From what I've gathered, there are various techniques used for solving asynchronous programming workflows: Callbacks (CSP) Promises Newer methods include: Rx.js Observables (or mostjs, bacon.js, xstream etc) ES6 generators Async/Await The trend ...

How come my keyframes animation isn't functioning properly on my div element?

I am currently designing a custom animation for a checkers game. My goal is to create an effect where the checker piece moves slowly to its next spot on the board. Below is the CSS code I have used: @keyframes animateCheckerPiece { 0% {top: ...

Issues with click events in the navigation menu

How can I make my menu close when clicking on other parts of my website, instead of opening? I know that I should use a click event for this, but when I implemented a click event, my menu encountered 2 unwanted problems: 1- Whenever I clicked on a menu i ...

CKEditor is removing tags and attributes when formatting HTML emails

I have developed an application that enables users to import HTML emails and make adjustments before forwarding them. I am attempting to utilize CKEditor for modifying the imported email; however, it appears to be removing bgcolor tags (and possibly more). ...

Unable to run a Bash script using PHP

I'm currently facing a challenge while attempting to run a simple Bash Script from within a PHP script. The process involves collecting data from an interactive HTML5 front-end page, transmitting it through ajax to the PHP script, parsing the variable ...

Issue encountered while running the command "npm start" on a recently generated project

I encountered an issue after creating a new ReactJS project and running "npm start" to launch it. This error has persisted across all my ReactJS projects, prompting me to create a new project to confirm that the error is not specific to one project. Here ...

Tips for invoking a reduce mechanism within a separate function

I need assistance with implementing a reduce function: users.reduce(function (acc, obj) { return acc + obj.age/3; }, 0); within the following function structure: function calculateUserAverageAge(users) {}; To analyze this array of objects and calculate ...

Discovering the process to create an onclick function with node.js code

index.html: <html> <h2>Welcome To User Profile Page !!!</h2> <button type="button">Edit Profile</button> <button type="button">Logout</button> </html> On my webpage, I have two buttons - Edit Profile and Lo ...

Error encountered: Unexpected syntax error found in jQuery ajax call

I am attempting to send a simple request to Instagram using the code snippet below: $.getJSON("https://www.instagram.com/kidsfromthe90sband/media/?callback=?", function(data) { alert(JSON.stringify(data)); }); http://jsfiddle.net/FPhcr/731/ ...

EaselJS: Enhancing Performance and Aesthetics with Over 200 Vector Shapes

When comparing EaselJS performance with Canvas native methods, I noticed a significant difference: 2.2 s vs 0.01 s (despite EaselJS mapping canvas native methods). I created a canvas application that draws a tree*. For animating the growth of the tree, us ...

The Ajax function fails to trigger during the first load of the page

Note: Kindly refer to the update at the end of this question before proceeding. The problem described is specific to IE 11 and emerged after a recent Windows update. Following the installation of 5 updates, including one for IE, I removed the latter hopin ...

When creating routes in Express 4.* using node.js, it is essential to use the root directory

Completely new to the world of node.js, I find myself navigating through an outdated and partially functioning course on udemy.com. In previous modules, I managed to successfully receive content through routes like app.get('/vegetables',functio ...

Is it not recommended to trigger the 'focusout' event before the anchor element triggers the 'click' event?

In a unique scenario, I've encountered an issue where an anchor triggers the 'click' event before the input field, causing it to lose focus and fire the 'focusout' event. Specifically, when writing something in the input field and ...

"Receiving the error message 'Object is not a function' occurs when attempting to pass a Node.js HTTP server object to Socket.IO

A few months back, everything was working fine when I set up an HTTPS server. Recently, I revisited the application and decided to switch to HTTP (though this change may not be directly related). In my code snippet below from 'init.js', I create ...