Hello Fellow React Peoples,
I finally finished my basic <Countdown />
componant. You use it like this:
<CountdownTimer start={someJSDate} end={someOtherJSDate} />
Here's the code for the component:
import React, { Component } from 'react'
import moment from 'moment'
class CountdownTimer extends Component {
constructor(props) {
super(props)
this.state = {
startTime: null,
endTime: null,
loadTime: null,
delta: null,
difference: null,
animationFrame: null
}
}
componentWillMount() {
this.setState({
startTime: this.props.start,
endTime: this.props.end,
loadTime: new Date(),
})
}
componentDidMount() {
this.tick()
}
tick() {
this.setState({
delta: new Date(+new Date() - this.state.loadTime),
animationFrame: requestAnimationFrame(() => this.tick())
})
}
render() {
let {
startTime,
endTime,
delta
} = this.state
return (
<div>
{
moment(moment(endTime).diff(startTime - delta)).format('d [Days], h [Hours], m [Minutes], s [Seconds and] SSS [Miliseconds]')
}
</div>
)
}
}
export default CountdownTimer
Excellent, now that I have it basically working - let's check out the CPU profile of how this runs in Chrome:
Hm... That seamed heavy... So I tried to optimize it.
import React, { Component } from 'react'
import moment from 'moment'
class CountdownTimer extends Component {
constructor(props) {
super(props)
this.state = {
startTime: null,
endTime: null,
loadTime: null,
delta: null,
throttleFPS: null,
throttleInterval: null,
throttleTime: null,
throttleDelta: null,
difference: null,
animationFrame: null
}
}
componentWillMount() {
this.setState({
startTime: this.props.start,
endTime: this.props.end,
loadTime: new Date(),
throttleFPS: 30,
throttleTime: performance.now()
})
}
componentDidMount() {
// Need to wait for FPS to be set in componentWillMount() to set throttleInterval
this.setState({
throttleInterval: 1000/this.state.throttleFPS
})
this.tick(this.state.throttleTime)
}
tick(throttleTick) {
let {
throttleDelta,
throttleTime,
throttleInterval
} = this.state
throttleDelta = throttleTick - throttleTime
if (throttleDelta > throttleInterval) {
this.setState({
throttleTime: throttleTick - (throttleTime % throttleInterval),
delta: new Date(+new Date() - this.state.loadTime),
})
}
this.setState({
animationFrame: requestAnimationFrame(throttleTick => this.tick(throttleTick))
})
}
render() {
let {
startTime,
endTime,
delta
} = this.state
return (
<div>
{
moment(moment(endTime).diff(startTime - delta)).format('d [Days], h [Hours], m [Minutes], s [Seconds and] SSS [Miliseconds]')
}
</div>
)
}
}
export default CountdownTimer
Okay, Let's see if that's any better...
Okay, if I look really close, I can see a difference between the two... Those pink bars are a bit shorter, and it looks like there is a little less load on the CPU - but basically, this is the same as the first.
But wait!!! what about shouldComponentUpdate()
?!
Code:
import React, { Component } from 'react'
import moment from 'moment'
class CountdownTimer extends Component {
constructor(props) {
super(props)
this.state = {
startTime: null,
endTime: null,
loadTime: null,
oldDelta: null,
delta: null,
throttleFPS: null,
throttleInterval: null,
throttleTime: null,
throttleDelta: null,
difference: null,
animationFrame: null
}
}
componentWillMount() {
this.setState({
startTime: this.props.start,
endTime: this.props.end,
loadTime: new Date(),
throttleFPS: 30,
throttleTime: performance.now()
})
}
componentDidMount() {
// Need to wait for FPS to be set in componentWillMount() to set throttleInterval
this.setState({
throttleInterval: 1000/this.state.throttleFPS
})
this.tick(this.state.throttleTime)
}
tick(throttleTick) {
let {
throttleDelta,
throttleTime,
throttleInterval
} = this.state
throttleDelta = throttleTick - throttleTime
if (throttleDelta > throttleInterval) {
this.setState({
throttleTime: throttleTick - (throttleTime % throttleInterval),
delta: new Date(+new Date() - this.state.loadTime),
})
}
this.setState({
animationFrame: requestAnimationFrame(throttleTick => this.tick(throttleTick))
})
}
shouldComponentUpdate() {
let {
oldDelta,
delta
} = this.state
if (oldDelta === null) {
this.setState({
oldDelta: delta
})
return true
} else if (oldDelta < delta) {
this.setState({
oldDelta: delta
})
return true
} else {
return false
}
}
render() {
let {
startTime,
endTime,
delta
} = this.state
return (
<div>
{
moment(moment(endTime).diff(startTime - delta)).format('d [Days], h [Hours], m [Minutes], s [Seconds and] SSS [Miliseconds]')
}
</div>
)
}
}
export default CountdownTimer
And the screenshot:
Meh, nothing to write home to mom about - and certainly not my client.
What if I combine both requestAnimationFrame()
AND setTimeout()
??
The code:
import React, { Component } from 'react'
import moment from 'moment'
class CountdownTimer extends Component {
constructor(props) {
super(props)
this.state = {
startTime: null,
endTime: null,
loadTime: null,
delta: null,
throttleFPS: null,
throttleInterval: null,
difference: null,
animationFrame: null
}
}
componentWillMount() {
this.setState({
startTime: this.props.start,
endTime: this.props.end,
loadTime: new Date(),
throttleFPS: 30,
throttleTime: performance.now()
})
}
componentDidMount() {
// Need to wait for FPS to be set in componentWillMount() to set throttleInterval
this.setState({
throttleInterval: 1000/this.state.throttleFPS
})
this.tick()
}
tick() {
let {
throttleInterval
} = this.state
setTimeout(() => {
this.setState({
delta: new Date(+new Date() - this.state.loadTime),
animationFrame: requestAnimationFrame(() => this.tick())
})
}, throttleInterval)
}
render() {
let {
startTime,
endTime,
delta
} = this.state
return (
<div>
{
moment(moment(endTime).diff(startTime - delta)).format('d [Days], h [Hours], m [Minutes], s [Seconds and] SSS [Miliseconds]')
}
</div>
)
}
}
export default CountdownTimer
And at long last, the CPU profile screenshot:
Okay, That's much better - don't you think?
Now, what about propTypes? Should I put them in? Can I check for a Date()? Or is that just a String?
My next big enhancement will be to somehow use redux to link all of the countdown timers on a single page together and see how that performance is against having them run their own state. The page I am building will have two of them running - but only one of them on the screen at any time. Maybe it would be better to "detect" if the componant is on the screen somehow and if it's not - than turn the animation off by canceling the requestAnimationFrame()
Any thoughts or code critiques are welcome. I hope this helps somebody out there.
So... I had the math reversed for the countdown timer... I've changed it above in all the code to moment(endTime).diff(startTime - delta)