Skip to content

Commit f7c6835

Browse files
authored
Merge pull request #48 from amrlabib/enhancement/use-interval
Enhancement/use interval
2 parents a24d420 + 110228b commit f7c6835

File tree

9 files changed

+113
-127
lines changed

9 files changed

+113
-127
lines changed

.eslintrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"react-hooks/rules-of-hooks": "error",
88
"react-hooks/exhaustive-deps": "warn",
99
"react/jsx-filename-extension": 0,
10+
"import/prefer-default-export": 0,
1011
"no-unused-expressions": [ 2,{
1112
"allowShortCircuit": true,
1213
}],

demo/components/UseTimerDemo.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@ export default function UseTimerDemo({ expiryTimestamp }: Object) {
1313
pause,
1414
resume,
1515
restart,
16-
} = useTimer({ expiryTimestamp, onExpire: () => console.warn('onExpire called') });
17-
16+
} = useTimer({ expiryTimestamp, autoStart: true, onExpire: () => console.warn('onExpire called') });
1817

1918
return (
2019
<div>

docs/index.js

Lines changed: 28 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

readme.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ function MyTimer({ expiryTimestamp }) {
3333
pause,
3434
resume,
3535
restart,
36-
} = useTimer({ expiryTimestamp, onExpire: () => console.warn('onExpire called') });
36+
} = useTimer({ expiryTimestamp, autoStart: true, onExpire: () => console.warn('onExpire called') });
3737

3838

3939
return (
@@ -73,8 +73,10 @@ export default function App() {
7373
| key | Type | Required | Description |
7474
| --- | --- | --- | ---- |
7575
| expiryTimestamp | number(timestamp) | YES | this will define for how long the timer will be running |
76+
| autoStart | boolean | No | flag to decide if timer should start automatically |
7677
| onExpire | Function | No | callback function to be executed once countdown timer is expired |
7778

79+
7880
### Values
7981

8082
| key | Type | Description |

src/hooks/index.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import useInterval from './useInterval';
2+
3+
export {
4+
useInterval,
5+
};

src/hooks/useInterval.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { useEffect, useRef } from 'react';
2+
3+
export default function useInterval(callback, delay) {
4+
const callbacRef = useRef();
5+
6+
// update callback function with current render callback that has access to latest props and state
7+
useEffect(() => {
8+
callbacRef.current = callback;
9+
});
10+
11+
useEffect(() => {
12+
if (!delay) {
13+
return () => {};
14+
}
15+
16+
const interval = setInterval(() => {
17+
callbacRef.current && callbacRef.current();
18+
}, delay);
19+
return () => clearInterval(interval);
20+
}, [delay]);
21+
}

src/useStopwatch.js

Lines changed: 9 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,28 @@
1-
import { useState, useEffect, useRef } from 'react';
1+
import { useState } from 'react';
22
import { Time } from './utils';
3+
import { useInterval } from './hooks';
34

4-
export default function useStopwatch(settings) {
5-
const { autoStart, offsetTimestamp } = settings || {};
6-
5+
export default function useStopwatch({ autoStart, offsetTimestamp }) {
76
const [seconds, setSeconds] = useState(Time.getSecondsFromExpiry(offsetTimestamp || 0));
87
const [isRunning, setIsRunning] = useState(autoStart);
9-
const intervalRef = useRef();
108

11-
function clearIntervalRef() {
12-
if (intervalRef.current) {
13-
setIsRunning(false);
14-
clearInterval(intervalRef.current);
15-
intervalRef.current = undefined;
16-
}
17-
}
9+
useInterval(() => {
10+
setSeconds((prevSeconds) => (prevSeconds + 1));
11+
}, isRunning ? 1000 : null);
1812

1913
function start() {
20-
if (!intervalRef.current) {
21-
setIsRunning(true);
22-
intervalRef.current = setInterval(() => setSeconds((prevSeconds) => (prevSeconds + 1)), 1000);
23-
}
14+
setIsRunning(true);
2415
}
2516

2617
function pause() {
27-
clearIntervalRef();
18+
setIsRunning(false);
2819
}
2920

3021
function reset(offset: number) {
31-
clearIntervalRef();
22+
setIsRunning(autoStart);
3223
setSeconds(Time.getSecondsFromExpiry(offset || 0));
33-
if (autoStart) {
34-
start();
35-
}
3624
}
3725

38-
// didMount effect
39-
useEffect(() => {
40-
if (autoStart) {
41-
start();
42-
}
43-
return clearIntervalRef;
44-
}, []);
45-
4626
return {
4727
...Time.getTimeFromSeconds(seconds), start, pause, reset, isRunning,
4828
};

src/useTime.js

Lines changed: 6 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,13 @@
1-
import { useState, useEffect, useRef } from 'react';
1+
import { useState } from 'react';
22
import { Time } from './utils';
3+
import { useInterval } from './hooks';
34

4-
export default function useTime(settings) {
5-
const { format } = settings || {};
6-
5+
export default function useTime({ format }) {
76
const [seconds, setSeconds] = useState(Time.getSecondsFromTimeNow());
8-
const intervalRef = useRef();
9-
10-
function clearIntervalRef() {
11-
if (intervalRef.current) {
12-
clearInterval(intervalRef.current);
13-
intervalRef.current = undefined;
14-
}
15-
}
16-
17-
function start() {
18-
if (!intervalRef.current) {
19-
intervalRef.current = setInterval(() => setSeconds(Time.getSecondsFromTimeNow()), 1000);
20-
}
21-
}
22-
23-
// didMount effect
24-
useEffect(() => {
25-
start();
26-
return clearIntervalRef;
27-
}, []);
287

8+
useInterval(() => {
9+
setSeconds(Time.getSecondsFromTimeNow());
10+
}, 1000);
2911

3012
return {
3113
...Time.getFormattedTimeFromSeconds(seconds, format),

src/useTimer.js

Lines changed: 39 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,89 +1,61 @@
1-
import { useState, useEffect, useRef } from 'react';
1+
import { useState } from 'react';
22
import { Time, Validate } from './utils';
3+
import { useInterval } from './hooks';
34

4-
export default function useTimer(settings) {
5-
const { expiryTimestamp: expiry, onExpire } = settings || {};
5+
const DEFAULT_DELAY = 1000;
6+
export default function useTimer({ expiryTimestamp: expiry, onExpire, autoStart }) {
67
const [expiryTimestamp, setExpiryTimestamp] = useState(expiry);
78
const [seconds, setSeconds] = useState(Time.getSecondsFromExpiry(expiryTimestamp));
8-
const [isRunning, setIsRunning] = useState(true);
9-
const intervalRef = useRef();
10-
11-
function clearIntervalRef() {
12-
if (intervalRef.current) {
13-
setIsRunning(false);
14-
clearInterval(intervalRef.current);
15-
intervalRef.current = undefined;
16-
}
17-
}
9+
const [isRunning, setIsRunning] = useState(autoStart);
10+
const [didStart, setDidStart] = useState(autoStart);
11+
const extraMilliSeconds = Math.floor((seconds - Math.floor(seconds)) * 1000);
12+
const [delay, setDelay] = useState(extraMilliSeconds > 0 ? extraMilliSeconds : 1000);
1813

1914
function handleExpire() {
20-
clearIntervalRef();
2115
Validate.onExpire(onExpire) && onExpire();
22-
}
23-
24-
function start() {
25-
if (!intervalRef.current) {
26-
setIsRunning(true);
27-
intervalRef.current = setInterval(() => {
28-
const secondsValue = Time.getSecondsFromExpiry(expiryTimestamp);
29-
if (secondsValue <= 0) {
30-
handleExpire();
31-
}
32-
setSeconds(secondsValue);
33-
}, 1000);
34-
}
16+
setIsRunning(false);
17+
setDelay(null);
3518
}
3619

3720
function pause() {
38-
clearIntervalRef();
21+
setIsRunning(false);
3922
}
4023

41-
function resume() {
42-
if (!intervalRef.current) {
43-
setIsRunning(true);
44-
intervalRef.current = setInterval(() => setSeconds((prevSeconds) => {
45-
const secondsValue = prevSeconds - 1;
46-
if (secondsValue <= 0) {
47-
handleExpire();
48-
}
49-
return secondsValue;
50-
}), 1000);
51-
}
52-
}
53-
54-
function restart(newExpiryTimestamp) {
55-
clearIntervalRef();
24+
function restart(newExpiryTimestamp, newAutoStart) {
25+
const secondsValue = Time.getSecondsFromExpiry(newExpiryTimestamp);
26+
const extraMilliSecondsValue = Math.floor((secondsValue - Math.floor(secondsValue)) * 1000);
27+
setDelay(extraMilliSecondsValue > 0 ? extraMilliSecondsValue : 1000);
28+
setDidStart(newAutoStart);
29+
setIsRunning(newAutoStart);
5630
setExpiryTimestamp(newExpiryTimestamp);
31+
setSeconds(secondsValue);
5732
}
5833

59-
function handleExtraMilliSeconds(secondsValue, extraMilliSeconds) {
60-
setIsRunning(true);
61-
intervalRef.current = setTimeout(() => {
62-
const currentSeconds = Time.getSecondsFromExpiry(expiryTimestamp);
63-
setSeconds(currentSeconds);
64-
if (currentSeconds <= 0) {
65-
handleExpire();
66-
} else {
67-
intervalRef.current = undefined;
68-
start();
69-
}
70-
}, extraMilliSeconds);
34+
function resume() {
35+
const time = new Date();
36+
time.setMilliseconds(time.getMilliseconds() + (seconds * 1000));
37+
restart(time, true);
7138
}
7239

73-
useEffect(() => {
74-
if (Validate.expiryTimestamp(expiryTimestamp)) {
75-
const secondsValue = Time.getSecondsFromExpiry(expiryTimestamp);
76-
const extraMilliSeconds = Math.floor((secondsValue - Math.floor(secondsValue)) * 1000);
77-
setSeconds(secondsValue);
78-
if (extraMilliSeconds > 0) {
79-
handleExtraMilliSeconds(secondsValue, extraMilliSeconds);
80-
} else {
81-
start();
82-
}
40+
function start() {
41+
if (didStart) {
42+
setSeconds(Time.getSecondsFromExpiry(expiryTimestamp));
43+
setIsRunning(true);
44+
} else {
45+
resume();
8346
}
84-
return clearIntervalRef;
85-
}, [expiryTimestamp]);
47+
}
8648

49+
useInterval(() => {
50+
if (delay !== DEFAULT_DELAY) {
51+
setDelay(DEFAULT_DELAY);
52+
}
53+
const secondsValue = Time.getSecondsFromExpiry(expiryTimestamp);
54+
setSeconds(secondsValue);
55+
if (secondsValue <= 0) {
56+
handleExpire();
57+
}
58+
}, isRunning ? delay : null);
8759

8860
return {
8961
...Time.getTimeFromSeconds(seconds), start, pause, resume, restart, isRunning,

0 commit comments

Comments
 (0)