From 176aecf4b5e1da08cd0de110a3aecf122af7b368 Mon Sep 17 00:00:00 2001 From: Amr Labib Date: Tue, 14 Jan 2020 15:27:35 +0400 Subject: [PATCH 1/4] handle useTimer with single seconds state and create util files for Validate and Time --- src/useTimer.js | 135 +++++++----------------------------------- src/utils/Time.js | 24 ++++++++ src/utils/Validate.js | 17 ++++++ src/utils/index.js | 7 +++ 4 files changed, 70 insertions(+), 113 deletions(-) create mode 100644 src/utils/Time.js create mode 100644 src/utils/Validate.js create mode 100644 src/utils/index.js diff --git a/src/useTimer.js b/src/useTimer.js index 0480e80..aee12f7 100644 --- a/src/useTimer.js +++ b/src/useTimer.js @@ -1,148 +1,57 @@ import { useState, useEffect, useRef } from 'react'; - - -function isValidExpiryTimestamp(expiryTimestamp) { - const isValid = (new Date(expiryTimestamp)).getTime() > 0; - if (!isValid) { - console.warn('react-timer-hook: { useTimer } Invalid expiryTimestamp settings', expiryTimestamp); - } - return isValid; -} - -function isValidOnExpire(onExpire) { - const isValid = onExpire && typeof onExpire === 'function'; - if (onExpire && !isValid) { - console.warn('react-timer-hook: { useTimer } Invalid onExpire settings function', onExpire); - } - return isValid; -} +import { Time, Validate } from './utils'; export default function useTimer(settings) { const { expiryTimestamp: expiry, onExpire } = settings || {}; const [expiryTimestamp, setExpiryTimestamp] = useState(expiry); - - const [seconds, setSeconds] = useState(0); - const [minutes, setMinutes] = useState(0); - const [hours, setHours] = useState(0); - const [days, setDays] = useState(0); + const [seconds, setSeconds] = useState(Time.getSecondsFromExpiry(expiryTimestamp)); const intervalRef = useRef(); - function reset() { + function clearIntervalRef() { if (intervalRef.current) { clearInterval(intervalRef.current); intervalRef.current = undefined; } - setSeconds(0); - setMinutes(0); - setHours(0); - setDays(0); - } - - function subtractDay() { - setDays((prevDays) => { - if (prevDays > 0) { - return prevDays - 1; - } - reset(); - isValidOnExpire(onExpire) && onExpire(); - return 0; - }); - } - - function subtractHour() { - setHours((prevHours) => { - if (prevHours === 0) { - subtractDay(); - return 23; - } - - if (prevHours > 0) { - return prevHours - 1; - } - return 0; - }); - } - - function subtractMinute() { - setMinutes((prevMinutes) => { - if (prevMinutes === 0) { - subtractHour(); - return 59; - } - - if (prevMinutes > 0) { - return prevMinutes - 1; - } - return 0; - }); - } - - function subtractSecond() { - setSeconds((prevSeconds) => { - if (prevSeconds === 0) { - subtractMinute(); - return 59; - } - - if (prevSeconds > 0) { - return prevSeconds - 1; - } - return 0; - }); - } - - // Timer expiry date calculation - function calculateExpiryDate() { - const now = new Date().getTime(); - const distance = expiryTimestamp - now; - const daysValue = Math.floor(distance / (1000 * 60 * 60 * 24)); - const hoursValue = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); - const minutesValue = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60)); - const secondsValue = Math.floor((distance % (1000 * 60)) / 1000); - if (secondsValue < 0) { - reset(); - isValidOnExpire(onExpire) && onExpire(); - } else { - setSeconds(secondsValue); - setMinutes(minutesValue); - setHours(hoursValue); - setDays(daysValue); - } } function start() { - if (isValidExpiryTimestamp(expiryTimestamp) && !intervalRef.current) { - calculateExpiryDate(); - intervalRef.current = setInterval(() => calculateExpiryDate(), 1000); + if (!intervalRef.current) { + intervalRef.current = setInterval(() => { + const secondsValue = Time.getSecondsFromExpiry(expiryTimestamp); + if (secondsValue <= 0) { + clearIntervalRef(); + Validate.onExpire(onExpire) && onExpire(); + } + setSeconds(secondsValue); + }, 1000); } } function pause() { - if (intervalRef.current) { - clearInterval(intervalRef.current); - intervalRef.current = undefined; - } + clearIntervalRef(); } function resume() { - if (isValidExpiryTimestamp(expiryTimestamp) && !intervalRef.current) { - intervalRef.current = setInterval(() => subtractSecond(), 1000); + if (!intervalRef.current) { + intervalRef.current = setInterval(() => setSeconds((prevSeconds) => (prevSeconds - 1)), 1000); } } function restart(newExpiryTimestamp) { - reset(); + clearIntervalRef(); setExpiryTimestamp(newExpiryTimestamp); } - // didMount effect useEffect(() => { - start(); - return reset; + if (Validate.expiryTimestamp(expiryTimestamp)) { + setSeconds(Time.getSecondsFromExpiry(expiryTimestamp)); + start(); + } + return clearIntervalRef; }, [expiryTimestamp]); return { - seconds, minutes, hours, days, start, pause, resume, restart, + ...Time.getTimeFromSeconds(seconds), start, pause, resume, restart, }; } diff --git a/src/utils/Time.js b/src/utils/Time.js new file mode 100644 index 0000000..8d267e7 --- /dev/null +++ b/src/utils/Time.js @@ -0,0 +1,24 @@ +export default class Time { + static getTimeFromSeconds(totalSeconds) { + const days = Math.floor(totalSeconds / (60 * 60 * 24)); + const hours = Math.floor((totalSeconds % (60 * 60 * 24)) / (60 * 60)); + const minutes = Math.floor((totalSeconds % (60 * 60)) / 60); + const seconds = Math.floor(totalSeconds % 60); + + return { + seconds, + minutes, + hours, + days, + }; + } + + static getSecondsFromExpiry(expiry) { + const now = new Date().getTime(); + const milliSecondsDistance = expiry - now; + if (milliSecondsDistance > 0) { + return Math.floor(milliSecondsDistance / 1000); + } + return 0; + } +} diff --git a/src/utils/Validate.js b/src/utils/Validate.js new file mode 100644 index 0000000..7f931bd --- /dev/null +++ b/src/utils/Validate.js @@ -0,0 +1,17 @@ +export default class Validate { + static expiryTimestamp(expiryTimestamp) { + const isValid = (new Date(expiryTimestamp)).getTime() > 0; + if (!isValid) { + console.warn('react-timer-hook: { useTimer } Invalid expiryTimestamp settings', expiryTimestamp); // eslint-disable-line + } + return isValid; + } + + static onExpire(onExpire) { + const isValid = onExpire && typeof onExpire === 'function'; + if (onExpire && !isValid) { + console.warn('react-timer-hook: { useTimer } Invalid onExpire settings function', onExpire); // eslint-disable-line + } + return isValid; + } +} diff --git a/src/utils/index.js b/src/utils/index.js new file mode 100644 index 0000000..9055e7b --- /dev/null +++ b/src/utils/index.js @@ -0,0 +1,7 @@ +import Time from './Time'; +import Validate from './Validate'; + +export { + Time, + Validate, +}; From 28083dc64ff0ab65844026bbe477704185fd2035 Mon Sep 17 00:00:00 2001 From: Amr Labib Date: Tue, 14 Jan 2020 15:36:19 +0400 Subject: [PATCH 2/4] fix useTimer expire after resume --- src/useTimer.js | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/useTimer.js b/src/useTimer.js index aee12f7..faca743 100644 --- a/src/useTimer.js +++ b/src/useTimer.js @@ -14,13 +14,17 @@ export default function useTimer(settings) { } } + function handleExpire() { + clearIntervalRef(); + Validate.onExpire(onExpire) && onExpire(); + } + function start() { if (!intervalRef.current) { intervalRef.current = setInterval(() => { const secondsValue = Time.getSecondsFromExpiry(expiryTimestamp); if (secondsValue <= 0) { - clearIntervalRef(); - Validate.onExpire(onExpire) && onExpire(); + handleExpire(); } setSeconds(secondsValue); }, 1000); @@ -33,7 +37,13 @@ export default function useTimer(settings) { function resume() { if (!intervalRef.current) { - intervalRef.current = setInterval(() => setSeconds((prevSeconds) => (prevSeconds - 1)), 1000); + intervalRef.current = setInterval(() => setSeconds((prevSeconds) => { + const secondsValue = prevSeconds - 1; + if (secondsValue <= 0) { + handleExpire(); + } + return secondsValue; + }), 1000); } } From 8ca3324793d974b1691304eb60f7a73d28143a4c Mon Sep 17 00:00:00 2001 From: Amr Labib Date: Tue, 14 Jan 2020 16:45:14 +0400 Subject: [PATCH 3/4] update useTime to use only seconds state --- src/useTime.js | 48 +++++++++-------------------------------------- src/utils/Time.js | 25 ++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 39 deletions(-) diff --git a/src/useTime.js b/src/useTime.js index 818fcd4..ebd47de 100644 --- a/src/useTime.js +++ b/src/useTime.js @@ -1,63 +1,33 @@ import { useState, useEffect, useRef } from 'react'; +import { Time } from './utils'; export default function useTime(settings) { const { format } = settings || {}; - const [seconds, setSeconds] = useState(0); - const [minutes, setMinutes] = useState(0); - const [hours, setHours] = useState(0); - const [ampm, setAmPm] = useState(''); + const [seconds, setSeconds] = useState(Time.getSecondsFromTimeNow()); const intervalRef = useRef(); - function formatHours(hoursValue) { - if (format === '12-hour') { - const ampmValue = hoursValue >= 12 ? 'pm' : 'am'; - let formattedHours = hoursValue % 12; - formattedHours = formattedHours || 12; - return { hoursValue: formattedHours, ampmValue }; + function clearIntervalRef() { + if (intervalRef.current) { + clearInterval(intervalRef.current); + intervalRef.current = undefined; } - return { hoursValue, ampmValue: '' }; } - function setCurrentTime() { - const now = new Date(); - const secondsValue = now.getSeconds(); - const minutesValue = now.getMinutes(); - const { hoursValue, ampmValue } = formatHours(now.getHours()); - - setSeconds(secondsValue); - setMinutes(minutesValue); - setHours(hoursValue); - setAmPm(ampmValue); - } - - function start() { if (!intervalRef.current) { - setCurrentTime(); - intervalRef.current = setInterval(() => setCurrentTime(), 1000); - } - } - - function reset() { - if (intervalRef.current) { - clearInterval(intervalRef.current); - intervalRef.current = undefined; + intervalRef.current = setInterval(() => setSeconds(Time.getSecondsFromTimeNow()), 1000); } - setSeconds(0); - setMinutes(0); - setHours(0); - setAmPm(''); } // didMount effect useEffect(() => { start(); - return reset; + return clearIntervalRef; }, []); return { - seconds, minutes, hours, ampm, + ...Time.getFormattedTimeFromSeconds(seconds, format), }; } diff --git a/src/utils/Time.js b/src/utils/Time.js index 8d267e7..36c299c 100644 --- a/src/utils/Time.js +++ b/src/utils/Time.js @@ -21,4 +21,29 @@ export default class Time { } return 0; } + + static getSecondsFromTimeNow() { + const now = new Date(); + const currentTimestamp = now.getTime(); + const offset = (now.getTimezoneOffset() * 60); + return (currentTimestamp / 1000) - offset; + } + + static getFormattedTimeFromSeconds(totalSeconds, format) { + const { seconds: secondsValue, minutes, hours } = Time.getTimeFromSeconds(totalSeconds); + let ampm = ''; + let hoursValue = hours; + + if (format === '12-hour') { + ampm = hours >= 12 ? 'pm' : 'am'; + hoursValue = hours % 12; + } + + return { + seconds: secondsValue, + minutes, + hours: hoursValue, + ampm, + }; + } } From 4b099c138832615d290cade3fa3f6fac3c17616a Mon Sep 17 00:00:00 2001 From: Amr Labib Date: Tue, 14 Jan 2020 18:00:43 +0400 Subject: [PATCH 4/4] use single seconds state in useStopwatch --- src/useStopwatch.js | 58 ++++++++++----------------------------------- 1 file changed, 13 insertions(+), 45 deletions(-) diff --git a/src/useStopwatch.js b/src/useStopwatch.js index a9542a3..02765a3 100644 --- a/src/useStopwatch.js +++ b/src/useStopwatch.js @@ -1,51 +1,22 @@ import { useState, useEffect, useRef } from 'react'; +import { Time } from './utils'; export default function useStopwatch(settings) { const { autoStart } = settings || {}; const [seconds, setSeconds] = useState(0); - const [minutes, setMinutes] = useState(0); - const [hours, setHours] = useState(0); - const [days, setDays] = useState(0); const intervalRef = useRef(); - function addDay() { - setDays((prevDays) => (prevDays + 1)); - } - - function addHour() { - setHours((prevHours) => { - if (prevHours === 23) { - addDay(); - return 0; - } - return prevHours + 1; - }); - } - - function addMinute() { - setMinutes((prevMinutes) => { - if (prevMinutes === 59) { - addHour(); - return 0; - } - return prevMinutes + 1; - }); - } - - function addSecond() { - setSeconds((prevSeconds) => { - if (prevSeconds === 59) { - addMinute(); - return 0; - } - return prevSeconds + 1; - }); + function clearIntervalRef() { + if (intervalRef.current) { + clearInterval(intervalRef.current); + intervalRef.current = undefined; + } } function start() { if (!intervalRef.current) { - intervalRef.current = setInterval(() => addSecond(), 1000); + intervalRef.current = setInterval(() => setSeconds((prevSeconds) => (prevSeconds + 1)), 1000); } } @@ -57,14 +28,11 @@ export default function useStopwatch(settings) { } function reset() { - if (intervalRef.current) { - clearInterval(intervalRef.current); - intervalRef.current = undefined; - } + clearIntervalRef(); setSeconds(0); - setMinutes(0); - setHours(0); - setDays(0); + if (autoStart) { + start(); + } } // didMount effect @@ -72,10 +40,10 @@ export default function useStopwatch(settings) { if (autoStart) { start(); } - return reset; + return clearIntervalRef; }, []); return { - seconds, minutes, hours, days, start, pause, reset, + ...Time.getTimeFromSeconds(seconds), start, pause, reset, }; }