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
5 Answers
Reset to default 5What 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!