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>
);
}
}
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>
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.
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
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With