Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Creating independent stopwatches for each item in array. Setting the stopped time, based on data returned by the API

Creating independent stopwatches. I have two elements named A andB. When I click on the A element, its descriptionHello and stopwatch will appear. When I click on the B element, itsWorld description and stopwatch will appear. I have a problem with stopwatches. When I click on the element A and start the stopwatch, go to the elementB then this stopwatch is running. My goal is that when I run the stopwatch for the element A it will count only for this element. When he stops the stopwatch in the element A, and go to the elementB, then in this element the stopwatch will count only for this element. I stop the stopwatch in the B element and go to theA element and I will be able to resume the stopwatch. I am asking for some ideas to solve this problem. I send by calling the startTime function (method post -> object with the starting date). I click stop -> calls stopTimer (method post -> I send the object with the end date). In response, the item is debossed with the starting date and end date and the number of seconds (the difference between the end date and the starting date) is saved in the state. On the basis of these data (start date, end date and second), set the time at which the stopwatch was stopped. How do I close my browser to download this data to set the time at which it was stopped. Please, give me some tips. I will correct my code on a regular basis and insert it here.

Expected effect:

Click element A -> start stopwatch -> stopwatch stop -> click elementB -> start stopwatch -> return to element A -> resume the timer on the time it was stopped

The whole code here: https://stackblitz.com/edit/react-x9h42z

Part of the code:

App.js

class App extends React.Component {
  constructor() {
    super();

    this.state = {

      items: [
        {
          name: 'A',
          description: 'Hello'
        },
        {
          name: 'B',
          description: 'World'
        }
      ],
      selectIndex: null
    };
  }

  select = (index) => {
    this.setState({
      selectIndex: index
    })
  }


  render() {
    console.log(this.state.selectIndex)
    return (
      <div>
        <ul>
          {
            this.state.items
              .map((item, index) =>
                <Item
                  key={index}
                  index={index}
                  item={item}
                  select={this.select}
                  items = {this.state.items}
                  selectIndex = {this.state.selectIndex}
                />
              )
          }
        </ul>
         <ItemDetails
            items = {this.state.items}
            selectIndex = {this.state.selectIndex}

        />
      </div>
    );
  }
}

Stopwatch

class Stopwatch extends Component {
  constructor() {
    super();

    this.state = {
      timerOn: false,
      timerStart: 0,
      timerTime: 0
    };
  }

  startTimer = () => {
    this.setState({
      timerOn: true,
      timerTime: this.state.timerTime,
      timerStart: Date.now() - this.state.timerTime
    });
    this.timer = setInterval(() => {
      this.setState({
        timerTime: Date.now() - this.state.timerStart
      });
    }, 10);
  };

  stopTimer = () => {
    this.setState({ timerOn: false });
    clearInterval(this.timer);
  };

  resetTimer = () => {
    this.setState({
      timerStart: 0,
      timerTime: 0
    });
  };

  render() {
      const { timerTime } = this.state;
      let centiseconds = ("0" + (Math.floor(timerTime / 10) % 100)).slice(-2);
      let seconds = ("0" + (Math.floor(timerTime / 1000) % 60)).slice(-2);
      let minutes = ("0" + (Math.floor(timerTime / 60000) % 60)).slice(-2);
      let hours = ("0" + Math.floor(timerTime / 3600000)).slice(-2);

    return (
      <div>


    <div className="Stopwatch-display">
      {hours} : {minutes} : {seconds} : {centiseconds}
    </div>


    {this.state.timerOn === false && this.state.timerTime === 0 && (
    <button onClick={this.startTimer}>Start</button>
    )}

    {this.state.timerOn === true && (
      <button onClick={this.stopTimer}>Stop</button>
    )}

    {this.state.timerOn === false && this.state.timerTime > 0 && (
      <button onClick={this.startTimer}>Resume</button>
    )}

    {this.state.timerOn === false && this.state.timerTime > 0 && (
      <button onClick={this.resetTimer}>Reset</button>
    )}
        </div>
      );
    }
}
like image 553
Umbro Avatar asked Jul 09 '19 21:07

Umbro


4 Answers

What you need is to create two instances of stopwatches one for each list item. I have made changes to the link link you provided. I added stopwatch in your list array to each object with a unique key for React to know that they are a different component. Now, I am simply rendering all the list items with stopwatches and to maintain the state of each stopwatch even after the switch I am just using a simple display none technique rather than removing the component altogether. Check the code and let me know if it works for you?

import React, { Component } from 'react';
import { render } from 'react-dom';
import './style.css';


class Item extends Component {

  render() {
    const selectItem = this.props.items[this.props.selectIndex]
    console.log(selectItem);
    
    return ( 
      
        <li onClick={() => this.props.select(this.props.index)}>
          <div>
            Name:{this.props.item.name}
          </div>
        </li>
    )
  }
}

class ItemDetails extends Component {
 
  render() {
    const selectItem = this.props.items[this.props.selectIndex]
    console.log(selectItem);
    let content = this.props.items.map((item, index) => {
      return (
        <div className={this.props.selectIndex === index?'show':'hide'}>
          <p>
              Description:{item.description}
          </p>
          {item.stopWatch}
        </div>
      );
    })
    return (  
      <div>
        {selectItem ?
            content
          :
          null
        }
      </div>
    )
  }
}

class App extends React.Component {
  constructor() {
    super();

    this.state = {

      items: [
        {
          name: 'A',
          description: 'Hello',
          stopWatch: <Stopwatch key={1} />
        },
        {
          name: 'B',
          description: 'World',
          stopWatch: <Stopwatch key={2} />
        }
      ],
      selectIndex: null
    };
  }

  select = (index) => {
    this.setState({
      selectIndex: index
    })
  }


  render() {
    console.log(this.state.selectIndex)
    return (
      <div>
        <ul>
          {
            this.state.items
              .map((item, index) =>
                <Item
                  key={index}
                  index={index}
                  item={item}
                  select={this.select}
                  items = {this.state.items}
                  selectIndex = {this.state.selectIndex}
                />
              )
          }
        </ul>
         <ItemDetails
            items = {this.state.items}
            selectIndex = {this.state.selectIndex}

        />
      </div>
    );
  }
}


class Stopwatch extends Component {
  constructor() {
    super();

    this.state = {
      timerOn: false,
      timerStart: 0,
      timerTime: 0
    };
  }

  startTimer = () => {
    this.setState({
      timerOn: true,
      timerTime: this.state.timerTime,
      timerStart: Date.now() - this.state.timerTime
    });
    this.timer = setInterval(() => {
      this.setState({
        timerTime: Date.now() - this.state.timerStart
      });
    }, 10);
  };

  stopTimer = () => {
    this.setState({ timerOn: false });
    clearInterval(this.timer);
  };

  resetTimer = () => {
    this.setState({
      timerStart: 0,
      timerTime: 0
    });
  };

  render() {
      const { timerTime } = this.state;
      let centiseconds = ("0" + (Math.floor(timerTime / 10) % 100)).slice(-2);
      let seconds = ("0" + (Math.floor(timerTime / 1000) % 60)).slice(-2);
      let minutes = ("0" + (Math.floor(timerTime / 60000) % 60)).slice(-2);
      let hours = ("0" + Math.floor(timerTime / 3600000)).slice(-2);

    return (
      <div>
      

    <div className="Stopwatch-display">
      {hours} : {minutes} : {seconds} : {centiseconds}
    </div>


    {this.state.timerOn === false && this.state.timerTime === 0 && (
    <button onClick={this.startTimer}>Start</button>
    )}

    {this.state.timerOn === true && (
      <button onClick={this.stopTimer}>Stop</button>
    )}

    {this.state.timerOn === false && this.state.timerTime > 0 && (
      <button onClick={this.startTimer}>Resume</button>
    )}
    
    {this.state.timerOn === false && this.state.timerTime > 0 && (
      <button onClick={this.resetTimer}>Reset</button>
    )}
        </div>
      );
    }
}


render(<App />, document.getElementById('root'));
h1, p {
  font-family: Lato;
}

.show {
  display: block;
}

.hide {
  display: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
like image 58
Bharat23 Avatar answered Sep 30 '22 18:09

Bharat23


Your stopwatch does not update as your render method always returns <Stopwatch />, so even if selectItem changes react does not render a new <Stopwatch /> component for you, it shows the old one.

return (
  <div>
    {selectItem ?
      <div>
        <p>Description:{selectItem.description}</p>
        <Stopwatch />
      </div>
      :
      null
    }
  </div>
)

For react to render a new component for you you need to pass a key property to your component.

return (
  <div>
    {selectItem ?
      <div>
        <p>Description:{selectItem.description}</p>
        <Stopwatch key={selectItem.name}/>
      </div>
      :
      null
    }
  </div>
)

Now react renders new component for you when you switch between stopwatches, but every time you do that stopwatch resets, as the component itself it re-rendered initializing your state variables.

This is where state management pops in. You can use REDUX to manage your component state. You can also write a simple service to do it for you if you want your stopwatch to run in the background.

Demo: stackblitz.

like image 29
Munim Munna Avatar answered Sep 30 '22 17:09

Munim Munna


What you want is actually have several stop watches (one for each item) but show only one of them (which is the selected one)

On the ItemDetails component, Replacing:

<Stopwatch />

with

{this.props.items.map((_, index) => (
  <div style={{ display: index === this.props.selectIndex ? "block" : "none" }}>
    <Stopwatch />
  </div>
))}

Will solve your problem as showed here: https://stackblitz.com/edit/react-atci76

That way react is taking care of two stop watches (cycle and states and all) but your HTML is only showing one of them.

Alternatively, if you are going to show lots of stopwatches, you can have one Stopwatch state passed as property to your Stopwatch object and then switch the state using the button

like image 42
Bruno Vieira Avatar answered Sep 30 '22 18:09

Bruno Vieira


The problem:

You composition is wrong. Even though you are rendering one <Item /> for each element of the state your <ItemDetails /> is only called once, so the state will be always the same, doesn't matter which item is current selected. This is happening because your component <ItemDetails /> knows all details and only changes between then, not changing the Stopwatch /> component.

Possible Solution

There is actually a few alternatives to this problem, my favorite is:

Make the Item component standalone:

You could, instead of having one component to display details of the current selected user, have single component which knows how to display it's details an knows the state of it's own <Stopwatch /> and only show details and stopwatch if is selected. Something like this:

class Item extends Component {

  render() {
    const selectItem = this.props.items[this.props.selectIndex]
    console.log(selectItem);

    return ( 

        <li onClick={() => this.props.select(this.props.index)}>
          <div>
            Name:{this.props.item.name}
          </div>
          {this.props.isSelected ?
              <div>
                  <p>
                      Description:{selectItem.description}
                  </p>
                     <Stopwatch />
              </div>
               :

               null
        }
        </li>
    )
  }

}

This way you have a decoupled way of dealing with multiple items with different times.

This is actually not exactly what you are looking for, cause even like this you still have your Stopwatches components beeing mounted and unmounted so the state gets reseted everytime, to workaround this you need a way to "remember" the previous state of each counter or never unmount then, just hide those who aren't selected.

I've altered your fiddle to reflect 2 <Stopwatch /> running separately

like image 27
Dupocas Avatar answered Sep 30 '22 18:09

Dupocas