最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

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

programmeradmin0浏览0评论

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:

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>
      );
    }
}

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./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>
      );
    }
}
Share Improve this question edited Jul 12, 2019 at 15:29 Umbro asked Jul 9, 2019 at 21:15 UmbroUmbro 2,20412 gold badges46 silver badges107 bronze badges
Add a ment  | 

5 Answers 5

Reset to default 5

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 ponent. 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 ponent 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./ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare./ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Your stopwatch does not update as your render method always returns <Stopwatch />, so even if selectItem changes react does not render a new <Stopwatch /> ponent 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 ponent for you you need to pass a key property to your ponent.

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

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

This is where state management pops in. You can use REDUX to manage your ponent 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.

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 ponent, 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./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

The problem:

You position 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 ponent <ItemDetails /> knows all details and only changes between then, not changing the Stopwatch /> ponent.

Possible Solution

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

Make the Item ponent standalone:

You could, instead of having one ponent to display details of the current selected user, have single ponent 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 ponents 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

You could change the Stopwatch ponent to store timers by a label name.

The Stopwatch will have a label prop. When the label changes you could store the current timer and load a new one. The new one can be an empty one or one that is already known, loaded from state.

class Stopwatch extends Component {
  // moved the empty timer for easy reusing
  defaultTimer = {
    timerOn: false,
    timerStart: 0,
    timerTime: 0,
    time: {
      startTime: "",
      endTime: "",
      seconds: ""
    }
  };

  state = {
    // We store the saved timers in here. 
    // For convenience of finding the I used an object to save them
    knownTimers: {},
    ...this.defaultTimer
  };

  ponentDidUpdate(prevProps) {
    // when the label changes
    if (prevProps.label !== this.props.label) {
      const { knownTimers, timerOn, timerStart, timerTime, time } = this.state;
      // we stop any running timers
      clearInterval(this.timer);
      // we build the _timer_ we want save
      const knownTimerToSave = { timerOn, timerStart, timerTime, time };
      // if the label identifies a known _timer_ we load that one
      // if not we load a new timer
      const loadedTimer = knownTimers[this.props.label]
        ? knownTimers[this.props.label]
        : this.defaultTimer;

      this.setState({
        // we load the current timer
        ...loadedTimer,
        // we overwrite `knownTimers` with the save timers
        knownTimers: {
          ...knownTimers,
          [prevProps.label]: {
            ...knownTimerToSave,
            // we make sure we don't save a running timer
            timerOn: false
          }
        }
      });
    }
  }

  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>
    );
  }
}

If the label changed when a timer is running this is automatically turned off.

In order for this to work Stopwatch must be rendered once - not displayed conditionally. When the ponent unmounts the state clears and you loose all the known timers.

Here is demo of the ponent in action

There are other ways to acplish this. Hope this helps!

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论