Arslan Tariq
Arslan Tariq

Reputation: 2528

A single state for two progress-bar components in Reactjs

I have created a generic component for showing progress. It takes a prop 'type' to render the type of progress. The types are 'bar' progress and 'circular' progress. The bar progress displays and when i click an Accordion shows circular progress like this:

both progress bars

What I want is that if I click pause on any progress(bar or circular), both the progress should stop. Here is the code for this generic progress component:

import React, {Component} from 'react';
import CircularProgressBar from 'react-circular-progressbar';
import config from '../../config';
import './Progress.css';
import './ProgressCircular.css';

class GenericProgress extends Component {

    constructor(props) {
        super(props);
        this.state = {
            progressPercent: props.progress,
            width: "100%",
            startTime: props.startTime,
            progressStatus: props.status,
            extractId: props.extractId,
        };

        this.tick=this.tick.bind(this);
    }

    tick() {
        const reqObj={
            "op": "progress",
            "extractID" : this.props.extractId,
            "last_ts" : this.state.last_ts,
            "progress": this.state.progressPercent,
        };
        fetch(`${config.apiHost}/extracttool/extract`,
            {
                method: 'POST',
                body: JSON.stringify(reqObj),
                headers: {
                    'Content-Type': 'application/json'
                }
            }
        ).then((response) => {
            return response.json();
        }).then((data) => {
            if(this.state.progressStatus !== 'Paused' ) {
                const progressCounter = data.payload.progress;
                const last_ts = data.payload.last_ts;
                if (progressCounter >= 100) {
                    this.props.changeExecutionStatus('Complete');
                    this.setState({...this.state, progressPercent: 100, progressStatus: 'Complete'});
                    clearInterval(this.timerID);
                } else {
                    this.setState({
                        ...this.state,
                        progressPercent: progressCounter,
                        last_ts: last_ts
                    });
                }
            }
        });
    }

    callApi = (reqObj, status) => {

        fetch(`${config.apiHost}/extracttool/extract`,
            {
                method: 'POST',
                body: JSON.stringify(reqObj),
                headers: {
                    'Content-Type': 'application/json'
                }
            }
        ).then((response) => {
            return response.json();
        }).then((data) => {
            this.setState({
                progressStatus: status
            });
        });
    }

    componentDidMount() {
        if (this.state.progressStatus === 'Progress' ) {
            this.startTimer();
        }
    }

    onPause = () => {
        this.props.changeExecutionStatus('Paused');
        clearInterval(this.timerID);
        const reqObj={
            op: "flow_control",
            extractID: this.props.extractID,
            value: "pause"
        };
        this.callApi(reqObj, 'Paused');
    }

    startTimer = () => {
        this.timerID = setInterval(
            () => this.tick(),
            2500
        );
    }

    onResume = () => {
        this.props.changeExecutionStatus('Progress');
        const reqObj={
            op: "flow_control",
            extractID: this.props.extractId,
            value: "resume"
        };
        this.callApi(reqObj, 'Progress');
        this.startTimer();
    }

    onCancel = () => {
        this.props.changeExecutionStatus('Cancelled');
        clearInterval(this.timerID);
        const reqObj={
            op: "flow_control",
            extractID: this.props.extractId,
            value: "cancel"
        };
        this.callApi(reqObj, 'Cancelled');
    }

    componentWillUnmount() {
        clearInterval(this.timerID);
    }

    render() {
        const { progressStatus, progressPercent, startTime } = this.state;

        let progressClass = progressStatus === 'Complete' ? 'progress-bar progress-bar-success' : 'progress-bar';
        if ( progressStatus === 'Paused' ) {
            progressClass = 'progress-bar-warning progress-bar';
        } else if( progressStatus === 'Cancelled' ) {
            progressClass = 'progress-bar-danger progress-bar';
        }

        return (
            <div className="progress-bar-container">
                {
                    this.props.type === 'bar' &&
                    <div>
                        <div className="progress">
                            <span className="progressStartTime">Start Time: {startTime}</span>
                            <div
                                className={progressClass}
                                role="progressbar"
                                aria-valuenow={ progressPercent }
                                aria-valuemin="0"
                                aria-valuemax="100"
                                style={{width: progressPercent + "%"}}
                            >
                            </div>
                        </div>
                        <span className="extractProgress">{progressPercent < 100 ? progressStatus + ': '+this.state.progressPercent + '%' : 'Complete'}</span>
                        {
                            progressStatus === 'Paused' &&
                            <span className="playIcon" onClick={this.onResume}> </span>
                        }
                        {
                            progressStatus === 'Progress' &&
                            <span className="pauseIcon" onClick={this.onPause}> </span>
                        }
                        {
                            progressStatus !== 'Complete' && progressStatus !== 'Cancelled' &&
                            <span className="cancelIcon" onClick={this.onCancel}> </span>
                        }
                    </div>
                }
                {
                    this.props.type === 'circular' &&
                    <div>
                        <div className="CircularProgress">
                            {
                                progressStatus === 'Paused' &&
                                <span className="playIcon" onClick={this.onResume}> </span>
                            }
                            {
                                progressStatus === 'Progress' &&
                                <span className="pauseIcon" onClick={this.onPause}> </span>
                            }
                            <CircularProgressBar percentage={progressPercent} />
                            {
                                progressStatus !== 'Complete' && progressStatus !== 'Cancelled' &&
                                <span className="cancelIcon" onClick={this.onCancel}> </span>
                            }
                        </div>
                    </div>
                }
            </div>
        );
    }
}

export default GenericProgress;

And here is the component where I am calling these progress bar and circular:

import React from 'react';
import { Panel, Row } from 'react-bootstrap';
import {Link} from 'react-router-dom';
import GenericProgress from './GenericProgress';
import LogFile from './LogFile';
import moment from 'moment'
import './Extract.css';

class Extract extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            open: props.isOpen ? true : false,
            executionStatus: this.props.data.execution_status
        }

        this.changeExecutionStatus = this.changeExecutionStatus.bind(this);
    }

    componentWillReceiveProps(newProps) {
        if(this.props !== newProps){
            if(this.state.executionStatus !== this.props.execution_status) {
                console.log(this.state.executionStatus);
                this.changeExecutionStatus(this.state.executionStatus);
            }
        }
    }

    changeExecutionStatus(status) {
        this.setState({
            executionStatus: status
        })
    }

    render() {
        const {name, progress, start_time, end_time, execution_status, id, engagement} = this.props.data;
        const start_date_time = moment(start_time).format('MMMM Do YYYY, h:mm:ss a');
        const end_date_time = moment(end_time).format('MMMM Do YYYY, h:mm:ss a');

        const startTime = start_date_time.split(',')[1];
        const startDate = start_date_time.split(',')[0];

        const endTime = end_date_time.split(',')[1];
        const endDate = end_date_time.split(',')[0];

        return (
          <div className="extract">
               <div>
               <span className={ this.state.open ? "arrowUpIcon" : "arrowDownicon" } onClick={() => {this.setState({open: !this.state.open})}}></span>
               <h4>
                   {
                     this.props.clientDetails ?
                       <Link to={{
                           pathname: '/client/'+this.props.clientId,
                           state: {
                               extractId: id,
                               engagementId: engagement,
                               source: 'extractDirect'

                           }
                       }} >{name}</Link>
                       :
                       name
                   }
               </h4>
               <div className="progressBar">
                   <GenericProgress
                       type="bar"
                       progress={progress}
                       startTime={start_time}
                       status={this.state.executionStatus}
                       extractId={id}
                       changeExecutionStatus={this.changeExecutionStatus} />
               </div>
                   <Panel collapsible expanded={this.state.open}>
                       <div>
                           <Row>
                               <div className="col-lg-3">
                                   <div>
                                       <GenericProgress
                                           type="circular"
                                           progress={progress}
                                           startTime={start_time}
                                           status={this.state.executionStatus}
                                           extractId={id}
                                           changeExecutionStatus={this.changeExecutionStatus} />
                                   </div>
                                   <br/>
                                   <div>
                                       <b>Start Time:</b> {startTime}
                                       <br/>
                                       <b>Start Date:</b> {startDate}
                                       <br/><br/><br/>
                                       <b>End Time:</b> {endTime}
                                       <br/>
                                       <b>End Date:</b> {endDate}
                                   </div>
                               </div>
                               <div className="col-lg-9">
                                   <LogFile
                                       startDate={startDate}
                                       startTime={startTime}
                                       status={execution_status}
                                   />
                               </div>
                           </Row>
                       </div>
                   </Panel>
               </div>
          </div>
        );
    }
}

export default Extract;

Upvotes: 1

Views: 1221

Answers (2)

Ivan Burnaev
Ivan Burnaev

Reputation: 2730

Now you have two source of truth. The progress status in parent component and progress status in each of Progress Components.

You should make the Progress Component really dumb. It should only render given props.

Move your fetch logic in parent component and change the progress status from it.

Upvotes: 1

idjuradj
idjuradj

Reputation: 1465

From what I can see in your code, you need to keep track of progress in your parent component (Extract component)

The logic would be:

  • in the constructor of the Extract component set the initial value of progress value (either playing/stopped or based on this.props.data)
  • pass that value to GenericProgress component as prop
  • onPause/onPlay and other relevant event handlers should update the state of the parent

From your code, I think you did something similar, but you are inconsistent with the value of progressStatus - you are setting it from props initially (in the constructor), and later on in the code setting different value based on the response from the API

Upvotes: 0

Related Questions