Skip to content

Enhancement/use interval #48

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Apr 17, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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