From 24df843f103263cbd4002b5771428631890d8e90 Mon Sep 17 00:00:00 2001 From: Amr Labib Date: Sat, 10 Apr 2021 22:39:01 +0400 Subject: [PATCH 01/10] add useInterval and reimplement useTimer in cleaner way --- demo/components/UseTimerDemo.js | 3 +- docs/index.js | 28 +++++++++- src/hooks/index.js | 5 ++ src/hooks/useInterval.js | 17 ++++++ src/useTimer-old.js | 91 +++++++++++++++++++++++++++++++++ src/useTimer.js | 86 +++++++++---------------------- 6 files changed, 163 insertions(+), 67 deletions(-) create mode 100644 src/hooks/index.js create mode 100644 src/hooks/useInterval.js create mode 100644 src/useTimer-old.js diff --git a/demo/components/UseTimerDemo.js b/demo/components/UseTimerDemo.js index 543e32a..28cd1f6 100644 --- a/demo/components/UseTimerDemo.js +++ b/demo/components/UseTimerDemo.js @@ -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 (
diff --git a/docs/index.js b/docs/index.js index b4a6c41..90b470d 100644 --- a/docs/index.js +++ b/docs/index.js @@ -166,7 +166,7 @@ eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\n /***/ (function(module, exports, __webpack_require__) { "use strict"; -eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.default = UseTimerDemo;\n\nvar _react = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n\nvar _react2 = _interopRequireDefault(_react);\n\nvar _index = __webpack_require__(/*! ../../src/index */ \"./src/index.js\");\n\nvar _TimerStyled = __webpack_require__(/*! ./TimerStyled */ \"./demo/components/TimerStyled.js\");\n\nvar _TimerStyled2 = _interopRequireDefault(_TimerStyled);\n\nvar _Button = __webpack_require__(/*! ./Button */ \"./demo/components/Button.js\");\n\nvar _Button2 = _interopRequireDefault(_Button);\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }\n\nfunction UseTimerDemo(_ref) {\n var expiryTimestamp = _ref.expiryTimestamp;\n\n var _useTimer = (0, _index.useTimer)({ expiryTimestamp: expiryTimestamp, onExpire: function onExpire() {\n return console.warn('onExpire called');\n } }),\n seconds = _useTimer.seconds,\n minutes = _useTimer.minutes,\n hours = _useTimer.hours,\n days = _useTimer.days,\n start = _useTimer.start,\n pause = _useTimer.pause,\n resume = _useTimer.resume,\n restart = _useTimer.restart;\n\n return _react2.default.createElement(\n 'div',\n null,\n _react2.default.createElement(\n 'h2',\n null,\n 'UseTimer Demo'\n ),\n _react2.default.createElement(_TimerStyled2.default, { seconds: seconds, minutes: minutes, hours: hours, days: days }),\n _react2.default.createElement(\n _Button2.default,\n { type: 'button', onClick: start },\n 'Start'\n ),\n _react2.default.createElement(\n _Button2.default,\n { type: 'button', onClick: pause },\n 'Pause'\n ),\n _react2.default.createElement(\n _Button2.default,\n { type: 'button', onClick: resume },\n 'Resume'\n ),\n _react2.default.createElement(\n _Button2.default,\n {\n type: 'button',\n onClick: function onClick() {\n // Restarts to 10 minutes timer\n var time = new Date();\n time.setSeconds(time.getSeconds() + 600);\n restart(time);\n }\n },\n 'Restart'\n )\n );\n}\n\n//# sourceURL=webpack:///./demo/components/UseTimerDemo.js?"); +eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.default = UseTimerDemo;\n\nvar _react = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n\nvar _react2 = _interopRequireDefault(_react);\n\nvar _index = __webpack_require__(/*! ../../src/index */ \"./src/index.js\");\n\nvar _TimerStyled = __webpack_require__(/*! ./TimerStyled */ \"./demo/components/TimerStyled.js\");\n\nvar _TimerStyled2 = _interopRequireDefault(_TimerStyled);\n\nvar _Button = __webpack_require__(/*! ./Button */ \"./demo/components/Button.js\");\n\nvar _Button2 = _interopRequireDefault(_Button);\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }\n\nfunction UseTimerDemo(_ref) {\n var expiryTimestamp = _ref.expiryTimestamp;\n\n var _useTimer = (0, _index.useTimer)({ expiryTimestamp: expiryTimestamp, autoStart: true, onExpire: function onExpire() {\n return console.warn('onExpire called');\n } }),\n seconds = _useTimer.seconds,\n minutes = _useTimer.minutes,\n hours = _useTimer.hours,\n days = _useTimer.days,\n start = _useTimer.start,\n pause = _useTimer.pause,\n resume = _useTimer.resume,\n restart = _useTimer.restart;\n\n return _react2.default.createElement(\n 'div',\n null,\n _react2.default.createElement(\n 'h2',\n null,\n 'UseTimer Demo'\n ),\n _react2.default.createElement(_TimerStyled2.default, { seconds: seconds, minutes: minutes, hours: hours, days: days }),\n _react2.default.createElement(\n _Button2.default,\n { type: 'button', onClick: start },\n 'Start'\n ),\n _react2.default.createElement(\n _Button2.default,\n { type: 'button', onClick: pause },\n 'Pause'\n ),\n _react2.default.createElement(\n _Button2.default,\n { type: 'button', onClick: resume },\n 'Resume'\n ),\n _react2.default.createElement(\n _Button2.default,\n {\n type: 'button',\n onClick: function onClick() {\n // Restarts to 10 minutes timer\n var time = new Date();\n time.setSeconds(time.getSeconds() + 600);\n restart(time);\n }\n },\n 'Restart'\n )\n );\n}\n\n//# sourceURL=webpack:///./demo/components/UseTimerDemo.js?"); /***/ }), @@ -443,6 +443,30 @@ eval("var g;\n\n// This works in non-strict mode\ng = (function() {\n\treturn th /***/ }), +/***/ "./src/hooks/index.js": +/*!****************************!*\ + !*** ./src/hooks/index.js ***! + \****************************/ +/*! no static exports found */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.useInterval = undefined;\n\nvar _useInterval = __webpack_require__(/*! ./useInterval */ \"./src/hooks/useInterval.js\");\n\nvar _useInterval2 = _interopRequireDefault(_useInterval);\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }\n\nexports.useInterval = _useInterval2.default;\n\n//# sourceURL=webpack:///./src/hooks/index.js?"); + +/***/ }), + +/***/ "./src/hooks/useInterval.js": +/*!**********************************!*\ + !*** ./src/hooks/useInterval.js ***! + \**********************************/ +/*! no static exports found */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.default = useInterval;\n\nvar _react = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n\nfunction useInterval(callback, delay) {\n var callbacRef = (0, _react.useRef)();\n\n // update callback function with current render callback that has access to latest props and state\n (0, _react.useEffect)(function () {\n callbacRef.current = callback;\n });\n\n (0, _react.useEffect)(function () {\n var interval = setInterval(function () {\n callbacRef.current && callbacRef.current();\n }, delay);\n return function () {\n return clearInterval(interval);\n };\n }, [delay]);\n}\n\n//# sourceURL=webpack:///./src/hooks/useInterval.js?"); + +/***/ }), + /***/ "./src/index.js": /*!**********************!*\ !*** ./src/index.js ***! @@ -487,7 +511,7 @@ eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\n /***/ (function(module, exports, __webpack_require__) { "use strict"; -eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\n\nvar _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };\n\nvar _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i[\"return\"]) _i[\"return\"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError(\"Invalid attempt to destructure non-iterable instance\"); } }; }();\n\nexports.default = useTimer;\n\nvar _react = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n\nvar _utils = __webpack_require__(/*! ./utils */ \"./src/utils/index.js\");\n\nfunction useTimer(settings) {\n var _ref = settings || {},\n expiry = _ref.expiryTimestamp,\n onExpire = _ref.onExpire;\n\n var _useState = (0, _react.useState)(expiry),\n _useState2 = _slicedToArray(_useState, 2),\n expiryTimestamp = _useState2[0],\n setExpiryTimestamp = _useState2[1];\n\n var _useState3 = (0, _react.useState)(_utils.Time.getSecondsFromExpiry(expiryTimestamp)),\n _useState4 = _slicedToArray(_useState3, 2),\n seconds = _useState4[0],\n setSeconds = _useState4[1];\n\n var _useState5 = (0, _react.useState)(true),\n _useState6 = _slicedToArray(_useState5, 2),\n isRunning = _useState6[0],\n setIsRunning = _useState6[1];\n\n var intervalRef = (0, _react.useRef)();\n\n function clearIntervalRef() {\n if (intervalRef.current) {\n setIsRunning(false);\n clearInterval(intervalRef.current);\n intervalRef.current = undefined;\n }\n }\n\n function handleExpire() {\n clearIntervalRef();\n _utils.Validate.onExpire(onExpire) && onExpire();\n }\n\n function start() {\n if (!intervalRef.current) {\n setIsRunning(true);\n intervalRef.current = setInterval(function () {\n var secondsValue = _utils.Time.getSecondsFromExpiry(expiryTimestamp);\n if (secondsValue <= 0) {\n handleExpire();\n }\n setSeconds(secondsValue);\n }, 1000);\n }\n }\n\n function pause() {\n clearIntervalRef();\n }\n\n function resume() {\n if (!intervalRef.current) {\n setIsRunning(true);\n intervalRef.current = setInterval(function () {\n return setSeconds(function (prevSeconds) {\n var secondsValue = prevSeconds - 1;\n if (secondsValue <= 0) {\n handleExpire();\n }\n return secondsValue;\n });\n }, 1000);\n }\n }\n\n function restart(newExpiryTimestamp) {\n clearIntervalRef();\n setExpiryTimestamp(newExpiryTimestamp);\n }\n\n function handleExtraMilliSeconds(secondsValue, extraMilliSeconds) {\n setIsRunning(true);\n intervalRef.current = setTimeout(function () {\n var currentSeconds = _utils.Time.getSecondsFromExpiry(expiryTimestamp);\n setSeconds(currentSeconds);\n if (currentSeconds <= 0) {\n handleExpire();\n } else {\n intervalRef.current = undefined;\n start();\n }\n }, extraMilliSeconds);\n }\n\n (0, _react.useEffect)(function () {\n if (_utils.Validate.expiryTimestamp(expiryTimestamp)) {\n var secondsValue = _utils.Time.getSecondsFromExpiry(expiryTimestamp);\n var extraMilliSeconds = Math.floor((secondsValue - Math.floor(secondsValue)) * 1000);\n setSeconds(secondsValue);\n if (extraMilliSeconds > 0) {\n handleExtraMilliSeconds(secondsValue, extraMilliSeconds);\n } else {\n start();\n }\n }\n return clearIntervalRef;\n }, [expiryTimestamp]);\n\n return _extends({}, _utils.Time.getTimeFromSeconds(seconds), { start: start, pause: pause, resume: resume, restart: restart, isRunning: isRunning\n });\n}\n\n//# sourceURL=webpack:///./src/useTimer.js?"); +eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\n\nvar _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };\n\nvar _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i[\"return\"]) _i[\"return\"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError(\"Invalid attempt to destructure non-iterable instance\"); } }; }();\n\nexports.default = useTimer;\n\nvar _react = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n\nvar _utils = __webpack_require__(/*! ./utils */ \"./src/utils/index.js\");\n\nvar _hooks = __webpack_require__(/*! ./hooks */ \"./src/hooks/index.js\");\n\nfunction useTimer(_ref) {\n var expiry = _ref.expiryTimestamp,\n onExpire = _ref.onExpire,\n autoStart = _ref.autoStart;\n\n var _useState = (0, _react.useState)(expiry),\n _useState2 = _slicedToArray(_useState, 2),\n expiryTimestamp = _useState2[0],\n setExpiryTimestamp = _useState2[1];\n\n var _useState3 = (0, _react.useState)(_utils.Time.getSecondsFromExpiry(expiryTimestamp)),\n _useState4 = _slicedToArray(_useState3, 2),\n seconds = _useState4[0],\n setSeconds = _useState4[1];\n\n var _useState5 = (0, _react.useState)(false),\n _useState6 = _slicedToArray(_useState5, 2),\n isRunning = _useState6[0],\n setIsRunning = _useState6[1];\n\n function handleExpire() {\n _utils.Validate.onExpire(onExpire) && onExpire();\n }\n\n function pause() {\n setIsRunning(false);\n }\n\n function start() {\n setIsRunning(true);\n }\n\n function resume() {\n var time = new Date();\n time.setSeconds(time.getSeconds() + seconds); // calculate new expiry timestamp based on last paused seconds count\n setExpiryTimestamp(time);\n setIsRunning(true);\n }\n\n function restart(newExpiryTimestamp) {\n setExpiryTimestamp(newExpiryTimestamp);\n }\n\n (0, _hooks.useInterval)(isRunning ? function () {\n var secondsValue = _utils.Time.getSecondsFromExpiry(expiryTimestamp);\n if (secondsValue <= 0) {\n handleExpire();\n }\n setSeconds(secondsValue);\n } : function () {}, 1000);\n\n (0, _react.useEffect)(function () {\n if (autoStart) {\n setIsRunning(true);\n }\n }, [autoStart]);\n\n return _extends({}, _utils.Time.getTimeFromSeconds(seconds), { start: start, pause: pause, resume: resume, restart: restart, isRunning: isRunning\n });\n}\n\n//# sourceURL=webpack:///./src/useTimer.js?"); /***/ }), diff --git a/src/hooks/index.js b/src/hooks/index.js new file mode 100644 index 0000000..28e8735 --- /dev/null +++ b/src/hooks/index.js @@ -0,0 +1,5 @@ +import useInterval from './useInterval'; + +export { + useInterval, +}; diff --git a/src/hooks/useInterval.js b/src/hooks/useInterval.js new file mode 100644 index 0000000..efa049d --- /dev/null +++ b/src/hooks/useInterval.js @@ -0,0 +1,17 @@ +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(() => { + const interval = setInterval(() => { + callbacRef.current && callbacRef.current(); + }, delay); + return () => clearInterval(interval); + }, [delay]); +} diff --git a/src/useTimer-old.js b/src/useTimer-old.js new file mode 100644 index 0000000..aa6e084 --- /dev/null +++ b/src/useTimer-old.js @@ -0,0 +1,91 @@ +import { useState, useEffect, useRef } from 'react'; +import { Time, Validate } from './utils'; + +export default function useTimer(settings) { + const { expiryTimestamp: expiry, onExpire } = settings || {}; + 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; + } + } + + 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); + } + } + + function pause() { + clearIntervalRef(); + } + + 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(); + setExpiryTimestamp(newExpiryTimestamp); + } + + 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); + } + + 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(); + } + } + return clearIntervalRef; + }, [expiryTimestamp]); + + + return { + ...Time.getTimeFromSeconds(seconds), start, pause, resume, restart, isRunning, + }; +} diff --git a/src/useTimer.js b/src/useTimer.js index aa6e084..23afa5f 100644 --- a/src/useTimer.js +++ b/src/useTimer.js @@ -1,89 +1,49 @@ -import { useState, useEffect, useRef } from 'react'; +import { useState, useEffect } from 'react'; import { Time, Validate } from './utils'; +import { useInterval } from './hooks'; -export default function useTimer(settings) { - const { expiryTimestamp: expiry, onExpire } = settings || {}; +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(false); 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); - } + function pause() { + setIsRunning(false); } - function pause() { - clearIntervalRef(); + function start() { + setIsRunning(true); } function resume() { - if (!intervalRef.current) { - setIsRunning(true); - intervalRef.current = setInterval(() => setSeconds((prevSeconds) => { - const secondsValue = prevSeconds - 1; - if (secondsValue <= 0) { - handleExpire(); - } - return secondsValue; - }), 1000); - } + const time = new Date(); + time.setSeconds(time.getSeconds() + seconds); // calculate new expiry timestamp based on last paused seconds count + setExpiryTimestamp(time); + setIsRunning(true); } function restart(newExpiryTimestamp) { - clearIntervalRef(); setExpiryTimestamp(newExpiryTimestamp); + setSeconds(Time.getSecondsFromExpiry(newExpiryTimestamp)); } - 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); - } + useInterval(isRunning ? () => { + const secondsValue = Time.getSecondsFromExpiry(expiryTimestamp); + if (secondsValue <= 0) { + handleExpire(); + } + setSeconds(secondsValue); + } : () => {}, 1000); 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(); - } + if (autoStart) { + setIsRunning(true); } - return clearIntervalRef; - }, [expiryTimestamp]); - + }, [autoStart]); return { ...Time.getTimeFromSeconds(seconds), start, pause, resume, restart, isRunning, From a6986b2ef4587030d93b88ebcd02e6e10ba4c641 Mon Sep 17 00:00:00 2001 From: Amr Labib Date: Sun, 11 Apr 2021 00:42:13 +0400 Subject: [PATCH 02/10] handle pause and start similar to current behaviour --- .eslintrc | 1 + src/useTimer.js | 24 ++++++++++++------------ 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/.eslintrc b/.eslintrc index de8925d..56dedae 100644 --- a/.eslintrc +++ b/.eslintrc @@ -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, }], diff --git a/src/useTimer.js b/src/useTimer.js index 23afa5f..9f7e746 100644 --- a/src/useTimer.js +++ b/src/useTimer.js @@ -1,11 +1,12 @@ -import { useState, useEffect } from 'react'; +import { useState } from 'react'; import { Time, Validate } from './utils'; import { useInterval } from './hooks'; export default function useTimer({ expiryTimestamp: expiry, onExpire, autoStart }) { const [expiryTimestamp, setExpiryTimestamp] = useState(expiry); const [seconds, setSeconds] = useState(Time.getSecondsFromExpiry(expiryTimestamp)); - const [isRunning, setIsRunning] = useState(false); + const [isRunning, setIsRunning] = useState(autoStart); + const [didStart, setDidStart] = useState(autoStart); function handleExpire() { Validate.onExpire(onExpire) && onExpire(); @@ -15,10 +16,6 @@ export default function useTimer({ expiryTimestamp: expiry, onExpire, autoStart setIsRunning(false); } - function start() { - setIsRunning(true); - } - function resume() { const time = new Date(); time.setSeconds(time.getSeconds() + seconds); // calculate new expiry timestamp based on last paused seconds count @@ -26,6 +23,15 @@ export default function useTimer({ expiryTimestamp: expiry, onExpire, autoStart setIsRunning(true); } + function start() { + if (didStart) { + setIsRunning(true); + } else { + resume(); + setDidStart(true); + } + } + function restart(newExpiryTimestamp) { setExpiryTimestamp(newExpiryTimestamp); setSeconds(Time.getSecondsFromExpiry(newExpiryTimestamp)); @@ -39,12 +45,6 @@ export default function useTimer({ expiryTimestamp: expiry, onExpire, autoStart setSeconds(secondsValue); } : () => {}, 1000); - useEffect(() => { - if (autoStart) { - setIsRunning(true); - } - }, [autoStart]); - return { ...Time.getTimeFromSeconds(seconds), start, pause, resume, restart, isRunning, }; From dffecde7390931b87ec8ba44f2e2f5335a54617b Mon Sep 17 00:00:00 2001 From: Amr Labib Date: Sun, 11 Apr 2021 02:26:06 +0400 Subject: [PATCH 03/10] consider autoStart in restart function --- src/useTimer.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/useTimer.js b/src/useTimer.js index 9f7e746..6b7b5aa 100644 --- a/src/useTimer.js +++ b/src/useTimer.js @@ -33,6 +33,7 @@ export default function useTimer({ expiryTimestamp: expiry, onExpire, autoStart } function restart(newExpiryTimestamp) { + setIsRunning(autoStart); setExpiryTimestamp(newExpiryTimestamp); setSeconds(Time.getSecondsFromExpiry(newExpiryTimestamp)); } From c7a1cddb71235cf1e6659ea6ed4ba511b0943ea1 Mon Sep 17 00:00:00 2001 From: Amr Labib Date: Sun, 11 Apr 2021 02:30:44 +0400 Subject: [PATCH 04/10] handle useTime using useInterval --- src/useTime.js | 30 ++++++------------------------ 1 file changed, 6 insertions(+), 24 deletions(-) diff --git a/src/useTime.js b/src/useTime.js index ebd47de..106347b 100644 --- a/src/useTime.js +++ b/src/useTime.js @@ -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), From 0dae45f19183a4f380688c545bdbad1342055977 Mon Sep 17 00:00:00 2001 From: Amr Labib Date: Sun, 11 Apr 2021 02:49:47 +0400 Subject: [PATCH 05/10] add useInterval in useStopwatch --- src/useStopwatch.js | 38 +++++++++----------------------------- 1 file changed, 9 insertions(+), 29 deletions(-) diff --git a/src/useStopwatch.js b/src/useStopwatch.js index a92233c..322ee9b 100644 --- a/src/useStopwatch.js +++ b/src/useStopwatch.js @@ -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(isRunning ? () => { + setSeconds((prevSeconds) => (prevSeconds + 1)); + } : () => {}, 1000); 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, }; From 4919d54b37cd20d11ef55b7b9117a575a97164b9 Mon Sep 17 00:00:00 2001 From: Amr Labib Date: Sun, 11 Apr 2021 03:26:53 +0400 Subject: [PATCH 06/10] handle pause by setting delay to null --- src/hooks/useInterval.js | 4 ++++ src/useStopwatch.js | 4 ++-- src/useTimer.js | 6 ++++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/hooks/useInterval.js b/src/hooks/useInterval.js index efa049d..7f4603f 100644 --- a/src/hooks/useInterval.js +++ b/src/hooks/useInterval.js @@ -9,6 +9,10 @@ export default function useInterval(callback, delay) { }); useEffect(() => { + if (!delay) { + return () => {}; + } + const interval = setInterval(() => { callbacRef.current && callbacRef.current(); }, delay); diff --git a/src/useStopwatch.js b/src/useStopwatch.js index 322ee9b..d5ab05f 100644 --- a/src/useStopwatch.js +++ b/src/useStopwatch.js @@ -6,9 +6,9 @@ export default function useStopwatch({ autoStart, offsetTimestamp }) { const [seconds, setSeconds] = useState(Time.getSecondsFromExpiry(offsetTimestamp || 0)); const [isRunning, setIsRunning] = useState(autoStart); - useInterval(isRunning ? () => { + useInterval(() => { setSeconds((prevSeconds) => (prevSeconds + 1)); - } : () => {}, 1000); + }, isRunning ? 1000 : null); function start() { setIsRunning(true); diff --git a/src/useTimer.js b/src/useTimer.js index 6b7b5aa..5ee6e99 100644 --- a/src/useTimer.js +++ b/src/useTimer.js @@ -21,11 +21,13 @@ export default function useTimer({ expiryTimestamp: expiry, onExpire, autoStart time.setSeconds(time.getSeconds() + seconds); // calculate new expiry timestamp based on last paused seconds count setExpiryTimestamp(time); setIsRunning(true); + setSeconds(Time.getSecondsFromExpiry(time)); } function start() { if (didStart) { setIsRunning(true); + setSeconds(Time.getSecondsFromExpiry(expiryTimestamp)); } else { resume(); setDidStart(true); @@ -38,13 +40,13 @@ export default function useTimer({ expiryTimestamp: expiry, onExpire, autoStart setSeconds(Time.getSecondsFromExpiry(newExpiryTimestamp)); } - useInterval(isRunning ? () => { + useInterval(() => { const secondsValue = Time.getSecondsFromExpiry(expiryTimestamp); if (secondsValue <= 0) { handleExpire(); } setSeconds(secondsValue); - } : () => {}, 1000); + }, isRunning ? 1000 : null); return { ...Time.getTimeFromSeconds(seconds), start, pause, resume, restart, isRunning, From 87cdd72f8b51b57a46eb12c8a16fefe9fff6aa1f Mon Sep 17 00:00:00 2001 From: Amr Labib Date: Sun, 11 Apr 2021 03:36:34 +0400 Subject: [PATCH 07/10] useTimer restart minor fix --- src/useTimer.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/useTimer.js b/src/useTimer.js index 5ee6e99..2813123 100644 --- a/src/useTimer.js +++ b/src/useTimer.js @@ -35,6 +35,7 @@ export default function useTimer({ expiryTimestamp: expiry, onExpire, autoStart } function restart(newExpiryTimestamp) { + setDidStart(autoStart); setIsRunning(autoStart); setExpiryTimestamp(newExpiryTimestamp); setSeconds(Time.getSecondsFromExpiry(newExpiryTimestamp)); From 8dd11b0020f65cf0b6650ccf808feca15cf2e777 Mon Sep 17 00:00:00 2001 From: Amr Labib Date: Wed, 14 Apr 2021 02:29:38 +0400 Subject: [PATCH 08/10] handle milliseconds in useTimer after new implementation --- docs/index.js | 10 +++++----- src/useTimer.js | 38 +++++++++++++++++++++++--------------- 2 files changed, 28 insertions(+), 20 deletions(-) diff --git a/docs/index.js b/docs/index.js index 90b470d..0b4cfff 100644 --- a/docs/index.js +++ b/docs/index.js @@ -166,7 +166,7 @@ eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\n /***/ (function(module, exports, __webpack_require__) { "use strict"; -eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.default = UseTimerDemo;\n\nvar _react = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n\nvar _react2 = _interopRequireDefault(_react);\n\nvar _index = __webpack_require__(/*! ../../src/index */ \"./src/index.js\");\n\nvar _TimerStyled = __webpack_require__(/*! ./TimerStyled */ \"./demo/components/TimerStyled.js\");\n\nvar _TimerStyled2 = _interopRequireDefault(_TimerStyled);\n\nvar _Button = __webpack_require__(/*! ./Button */ \"./demo/components/Button.js\");\n\nvar _Button2 = _interopRequireDefault(_Button);\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }\n\nfunction UseTimerDemo(_ref) {\n var expiryTimestamp = _ref.expiryTimestamp;\n\n var _useTimer = (0, _index.useTimer)({ expiryTimestamp: expiryTimestamp, autoStart: true, onExpire: function onExpire() {\n return console.warn('onExpire called');\n } }),\n seconds = _useTimer.seconds,\n minutes = _useTimer.minutes,\n hours = _useTimer.hours,\n days = _useTimer.days,\n start = _useTimer.start,\n pause = _useTimer.pause,\n resume = _useTimer.resume,\n restart = _useTimer.restart;\n\n return _react2.default.createElement(\n 'div',\n null,\n _react2.default.createElement(\n 'h2',\n null,\n 'UseTimer Demo'\n ),\n _react2.default.createElement(_TimerStyled2.default, { seconds: seconds, minutes: minutes, hours: hours, days: days }),\n _react2.default.createElement(\n _Button2.default,\n { type: 'button', onClick: start },\n 'Start'\n ),\n _react2.default.createElement(\n _Button2.default,\n { type: 'button', onClick: pause },\n 'Pause'\n ),\n _react2.default.createElement(\n _Button2.default,\n { type: 'button', onClick: resume },\n 'Resume'\n ),\n _react2.default.createElement(\n _Button2.default,\n {\n type: 'button',\n onClick: function onClick() {\n // Restarts to 10 minutes timer\n var time = new Date();\n time.setSeconds(time.getSeconds() + 600);\n restart(time);\n }\n },\n 'Restart'\n )\n );\n}\n\n//# sourceURL=webpack:///./demo/components/UseTimerDemo.js?"); +eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.default = UseTimerDemo;\n\nvar _react = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n\nvar _react2 = _interopRequireDefault(_react);\n\nvar _index = __webpack_require__(/*! ../../src/index */ \"./src/index.js\");\n\nvar _TimerStyled = __webpack_require__(/*! ./TimerStyled */ \"./demo/components/TimerStyled.js\");\n\nvar _TimerStyled2 = _interopRequireDefault(_TimerStyled);\n\nvar _Button = __webpack_require__(/*! ./Button */ \"./demo/components/Button.js\");\n\nvar _Button2 = _interopRequireDefault(_Button);\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }\n\nfunction UseTimerDemo(_ref) {\n var expiryTimestamp = _ref.expiryTimestamp;\n\n var _useTimer = (0, _index.useTimer)({ expiryTimestamp: expiryTimestamp, autoStart: false, onExpire: function onExpire() {\n return console.warn('onExpire called');\n } }),\n seconds = _useTimer.seconds,\n minutes = _useTimer.minutes,\n hours = _useTimer.hours,\n days = _useTimer.days,\n start = _useTimer.start,\n pause = _useTimer.pause,\n resume = _useTimer.resume,\n restart = _useTimer.restart;\n\n return _react2.default.createElement(\n 'div',\n null,\n _react2.default.createElement(\n 'h2',\n null,\n 'UseTimer Demo'\n ),\n _react2.default.createElement(_TimerStyled2.default, { seconds: seconds, minutes: minutes, hours: hours, days: days }),\n _react2.default.createElement(\n _Button2.default,\n { type: 'button', onClick: start },\n 'Start'\n ),\n _react2.default.createElement(\n _Button2.default,\n { type: 'button', onClick: pause },\n 'Pause'\n ),\n _react2.default.createElement(\n _Button2.default,\n { type: 'button', onClick: resume },\n 'Resume'\n ),\n _react2.default.createElement(\n _Button2.default,\n {\n type: 'button',\n onClick: function onClick() {\n // Restarts to 10 minutes timer\n var time = new Date();\n time.setSeconds(time.getSeconds() + 600);\n restart(time);\n }\n },\n 'Restart'\n )\n );\n}\n\n//# sourceURL=webpack:///./demo/components/UseTimerDemo.js?"); /***/ }), @@ -463,7 +463,7 @@ eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\n /***/ (function(module, exports, __webpack_require__) { "use strict"; -eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.default = useInterval;\n\nvar _react = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n\nfunction useInterval(callback, delay) {\n var callbacRef = (0, _react.useRef)();\n\n // update callback function with current render callback that has access to latest props and state\n (0, _react.useEffect)(function () {\n callbacRef.current = callback;\n });\n\n (0, _react.useEffect)(function () {\n var interval = setInterval(function () {\n callbacRef.current && callbacRef.current();\n }, delay);\n return function () {\n return clearInterval(interval);\n };\n }, [delay]);\n}\n\n//# sourceURL=webpack:///./src/hooks/useInterval.js?"); +eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.default = useInterval;\n\nvar _react = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n\nfunction useInterval(callback, delay) {\n var callbacRef = (0, _react.useRef)();\n\n // update callback function with current render callback that has access to latest props and state\n (0, _react.useEffect)(function () {\n callbacRef.current = callback;\n });\n\n (0, _react.useEffect)(function () {\n if (!delay) {\n return function () {};\n }\n\n var interval = setInterval(function () {\n callbacRef.current && callbacRef.current();\n }, delay);\n return function () {\n return clearInterval(interval);\n };\n }, [delay]);\n}\n\n//# sourceURL=webpack:///./src/hooks/useInterval.js?"); /***/ }), @@ -487,7 +487,7 @@ eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\n /***/ (function(module, exports, __webpack_require__) { "use strict"; -eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\n\nvar _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };\n\nvar _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i[\"return\"]) _i[\"return\"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError(\"Invalid attempt to destructure non-iterable instance\"); } }; }();\n\nexports.default = useStopwatch;\n\nvar _react = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n\nvar _utils = __webpack_require__(/*! ./utils */ \"./src/utils/index.js\");\n\nfunction useStopwatch(settings) {\n var _ref = settings || {},\n autoStart = _ref.autoStart,\n offsetTimestamp = _ref.offsetTimestamp;\n\n var _useState = (0, _react.useState)(_utils.Time.getSecondsFromExpiry(offsetTimestamp || 0)),\n _useState2 = _slicedToArray(_useState, 2),\n seconds = _useState2[0],\n setSeconds = _useState2[1];\n\n var _useState3 = (0, _react.useState)(autoStart),\n _useState4 = _slicedToArray(_useState3, 2),\n isRunning = _useState4[0],\n setIsRunning = _useState4[1];\n\n var intervalRef = (0, _react.useRef)();\n\n function clearIntervalRef() {\n if (intervalRef.current) {\n setIsRunning(false);\n clearInterval(intervalRef.current);\n intervalRef.current = undefined;\n }\n }\n\n function start() {\n if (!intervalRef.current) {\n setIsRunning(true);\n intervalRef.current = setInterval(function () {\n return setSeconds(function (prevSeconds) {\n return prevSeconds + 1;\n });\n }, 1000);\n }\n }\n\n function pause() {\n clearIntervalRef();\n }\n\n function reset(offset) {\n clearIntervalRef();\n setSeconds(_utils.Time.getSecondsFromExpiry(offset || 0));\n if (autoStart) {\n start();\n }\n }\n\n // didMount effect\n (0, _react.useEffect)(function () {\n if (autoStart) {\n start();\n }\n return clearIntervalRef;\n }, []);\n\n return _extends({}, _utils.Time.getTimeFromSeconds(seconds), { start: start, pause: pause, reset: reset, isRunning: isRunning\n });\n}\n\n//# sourceURL=webpack:///./src/useStopwatch.js?"); +eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\n\nvar _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };\n\nvar _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i[\"return\"]) _i[\"return\"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError(\"Invalid attempt to destructure non-iterable instance\"); } }; }();\n\nexports.default = useStopwatch;\n\nvar _react = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n\nvar _utils = __webpack_require__(/*! ./utils */ \"./src/utils/index.js\");\n\nvar _hooks = __webpack_require__(/*! ./hooks */ \"./src/hooks/index.js\");\n\nfunction useStopwatch(_ref) {\n var autoStart = _ref.autoStart,\n offsetTimestamp = _ref.offsetTimestamp;\n\n var _useState = (0, _react.useState)(_utils.Time.getSecondsFromExpiry(offsetTimestamp || 0)),\n _useState2 = _slicedToArray(_useState, 2),\n seconds = _useState2[0],\n setSeconds = _useState2[1];\n\n var _useState3 = (0, _react.useState)(autoStart),\n _useState4 = _slicedToArray(_useState3, 2),\n isRunning = _useState4[0],\n setIsRunning = _useState4[1];\n\n (0, _hooks.useInterval)(function () {\n setSeconds(function (prevSeconds) {\n return prevSeconds + 1;\n });\n }, isRunning ? 1000 : null);\n\n function start() {\n setIsRunning(true);\n }\n\n function pause() {\n setIsRunning(false);\n }\n\n function reset(offset) {\n setIsRunning(autoStart);\n setSeconds(_utils.Time.getSecondsFromExpiry(offset || 0));\n }\n\n return _extends({}, _utils.Time.getTimeFromSeconds(seconds), { start: start, pause: pause, reset: reset, isRunning: isRunning\n });\n}\n\n//# sourceURL=webpack:///./src/useStopwatch.js?"); /***/ }), @@ -499,7 +499,7 @@ eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\n /***/ (function(module, exports, __webpack_require__) { "use strict"; -eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\n\nvar _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };\n\nvar _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i[\"return\"]) _i[\"return\"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError(\"Invalid attempt to destructure non-iterable instance\"); } }; }();\n\nexports.default = useTime;\n\nvar _react = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n\nvar _utils = __webpack_require__(/*! ./utils */ \"./src/utils/index.js\");\n\nfunction useTime(settings) {\n var _ref = settings || {},\n format = _ref.format;\n\n var _useState = (0, _react.useState)(_utils.Time.getSecondsFromTimeNow()),\n _useState2 = _slicedToArray(_useState, 2),\n seconds = _useState2[0],\n setSeconds = _useState2[1];\n\n var intervalRef = (0, _react.useRef)();\n\n function clearIntervalRef() {\n if (intervalRef.current) {\n clearInterval(intervalRef.current);\n intervalRef.current = undefined;\n }\n }\n\n function start() {\n if (!intervalRef.current) {\n intervalRef.current = setInterval(function () {\n return setSeconds(_utils.Time.getSecondsFromTimeNow());\n }, 1000);\n }\n }\n\n // didMount effect\n (0, _react.useEffect)(function () {\n start();\n return clearIntervalRef;\n }, []);\n\n return _extends({}, _utils.Time.getFormattedTimeFromSeconds(seconds, format));\n}\n\n//# sourceURL=webpack:///./src/useTime.js?"); +eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\n\nvar _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };\n\nvar _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i[\"return\"]) _i[\"return\"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError(\"Invalid attempt to destructure non-iterable instance\"); } }; }();\n\nexports.default = useTime;\n\nvar _react = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n\nvar _utils = __webpack_require__(/*! ./utils */ \"./src/utils/index.js\");\n\nvar _hooks = __webpack_require__(/*! ./hooks */ \"./src/hooks/index.js\");\n\nfunction useTime(_ref) {\n var format = _ref.format;\n\n var _useState = (0, _react.useState)(_utils.Time.getSecondsFromTimeNow()),\n _useState2 = _slicedToArray(_useState, 2),\n seconds = _useState2[0],\n setSeconds = _useState2[1];\n\n (0, _hooks.useInterval)(function () {\n setSeconds(_utils.Time.getSecondsFromTimeNow());\n }, 1000);\n\n return _extends({}, _utils.Time.getFormattedTimeFromSeconds(seconds, format));\n}\n\n//# sourceURL=webpack:///./src/useTime.js?"); /***/ }), @@ -511,7 +511,7 @@ eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\n /***/ (function(module, exports, __webpack_require__) { "use strict"; -eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\n\nvar _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };\n\nvar _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i[\"return\"]) _i[\"return\"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError(\"Invalid attempt to destructure non-iterable instance\"); } }; }();\n\nexports.default = useTimer;\n\nvar _react = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n\nvar _utils = __webpack_require__(/*! ./utils */ \"./src/utils/index.js\");\n\nvar _hooks = __webpack_require__(/*! ./hooks */ \"./src/hooks/index.js\");\n\nfunction useTimer(_ref) {\n var expiry = _ref.expiryTimestamp,\n onExpire = _ref.onExpire,\n autoStart = _ref.autoStart;\n\n var _useState = (0, _react.useState)(expiry),\n _useState2 = _slicedToArray(_useState, 2),\n expiryTimestamp = _useState2[0],\n setExpiryTimestamp = _useState2[1];\n\n var _useState3 = (0, _react.useState)(_utils.Time.getSecondsFromExpiry(expiryTimestamp)),\n _useState4 = _slicedToArray(_useState3, 2),\n seconds = _useState4[0],\n setSeconds = _useState4[1];\n\n var _useState5 = (0, _react.useState)(false),\n _useState6 = _slicedToArray(_useState5, 2),\n isRunning = _useState6[0],\n setIsRunning = _useState6[1];\n\n function handleExpire() {\n _utils.Validate.onExpire(onExpire) && onExpire();\n }\n\n function pause() {\n setIsRunning(false);\n }\n\n function start() {\n setIsRunning(true);\n }\n\n function resume() {\n var time = new Date();\n time.setSeconds(time.getSeconds() + seconds); // calculate new expiry timestamp based on last paused seconds count\n setExpiryTimestamp(time);\n setIsRunning(true);\n }\n\n function restart(newExpiryTimestamp) {\n setExpiryTimestamp(newExpiryTimestamp);\n }\n\n (0, _hooks.useInterval)(isRunning ? function () {\n var secondsValue = _utils.Time.getSecondsFromExpiry(expiryTimestamp);\n if (secondsValue <= 0) {\n handleExpire();\n }\n setSeconds(secondsValue);\n } : function () {}, 1000);\n\n (0, _react.useEffect)(function () {\n if (autoStart) {\n setIsRunning(true);\n }\n }, [autoStart]);\n\n return _extends({}, _utils.Time.getTimeFromSeconds(seconds), { start: start, pause: pause, resume: resume, restart: restart, isRunning: isRunning\n });\n}\n\n//# sourceURL=webpack:///./src/useTimer.js?"); +eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\n\nvar _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };\n\nvar _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i[\"return\"]) _i[\"return\"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError(\"Invalid attempt to destructure non-iterable instance\"); } }; }();\n\nexports.default = useTimer;\n\nvar _react = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n\nvar _utils = __webpack_require__(/*! ./utils */ \"./src/utils/index.js\");\n\nvar _hooks = __webpack_require__(/*! ./hooks */ \"./src/hooks/index.js\");\n\nfunction useTimer(_ref) {\n var expiry = _ref.expiryTimestamp,\n onExpire = _ref.onExpire,\n autoStart = _ref.autoStart;\n\n var _useState = (0, _react.useState)(expiry),\n _useState2 = _slicedToArray(_useState, 2),\n expiryTimestamp = _useState2[0],\n setExpiryTimestamp = _useState2[1];\n\n var _useState3 = (0, _react.useState)(_utils.Time.getSecondsFromExpiry(expiryTimestamp)),\n _useState4 = _slicedToArray(_useState3, 2),\n seconds = _useState4[0],\n setSeconds = _useState4[1];\n\n var _useState5 = (0, _react.useState)(autoStart),\n _useState6 = _slicedToArray(_useState5, 2),\n isRunning = _useState6[0],\n setIsRunning = _useState6[1];\n\n var _useState7 = (0, _react.useState)(autoStart),\n _useState8 = _slicedToArray(_useState7, 2),\n didStart = _useState8[0],\n setDidStart = _useState8[1];\n\n function handleExpire() {\n _utils.Validate.onExpire(onExpire) && onExpire();\n }\n\n function pause() {\n setIsRunning(false);\n }\n\n function resume() {\n var time = new Date();\n time.setSeconds(time.getSeconds() + seconds); // calculate new expiry timestamp based on last paused seconds count\n setExpiryTimestamp(time);\n setIsRunning(true);\n setSeconds(_utils.Time.getSecondsFromExpiry(time));\n }\n\n function start() {\n if (didStart) {\n setIsRunning(true);\n setSeconds(_utils.Time.getSecondsFromExpiry(expiryTimestamp));\n } else {\n resume();\n setDidStart(true);\n }\n }\n\n function restart(newExpiryTimestamp) {\n setDidStart(autoStart);\n setIsRunning(autoStart);\n setExpiryTimestamp(newExpiryTimestamp);\n setSeconds(_utils.Time.getSecondsFromExpiry(newExpiryTimestamp));\n }\n\n (0, _hooks.useInterval)(function () {\n var secondsValue = _utils.Time.getSecondsFromExpiry(expiryTimestamp);\n if (secondsValue <= 0) {\n handleExpire();\n }\n setSeconds(secondsValue);\n }, isRunning ? 1000 : null);\n\n return _extends({}, _utils.Time.getTimeFromSeconds(seconds), { start: start, pause: pause, resume: resume, restart: restart, isRunning: isRunning\n });\n}\n\n//# sourceURL=webpack:///./src/useTimer.js?"); /***/ }), diff --git a/src/useTimer.js b/src/useTimer.js index 2813123..41d21bc 100644 --- a/src/useTimer.js +++ b/src/useTimer.js @@ -2,52 +2,60 @@ import { useState } from 'react'; import { Time, Validate } from './utils'; import { useInterval } from './hooks'; +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(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() { Validate.onExpire(onExpire) && onExpire(); + setIsRunning(false); + setDelay(null); } function pause() { setIsRunning(false); } + 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 resume() { const time = new Date(); - time.setSeconds(time.getSeconds() + seconds); // calculate new expiry timestamp based on last paused seconds count - setExpiryTimestamp(time); - setIsRunning(true); - setSeconds(Time.getSecondsFromExpiry(time)); + time.setMilliseconds(time.getMilliseconds() + (seconds * 1000)); + restart(time, true); } function start() { if (didStart) { - setIsRunning(true); setSeconds(Time.getSecondsFromExpiry(expiryTimestamp)); + setIsRunning(true); } else { resume(); - setDidStart(true); } } - function restart(newExpiryTimestamp) { - setDidStart(autoStart); - setIsRunning(autoStart); - setExpiryTimestamp(newExpiryTimestamp); - setSeconds(Time.getSecondsFromExpiry(newExpiryTimestamp)); - } - useInterval(() => { + if (delay !== DEFAULT_DELAY) { + setDelay(DEFAULT_DELAY); + } const secondsValue = Time.getSecondsFromExpiry(expiryTimestamp); + setSeconds(secondsValue); if (secondsValue <= 0) { handleExpire(); } - setSeconds(secondsValue); - }, isRunning ? 1000 : null); + }, isRunning ? delay : null); return { ...Time.getTimeFromSeconds(seconds), start, pause, resume, restart, isRunning, From f36a87916467ee975b4e97f9f41532cf13df1de8 Mon Sep 17 00:00:00 2001 From: Amr Labib Date: Thu, 15 Apr 2021 00:47:54 +0400 Subject: [PATCH 09/10] remove old useTimer --- src/useTimer-old.js | 91 --------------------------------------------- 1 file changed, 91 deletions(-) delete mode 100644 src/useTimer-old.js diff --git a/src/useTimer-old.js b/src/useTimer-old.js deleted file mode 100644 index aa6e084..0000000 --- a/src/useTimer-old.js +++ /dev/null @@ -1,91 +0,0 @@ -import { useState, useEffect, useRef } from 'react'; -import { Time, Validate } from './utils'; - -export default function useTimer(settings) { - const { expiryTimestamp: expiry, onExpire } = settings || {}; - 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; - } - } - - 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); - } - } - - function pause() { - clearIntervalRef(); - } - - 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(); - setExpiryTimestamp(newExpiryTimestamp); - } - - 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); - } - - 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(); - } - } - return clearIntervalRef; - }, [expiryTimestamp]); - - - return { - ...Time.getTimeFromSeconds(seconds), start, pause, resume, restart, isRunning, - }; -} From 110228bfc98b6c4789534ec3cecd8ae50ca6ba7a Mon Sep 17 00:00:00 2001 From: Amr Labib Date: Thu, 15 Apr 2021 01:30:55 +0400 Subject: [PATCH 10/10] add autoStart of useTimer in Readme file --- readme.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/readme.md b/readme.md index f747d2a..ef8b809 100644 --- a/readme.md +++ b/readme.md @@ -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 ( @@ -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 |