0

The following is most likely a fundamental question. I have the following class. The value of this.isRunning is updated to true in start() but after running start() at stop() the value is still false. What is the reason for that? This happens when the class is imported into a React component and instantiated.

The latest version of Stopwatch as used in a React component can be seen below:

import moment from "moment";

export default class Stopwatch {
  constructor() {
    console.log("run");
    this.isRunning = false;
    this.lastUpdateTime = 0;
    this.totalDuration = 0;
    this.interval = null;
  }

  start() {
    if (this.isRunning) {
      return console.error("Stopwatch is already started");
    }
    console.log("Stopwatch started");
    this.lastUpdateTime = window.performance.now();
    this.isRunning = true;
    console.log(this.isRunning);

    this.interval = setInterval(() => {
      this.totalDuration += window.performance.now() - this.lastUpdateTime;
      this.lastUpdateTime = window.performance.now();
    }, 100); // update about every 100 ms
  }

  stop() {
    console.log(this.isRunning)
    if (!this.isRunning) {
      return console.error("Stopwatch is already stopped");
    }
    console.log("Stopwatch stopped");
    console.log("Stopwatch duration:", this.getFormattedDuration());
    this.isRunning = false;
    clearInterval(this.interval);
  }

  reset() {
    console.log("Stopwatch reset");
    this.isRunning = false;
    this.lastUpdateTime = 0;
    this.totalDuration = 0;
    clearInterval(this.interval);
  }

  formatTime = (ms) => moment(ms).format("mm:ss.S");

  getFormattedDuration() {
    if (this.lastUpdateTime === 0) {
      return "00:00.000";
    }
    return this.formatTime(this.totalDuration);
  }
}

First, the stopwatch is started. Then it is stopped, and finally, reset.

The console output is the following:

Console output

Here is a stripped-down version of the original React component where the issue still occurs:

import React from 'react';
import { Button, Row, Col } from 'antd';
import { I18n } from 'aws-amplify';
import Stopwatch from '../../../../../utils/Stopwatch';

export default function TenMeter(props) {

    const stopwatch = new Stopwatch();

    const startTrial = async (index) => {
        stopwatch.start();
    }

    const completeTrial = async (index) => {
        stopwatch.stop();
        resetParameters();
    }

    const resetTrial = async (index) => {
        resetParameters();
    }

    const resetTest = (index) => {
        resetParameters();
    }

    /**
      * Reset parameter values.
      */
    const resetParameters = () => {
        stopwatch.reset();
    }

    function getTrialsButtonGroup(index) {
        return (
            <Row key={index} gutter={[16, 16]}>
                <Col xs={{ span: 24, offset: 0 }} lg={{ span: 24, offset: 0 }}>
                    <span>{I18n.get('Trial')} {index + 1}</span>
                    <Button style={{ marginLeft: "10px" }} onClick={() => startTrial(index)}>{I18n.get('Start')}</Button>
                    <Button style={{ marginLeft: "10px" }} onClick={() => resetTrial(index)}>{I18n.get('Reset')}</Button>
                    <Button style={{ marginLeft: "10px" }} onClick={() => completeTrial(index)}>{I18n.get('Stop')}</Button>
                </Col>
            </Row>
        )
    };

    return (
        <div>
                    {getTrialsButtonGroup(0)}
        </div>
    );
}

When used in the above React component, Stopwatch has several instances initialized as it can be seen that the constructor is run several times.

In this JSFiddle snippet the constructor runs only once but the same issue occurs when stopwatch.stop() is triggered using the console.

enter image description here

kataba
  • 93
  • 6
  • Maybe you are running `stop()` before `start()`. Can you add those details – Tushar Shahi Oct 14 '21 at 17:30
  • 1
    Maybe you are instantiang more than one class, test with a `console.log` inside constructor to see how many times it is called. – Leo Oct 14 '21 at 17:30
  • @TusharShahi I updated the description. I first run `start` and then `stop`. – kataba Oct 14 '21 at 17:32
  • @Leo There is only one instance of this class per React component. – kataba Oct 14 '21 at 17:32
  • 2
    @kataba alright, can you also post the relevant code of the component? just to see how the class is used inside it – Leo Oct 14 '21 at 17:34
  • @Leo I updated the question description with a JSFiddle snippet and an example component where this issue occurs. – kataba Oct 14 '21 at 18:18
  • As @Leo suggested, the class was instantiated more than once (although defined only once in the code) in the React component where it was used. But in the JSFiddle snippet, it is instantiated only once but the same issue occurs. – kataba Oct 14 '21 at 18:43
  • It works fine in the jsfiddle example. If you open the fiddle's console, `stopwatch` is exposed, and calling `start` and `stop` on it do exactly what you'd expect. Are you using the *browser* console and instantiating a new instance? The `stopwatch` in the fiddle is *not* available if you open the browser's console. – Dave Newton Oct 14 '21 at 18:54
  • @DaveNewton I used the JSFiddle console only. For me it shows the error in the updated question description when I refresh the page, type `stopwatch.stop()` in the console and hit `Enter`. – kataba Oct 14 '21 at 18:56
  • 1
    @kataba don't use the console of JSFiddle, it was updating the page and causing confusion... your stopwatch is working, look at this fiddle and use the "start" and "stop" buttons on the rendered HTML https://jsfiddle.net/L2nxms3v/ – Leo Oct 14 '21 at 19:03
  • 1
    also, about the React component, every call to component function will create a new instance because React will call your function... to solve this you can export and import the instance instead of the class, look at this question/answer to see how to do this: https://stackoverflow.com/questions/54378476/export-of-a-class-instance-in-javascript – Leo Oct 14 '21 at 19:04
  • @DaveNewton Here is a demo video: https://streamable.com/glwfdn I suppose that it might be a bug of JSFiddle. I am using Mozilla Firefox. – kataba Oct 14 '21 at 19:07
  • @Leo I was using Mozilla Firefox. I observed the page refresh issue too, but on Google Chrome. In your version of the fiddle, when using JSFiddle's console, the issue persists as expected. However, indeed using the rendered buttons does not cause the issue. – kataba Oct 14 '21 at 19:12
  • @Leo I suspected that this might be the issue in the case of using the class in a React component as I saw the multiple instantiations happening. Thank you, I will try the solution listed in the question answers. – kataba Oct 14 '21 at 19:14
  • 1
    Exporting a class instance using `export default new Stopwatch()` resolved the multiple instances issue caused by React's rerenders. – kataba Oct 14 '21 at 20:26

0 Answers0