Skip to content

Commit 988b1e3

Browse files
author
Ian Copp
committed
feat: improve CPU performance of progress bar rendering
1 parent 613a74b commit 988b1e3

File tree

4 files changed

+111
-119
lines changed

4 files changed

+111
-119
lines changed

next-env.d.ts

+5-35
Original file line numberDiff line numberDiff line change
@@ -4,39 +4,9 @@
44
/**
55
* @todo Submit to DefinitelyTyped.
66
*/
7-
declare module 'tiny-timer' {
8-
import { EventEmitter } from 'events'
9-
10-
/**
11-
* @event tick
12-
* @event done
13-
*/
14-
export default class Timer extends EventEmitter {
15-
/** The current time in ms. */
16-
readonly time: number
17-
18-
/** The total duration the timer is running for in ms. */
19-
readonly duration: number
20-
21-
/** The current status of the timer as a string. */
22-
readonly status: 'running' | 'paused' | 'stopped'
23-
24-
constructor(opts?: { interval?: number; stopwatch?: boolean })
25-
26-
/**
27-
* Starts timer running.
28-
* @param duration A duration specified in ms
29-
* @param interval Optionally overide the default refresh interval in ms
30-
*/
31-
start(duration: number, interval?: number): void
32-
33-
/** Stops timer. */
34-
stop(): void
35-
36-
/** Pauses timer. */
37-
pause(): void
38-
39-
/** Resumes timer. */
40-
resume(): void
41-
}
7+
declare module 'react-countdown-hook' {
8+
export default function useCountDown(
9+
timeToCount?: number,
10+
interval?: number
11+
): [number, (timeToCount?: number) => void]
4212
}

package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
},
7373
"dependencies": {
7474
"@hendt/ebay-api": "^0.9.0",
75+
"@use-it/interval": "^0.1.0",
7576
"bootswatch": "^4.4.0",
7677
"delay": "^4.3.0",
7778
"dotenv": "8.2.0",
@@ -87,8 +88,7 @@
8788
"react": "^16.13.0",
8889
"react-dom": "^16.13.0",
8990
"rword": "^3.1.0",
90-
"swr": "^0.1.0",
91-
"tiny-timer": "^1.4.0"
91+
"swr": "^0.1.0"
9292
},
9393
"devDependencies": {
9494
"@commitlint/cli": "^8.3.0",

src/pages/index.tsx

+99-73
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1+
import useInterval from '@use-it/interval'
12
import format from 'format-duration'
23
import he from 'he'
34
import fetch from 'isomorphic-unfetch'
45
import { GetServerSideProps, NextComponentType, NextPageContext } from 'next'
56
import absoluteUrl from 'next-absolute-url'
6-
import React, { useCallback, useEffect, useMemo, useState } from 'react'
7+
import React, { FunctionComponent, useState } from 'react'
78
import useSWR from 'swr'
89
import { fetcherFn } from 'swr/dist/types'
9-
import Timer from 'tiny-timer'
1010

1111
import FIVE_MINUTES_IN_MS from '~/constants/five-minutes-in-ms'
1212
import EbayItem from '~/typings/ebay-item'
@@ -15,6 +15,88 @@ const fetcher: fetcherFn<EbayItem & { bigImageUrl: string | null }> = (
1515
url: string
1616
) => fetch(url).then(r => r.json())
1717

18+
const BuyButton: FunctionComponent<{ item: EbayItem }> = ({ item }) => (
19+
<a
20+
className="btn btn-lg btn-success"
21+
href={item.viewItemURL}
22+
target="_blank"
23+
rel="noopener noreferrer"
24+
>
25+
Buy for ${item.sellingStatus.convertedCurrentPrice.toFixed(2)}{' '}
26+
{item.shippingInfo.shippingServiceCost === 0 ? (
27+
<>with free shipping</>
28+
) : (
29+
<>plus ${item.shippingInfo.shippingServiceCost.toFixed(2)} shipping</>
30+
)}
31+
</a>
32+
)
33+
BuyButton.displayName = 'BuyButton'
34+
35+
const ProgressBar: FunctionComponent<{
36+
now: number
37+
min: number
38+
max: number
39+
}> = ({ now, min, max }) => (
40+
<div className="progress">
41+
<div
42+
className="progress-bar progress-bar-striped bg-info"
43+
role="progressbar"
44+
aria-valuenow={now}
45+
aria-valuemin={min}
46+
aria-valuemax={max}
47+
style={{
48+
width: `${(now / max) * 100}%`
49+
}}
50+
/>
51+
</div>
52+
)
53+
ProgressBar.displayName = 'ProgressBar'
54+
55+
const CardBody: FunctionComponent<{
56+
item: EbayItem & { bigImageUrl: string | null }
57+
}> = ({ item }) => (
58+
<div className="card-body">
59+
<img
60+
className="rounded mx-auto d-block"
61+
src={item.bigImageUrl || item.galleryURL}
62+
/>
63+
<div className="text-center my-2">
64+
({he.decode(item.primaryCategory.categoryName)})
65+
</div>
66+
<div className="text-center mt-4">
67+
<BuyButton item={item} />
68+
</div>
69+
</div>
70+
)
71+
CardBody.displayName = 'CardBody'
72+
73+
const CardFooter: FunctionComponent<{
74+
now: number
75+
min: number
76+
max: number
77+
}> = ({ now, min, max }) => (
78+
<div className="card-footer text-muted">
79+
<div className="text-center">{format(max - now)} until the next item</div>
80+
<ProgressBar now={now} min={min} max={max} />
81+
</div>
82+
)
83+
CardFooter.displayName = 'CardBody'
84+
85+
const FooterText: FunctionComponent<{}> = () => (
86+
<div className="text-center mt-4">
87+
(put together from a box of scraps by{' '}
88+
<a
89+
href="https://github.com/icopp"
90+
target="_blank"
91+
rel="noopener noreferrer"
92+
>
93+
icopp
94+
</a>
95+
)
96+
</div>
97+
)
98+
FooterText.displayName = 'FooterText'
99+
18100
const Index: NextComponentType<
19101
NextPageContext,
20102
{ initialData: EbayItem & { bigImageUrl: string | null } },
@@ -24,92 +106,36 @@ const Index: NextComponentType<
24106
'/api/random-ebay-result',
25107
fetcher,
26108
{
27-
initialData
109+
initialData,
110+
revalidateOnFocus: false
28111
}
29112
)
30113

31-
const [tick, setTick] = useState<number>(0)
32-
const timer = useMemo(() => new Timer({ interval: 500, stopwatch: true }), [])
33-
const finishTimer = useCallback(() => {
34-
mutate()
35-
timer.stop()
36-
timer.start(FIVE_MINUTES_IN_MS)
37-
}, [])
38-
useEffect(() => {
39-
timer.addListener('tick', setTick)
40-
timer.addListener('done', finishTimer)
41-
timer.start(FIVE_MINUTES_IN_MS)
42-
43-
return function(): void {
44-
timer.removeListener('tick', setTick)
45-
timer.removeListener('done', finishTimer)
46-
timer.stop()
114+
const [timePassed, setTimePassed] = useState(0)
115+
useInterval(() => {
116+
if (timePassed >= FIVE_MINUTES_IN_MS) {
117+
mutate()
118+
setTimePassed(0)
119+
return
47120
}
48-
}, [])
121+
122+
setTimePassed(timePassed => timePassed + 1000)
123+
}, 1000)
49124

50125
return (
51126
<div className="container">
52127
{data && (
53128
<div className="card my-4">
54129
<h3 className="card-header text-center">{he.decode(data.title)}</h3>
55-
<div className="card-body">
56-
<img
57-
className="rounded mx-auto d-block"
58-
src={data.bigImageUrl || data.galleryURL}
59-
/>
60-
<div className="text-center my-2">
61-
({he.decode(data.primaryCategory.categoryName)})
62-
</div>
63-
<div className="text-center mt-4">
64-
<a
65-
className="btn btn-lg btn-success"
66-
href={data.viewItemURL}
67-
target="_blank"
68-
rel="noopener noreferrer"
69-
>
70-
Buy for ${data.sellingStatus.convertedCurrentPrice.toFixed(2)}{' '}
71-
{data.shippingInfo.shippingServiceCost === 0 ? (
72-
<>with free shipping</>
73-
) : (
74-
<>
75-
plus ${data.shippingInfo.shippingServiceCost.toFixed(2)}{' '}
76-
shipping
77-
</>
78-
)}
79-
</a>
80-
</div>
81-
</div>
82-
<div className="card-footer text-muted">
83-
<div className="text-center">
84-
{format(FIVE_MINUTES_IN_MS - tick)} until the next item
85-
</div>
86-
<div className="progress">
87-
<div
88-
className="progress-bar progress-bar-striped progress-bar-animated bg-info"
89-
role="progressbar"
90-
aria-valuenow={tick}
91-
aria-valuemin={0}
92-
aria-valuemax={FIVE_MINUTES_IN_MS}
93-
style={{ width: `${(tick / FIVE_MINUTES_IN_MS) * 100}%` }}
94-
/>
95-
</div>
96-
</div>
130+
<CardBody item={data} />
131+
<CardFooter now={timePassed} min={0} max={FIVE_MINUTES_IN_MS} />
97132
</div>
98133
)}
99-
<div className="text-center mt-4">
100-
(put together from a box of scraps by{' '}
101-
<a
102-
href="https://github.com/icopp"
103-
target="_blank"
104-
rel="noopener noreferrer"
105-
>
106-
icopp
107-
</a>
108-
)
109-
</div>
134+
<FooterText />
110135
</div>
111136
)
112137
}
138+
Index.displayName = 'Index'
113139
export default Index
114140

115141
export const getServerSideProps: GetServerSideProps = async ({ req }) => {

yarn.lock

+5-9
Original file line numberDiff line numberDiff line change
@@ -1297,6 +1297,10 @@
12971297
semver "^6.3.0"
12981298
tsutils "^3.17.1"
12991299

1300+
"@use-it/interval@^0.1.0":
1301+
version "0.1.3"
1302+
resolved "https://registry.yarnpkg.com/@use-it/interval/-/interval-0.1.3.tgz#5d1096b2295d7a5dda8e8022f3abb5f9d9ef27f8"
1303+
13001304
"@webassemblyjs/[email protected]":
13011305
version "1.8.5"
13021306
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.8.5.tgz#51b1c5fe6576a34953bf4b253df9f0d490d9e359"
@@ -5864,11 +5868,7 @@ lodash.zip@^4.2.0:
58645868
version "4.2.0"
58655869
resolved "https://registry.yarnpkg.com/lodash.zip/-/lodash.zip-4.2.0.tgz#ec6662e4896408ed4ab6c542a3990b72cc080020"
58665870

5867-
[email protected], lodash@^4.17.4:
5868-
version "4.17.15"
5869-
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
5870-
5871-
lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.2.1:
5871+
[email protected], lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.4, lodash@^4.2.1:
58725872
version "4.17.15"
58735873
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
58745874

@@ -9404,10 +9404,6 @@ tiny-relative-date@^1.3.0:
94049404
version "1.3.0"
94059405
resolved "https://registry.yarnpkg.com/tiny-relative-date/-/tiny-relative-date-1.3.0.tgz#fa08aad501ed730f31cc043181d995c39a935e07"
94069406

9407-
tiny-timer@^1.4.0:
9408-
version "1.4.0"
9409-
resolved "https://registry.yarnpkg.com/tiny-timer/-/tiny-timer-1.4.0.tgz#fd3eab4324de9ea157d76f6ec6504b2c138a3985"
9410-
94119407
tmp@^0.0.33:
94129408
version "0.0.33"
94139409
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"

0 commit comments

Comments
 (0)