Skip to content

Commit

Permalink
Merge pull request #48 from amrlabib/enhancement/use-interval
Browse files Browse the repository at this point in the history
Enhancement/use interval
  • Loading branch information
amrlabib authored Apr 17, 2021
2 parents a24d420 + 110228b commit f7c6835
Show file tree
Hide file tree
Showing 9 changed files with 113 additions and 127 deletions.
1 change: 1 addition & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn",
"react/jsx-filename-extension": 0,
"import/prefer-default-export": 0,
"no-unused-expressions": [ 2,{
"allowShortCircuit": true,
}],
Expand Down
3 changes: 1 addition & 2 deletions demo/components/UseTimerDemo.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ export default function UseTimerDemo({ expiryTimestamp }: Object) {
pause,
resume,
restart,
} = useTimer({ expiryTimestamp, onExpire: () => console.warn('onExpire called') });

} = useTimer({ expiryTimestamp, autoStart: true, onExpire: () => console.warn('onExpire called') });

return (
<div>
Expand Down
32 changes: 28 additions & 4 deletions docs/index.js

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ function MyTimer({ expiryTimestamp }) {
pause,
resume,
restart,
} = useTimer({ expiryTimestamp, onExpire: () => console.warn('onExpire called') });
} = useTimer({ expiryTimestamp, autoStart: true, onExpire: () => console.warn('onExpire called') });


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


### Values

| key | Type | Description |
Expand Down
5 changes: 5 additions & 0 deletions src/hooks/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import useInterval from './useInterval';

export {
useInterval,
};
21 changes: 21 additions & 0 deletions src/hooks/useInterval.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { useEffect, useRef } from 'react';

export default function useInterval(callback, delay) {
const callbacRef = useRef();

// update callback function with current render callback that has access to latest props and state
useEffect(() => {
callbacRef.current = callback;
});

useEffect(() => {
if (!delay) {
return () => {};
}

const interval = setInterval(() => {
callbacRef.current && callbacRef.current();
}, delay);
return () => clearInterval(interval);
}, [delay]);
}
38 changes: 9 additions & 29 deletions src/useStopwatch.js
Original file line number Diff line number Diff line change
@@ -1,48 +1,28 @@
import { useState, useEffect, useRef } from 'react';
import { useState } from 'react';
import { Time } from './utils';
import { useInterval } from './hooks';

export default function useStopwatch(settings) {
const { autoStart, offsetTimestamp } = settings || {};

export default function useStopwatch({ autoStart, offsetTimestamp }) {
const [seconds, setSeconds] = useState(Time.getSecondsFromExpiry(offsetTimestamp || 0));
const [isRunning, setIsRunning] = useState(autoStart);
const intervalRef = useRef();

function clearIntervalRef() {
if (intervalRef.current) {
setIsRunning(false);
clearInterval(intervalRef.current);
intervalRef.current = undefined;
}
}
useInterval(() => {
setSeconds((prevSeconds) => (prevSeconds + 1));
}, isRunning ? 1000 : null);

function start() {
if (!intervalRef.current) {
setIsRunning(true);
intervalRef.current = setInterval(() => setSeconds((prevSeconds) => (prevSeconds + 1)), 1000);
}
setIsRunning(true);
}

function pause() {
clearIntervalRef();
setIsRunning(false);
}

function reset(offset: number) {
clearIntervalRef();
setIsRunning(autoStart);
setSeconds(Time.getSecondsFromExpiry(offset || 0));
if (autoStart) {
start();
}
}

// didMount effect
useEffect(() => {
if (autoStart) {
start();
}
return clearIntervalRef;
}, []);

return {
...Time.getTimeFromSeconds(seconds), start, pause, reset, isRunning,
};
Expand Down
30 changes: 6 additions & 24 deletions src/useTime.js
Original file line number Diff line number Diff line change
@@ -1,31 +1,13 @@
import { useState, useEffect, useRef } from 'react';
import { useState } from 'react';
import { Time } from './utils';
import { useInterval } from './hooks';

export default function useTime(settings) {
const { format } = settings || {};

export default function useTime({ format }) {
const [seconds, setSeconds] = useState(Time.getSecondsFromTimeNow());
const intervalRef = useRef();

function clearIntervalRef() {
if (intervalRef.current) {
clearInterval(intervalRef.current);
intervalRef.current = undefined;
}
}

function start() {
if (!intervalRef.current) {
intervalRef.current = setInterval(() => setSeconds(Time.getSecondsFromTimeNow()), 1000);
}
}

// didMount effect
useEffect(() => {
start();
return clearIntervalRef;
}, []);

useInterval(() => {
setSeconds(Time.getSecondsFromTimeNow());
}, 1000);

return {
...Time.getFormattedTimeFromSeconds(seconds, format),
Expand Down
106 changes: 39 additions & 67 deletions src/useTimer.js
Original file line number Diff line number Diff line change
@@ -1,89 +1,61 @@
import { useState, useEffect, useRef } from 'react';
import { useState } from 'react';
import { Time, Validate } from './utils';
import { useInterval } from './hooks';

export default function useTimer(settings) {
const { expiryTimestamp: expiry, onExpire } = settings || {};
const DEFAULT_DELAY = 1000;
export default function useTimer({ expiryTimestamp: expiry, onExpire, autoStart }) {
const [expiryTimestamp, setExpiryTimestamp] = useState(expiry);
const [seconds, setSeconds] = useState(Time.getSecondsFromExpiry(expiryTimestamp));
const [isRunning, setIsRunning] = useState(true);
const intervalRef = useRef();

function clearIntervalRef() {
if (intervalRef.current) {
setIsRunning(false);
clearInterval(intervalRef.current);
intervalRef.current = undefined;
}
}
const [isRunning, setIsRunning] = useState(autoStart);
const [didStart, setDidStart] = useState(autoStart);
const extraMilliSeconds = Math.floor((seconds - Math.floor(seconds)) * 1000);
const [delay, setDelay] = useState(extraMilliSeconds > 0 ? extraMilliSeconds : 1000);

function handleExpire() {
clearIntervalRef();
Validate.onExpire(onExpire) && onExpire();
}

function start() {
if (!intervalRef.current) {
setIsRunning(true);
intervalRef.current = setInterval(() => {
const secondsValue = Time.getSecondsFromExpiry(expiryTimestamp);
if (secondsValue <= 0) {
handleExpire();
}
setSeconds(secondsValue);
}, 1000);
}
setIsRunning(false);
setDelay(null);
}

function pause() {
clearIntervalRef();
setIsRunning(false);
}

function resume() {
if (!intervalRef.current) {
setIsRunning(true);
intervalRef.current = setInterval(() => setSeconds((prevSeconds) => {
const secondsValue = prevSeconds - 1;
if (secondsValue <= 0) {
handleExpire();
}
return secondsValue;
}), 1000);
}
}

function restart(newExpiryTimestamp) {
clearIntervalRef();
function restart(newExpiryTimestamp, newAutoStart) {
const secondsValue = Time.getSecondsFromExpiry(newExpiryTimestamp);
const extraMilliSecondsValue = Math.floor((secondsValue - Math.floor(secondsValue)) * 1000);
setDelay(extraMilliSecondsValue > 0 ? extraMilliSecondsValue : 1000);
setDidStart(newAutoStart);
setIsRunning(newAutoStart);
setExpiryTimestamp(newExpiryTimestamp);
setSeconds(secondsValue);
}

function handleExtraMilliSeconds(secondsValue, extraMilliSeconds) {
setIsRunning(true);
intervalRef.current = setTimeout(() => {
const currentSeconds = Time.getSecondsFromExpiry(expiryTimestamp);
setSeconds(currentSeconds);
if (currentSeconds <= 0) {
handleExpire();
} else {
intervalRef.current = undefined;
start();
}
}, extraMilliSeconds);
function resume() {
const time = new Date();
time.setMilliseconds(time.getMilliseconds() + (seconds * 1000));
restart(time, true);
}

useEffect(() => {
if (Validate.expiryTimestamp(expiryTimestamp)) {
const secondsValue = Time.getSecondsFromExpiry(expiryTimestamp);
const extraMilliSeconds = Math.floor((secondsValue - Math.floor(secondsValue)) * 1000);
setSeconds(secondsValue);
if (extraMilliSeconds > 0) {
handleExtraMilliSeconds(secondsValue, extraMilliSeconds);
} else {
start();
}
function start() {
if (didStart) {
setSeconds(Time.getSecondsFromExpiry(expiryTimestamp));
setIsRunning(true);
} else {
resume();
}
return clearIntervalRef;
}, [expiryTimestamp]);
}

useInterval(() => {
if (delay !== DEFAULT_DELAY) {
setDelay(DEFAULT_DELAY);
}
const secondsValue = Time.getSecondsFromExpiry(expiryTimestamp);
setSeconds(secondsValue);
if (secondsValue <= 0) {
handleExpire();
}
}, isRunning ? delay : null);

return {
...Time.getTimeFromSeconds(seconds), start, pause, resume, restart, isRunning,
Expand Down

0 comments on commit f7c6835

Please sign in to comment.