Skip to content

Feature/support milliseconds #116

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 17 commits into from
Mar 5, 2025
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
6 changes: 3 additions & 3 deletions demo/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,11 @@ export default function App() {
<p>
React timer hook is a custom <a href="https://reactjs.org/docs/hooks-intro.html" target="_blank">react hook</a> built to handle timer, stopwatch, and time logic/state in your react component.
</p>
<UseTimerDemo expiryTimestamp={time} />
<UseTimerDemo expiryTimestamp={time} interval={20} />
<Separator />
<UseStopwatchDemo />
<UseStopwatchDemo interval={20} />
<Separator />
<UseTimeDemo />
<UseTimeDemo interval={20} />
</Container>
</div>
);
Expand Down
26 changes: 19 additions & 7 deletions demo/components/Digit.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@ const SingleDigit = styled.span`
border-radius: 5px;
padding: 10px 12px;
color: white;
&:first-child {
margin-right: 2px;
margin-right: 2px;
&:last-child {
margin-right: ;
}
&:after {
position: absolute;
Expand All @@ -48,18 +49,29 @@ const SingleDigit = styled.span`
}
`;

export default function Digit({ value, title }) {
const leftDigit = value >= 10 ? value.toString()[0] : '0';
const rightDigit = value >= 10 ? value.toString()[1] : value.toString();
export default function Digit({ value, title, isMIlliseconds }) {
const digits = value.toString().padStart(4, '0');
return (
<Container>
<Title>{title}</Title>
<DigitContainer>
{isMIlliseconds ?
<>
<SingleDigit>
{digits[0]}
</SingleDigit>
<SingleDigit>
{digits[1]}
</SingleDigit>
</>
:
null
}
<SingleDigit>
{leftDigit}
{digits[2]}
</SingleDigit>
<SingleDigit>
{rightDigit}
{digits[3]}
</SingleDigit>
</DigitContainer>
</Container>
Expand Down
8 changes: 7 additions & 1 deletion demo/components/TimerStyled.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const Separtor = styled.span`
margin: 5px 0px;
`;

export default function TimerStyled({ seconds, minutes, hours, days }) {
export default function TimerStyled({ milliseconds, seconds, minutes, hours, days, enableMilliseconds }) {
return (
<TimerContainer>
{days !== undefined ? <Digit value={days} title="DAYS" addSeparator /> : null}
Expand All @@ -36,6 +36,12 @@ export default function TimerStyled({ seconds, minutes, hours, days }) {
<Digit value={minutes} title="MINUTES" addSeparator />
<SepartorContainer><Separtor /><Separtor /></SepartorContainer>
<Digit value={seconds} title="SECONDS" />
{enableMilliseconds ?
<>
<SepartorContainer><Separtor /><Separtor /></SepartorContainer>
<Digit value={milliseconds} title="MILLISECONDS" isMIlliseconds />
</>
: null }
</TimerContainer>
);
}
9 changes: 6 additions & 3 deletions demo/components/UseStopwatchDemo.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,25 @@ import { useStopwatch } from '../../src/index';
import Button from './Button';
import TimerStyled from './TimerStyled';

export default function UseStopwatchDemo() {
export default function UseStopwatchDemo({ interval }) {
const time = new Date();
time.setMilliseconds(time.getMilliseconds() + 10000);
const {
milliseconds,
seconds,
minutes,
hours,
days,
start,
pause,
reset,
} = useStopwatch({ autoStart: true });
} = useStopwatch({ autoStart: true, interval, offsetTimestamp: 0 });


return (
<div>
<h2>UseStopwatch Demo</h2>
<TimerStyled seconds={seconds} minutes={minutes} hours={hours} days={days} />
<TimerStyled milliseconds={milliseconds} seconds={seconds} minutes={minutes} hours={hours} days={days} enableMilliseconds={interval < 1000} />
<Button onClick={start}>Start</Button>
<Button onClick={pause}>Pause</Button>
<Button onClick={reset}>Reset</Button>
Expand Down
7 changes: 4 additions & 3 deletions demo/components/UseTimeDemo.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,19 @@ import React from 'react';
import { useTime } from '../../src/index';
import TimerStyled from './TimerStyled';

export default function UseTimeDemo() {
export default function UseTimeDemo({ interval }) {
const {
milliseconds,
seconds,
minutes,
hours,
} = useTime({ });
} = useTime({ interval });

return (
<div>
<h2>UseTime Demo</h2>
<div>
<TimerStyled seconds={seconds} minutes={minutes} hours={hours} />
<TimerStyled milliseconds={milliseconds} seconds={seconds} minutes={minutes} hours={hours} enableMilliseconds={interval < 1000} />
</div>
</div>
);
Expand Down
7 changes: 4 additions & 3 deletions demo/components/UseTimerDemo.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import { useTimer } from '../../src/index';
import TimerStyled from './TimerStyled';
import Button from './Button';

export default function UseTimerDemo({ expiryTimestamp }) {
export default function UseTimerDemo({ expiryTimestamp, interval }) {
const {
milliseconds,
seconds,
minutes,
hours,
Expand All @@ -13,12 +14,12 @@ export default function UseTimerDemo({ expiryTimestamp }) {
pause,
resume,
restart,
} = useTimer({ expiryTimestamp, onExpire: () => console.warn('onExpire called') });
} = useTimer({ expiryTimestamp, onExpire: () => console.warn('onExpire called'), interval });

return (
<div>
<h2>UseTimer Demo</h2>
<TimerStyled seconds={seconds} minutes={minutes} hours={hours} days={days} />
<TimerStyled milliseconds={milliseconds} seconds={seconds} minutes={minutes} hours={hours} days={days} enableMilliseconds={interval < 1000} />
<Button type="button" onClick={start}>Start</Button>
<Button type="button" onClick={pause}>Pause</Button>
<Button type="button" onClick={resume}>Resume</Button>
Expand Down
39 changes: 25 additions & 14 deletions docs/index.js

Large diffs are not rendered by default.

22 changes: 16 additions & 6 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { useTimer } from 'react-timer-hook';
function MyTimer({ expiryTimestamp }) {
const {
totalSeconds,
milliseconds,
seconds,
minutes,
hours,
Expand All @@ -34,15 +35,15 @@ function MyTimer({ expiryTimestamp }) {
pause,
resume,
restart,
} = useTimer({ expiryTimestamp, onExpire: () => console.warn('onExpire called') });
} = useTimer({ expiryTimestamp, onExpire: () => console.warn('onExpire called'), interval: 20 });


return (
<div style={{textAlign: 'center'}}>
<h1>react-timer-hook </h1>
<p>Timer Demo</p>
<div style={{fontSize: '100px'}}>
<span>{days}</span>:<span>{hours}</span>:<span>{minutes}</span>:<span>{seconds}</span>
<span>{days}</span>:<span>{hours}</span>:<span>{minutes}</span>:<span>{seconds}</span>:<span>{milliseconds}</span>
</div>
<p>{isRunning ? 'Running' : 'Not running'}</p>
<button onClick={start}>Start</button>
Expand Down Expand Up @@ -75,18 +76,21 @@ export default function App() {
| --- | --- | --- | ---- |
| expiryTimestamp | Date object | YES | this will define for how long the timer will be running |
| autoStart | boolean | No | flag to decide if timer should start automatically, by default it is set to `true` |
| interval | number | No | value to change the interval of the timer, by default it is set to 1000ms. Note: this value will not affect the timer, it will just define the frequency used to calculate the current timer values. For example, if you have a use case where milliseconds are used, you need to use a smaller value for the interval, for example, 20ms or 100ms based on your needs. |
| onExpire | Function | No | callback function to be executed once countdown timer is expired |


### Values

| key | Type | Description |
| --- | --- | ---- |
| milliseconds | number | milliseconds value, to get accurate ms values you need to set interval to a smaller value example: 20ms |
| seconds | number | seconds value |
| minutes | number | minutes value |
| hours | number | hours value |
| days | number | days value |
| totalSeconds | number | total number of seconds left in timer NOT converted to minutes, hours or days |
| totalMilliseconds | number | total number of milliseconds left in timer NOT converted to minutes, hours or days |
| isRunning | boolean | flag to indicate if timer is running or not |
| pause | function | function to be called to pause timer |
| start | function | function if called after pause the timer will continue based on original expiryTimestamp |
Expand All @@ -107,6 +111,7 @@ import { useStopwatch } from 'react-timer-hook';
function MyStopwatch() {
const {
totalSeconds,
milliseconds,
seconds,
minutes,
hours,
Expand All @@ -115,15 +120,15 @@ function MyStopwatch() {
start,
pause,
reset,
} = useStopwatch({ autoStart: true });
} = useStopwatch({ autoStart: true, interval: 20 });


return (
<div style={{textAlign: 'center'}}>
<h1>react-timer-hook</h1>
<p>Stopwatch Demo</p>
<div style={{fontSize: '100px'}}>
<span>{days}</span>:<span>{hours}</span>:<span>{minutes}</span>:<span>{seconds}</span>
<span>{days}</span>:<span>{hours}</span>:<span>{minutes}</span>:<span>{seconds}</span>:<span>{milliseconds}</span>
</div>
<p>{isRunning ? 'Running' : 'Not running'}</p>
<button onClick={start}>Start</button>
Expand All @@ -148,11 +153,13 @@ export default function App() {
| --- | --- | --- | ---- |
| autoStart | boolean | No | if set to `true` stopwatch will auto start, by default it is set to `false` |
| offsetTimestamp | Date object | No | this will define the initial stopwatch offset example: `const stopwatchOffset = new Date(); stopwatchOffset.setSeconds(stopwatchOffset.getSeconds() + 300);` this will result in a 5 minutes offset and stopwatch will start from 0:0:5:0 instead of 0:0:0:0 |
| interval | number | No | value to change the interval of the stopwatch, by default it is set to 1000ms. Note: this value will not affect the stopwatch, it will just define the frequency used to calculate the current timer values. For example, if you have a use case where milliseconds are used, you need to use a smaller value for the interval, for example, 20ms or 100ms based on your needs. |

### Values

| key | Type | Description |
| --- | --- | ---- |
| milliseconds | number | milliseconds value, to get accurate ms values you need to set interval to a smaller value example: 20ms |
| seconds | number | seconds value |
| minutes | number | minutes value |
| hours | number | hours value |
Expand All @@ -177,18 +184,19 @@ import { useTime } from 'react-timer-hook';

function MyTime() {
const {
milliseconds,
seconds,
minutes,
hours,
ampm,
} = useTime({ format: '12-hour'});
} = useTime({ format: '12-hour', interval: 20 });

return (
<div style={{textAlign: 'center'}}>
<h1>react-timer-hook </h1>
<p>Current Time Demo</p>
<div style={{fontSize: '100px'}}>
<span>{hours}</span>:<span>{minutes}</span>:<span>{seconds}</span><span>{ampm}</span>
<span>{hours}</span>:<span>{minutes}</span>:<span>{seconds}</span><span>{milliseconds}</span><span>{ampm}</span>
</div>
</div>
);
Expand All @@ -208,11 +216,13 @@ export default function App() {
| key | Type | Required | Description |
| --- | --- | --- | ---- |
| format | string | No | if set to `12-hour` time will be formatted with am/pm |
| interval | number | No | value to change the interval of the time, by default it is set to 1000ms. Note: this value will not affect the thime, it will just define the frequency used to calculate the current time values. For example, if you have a use case where milliseconds are used, you need to use a smaller value for the interval, for example, 20ms or 100ms based on your needs. |

### Values

| key | Type | Description |
| --- | --- | ---- |
| milliseconds | number | milliseconds value |
| seconds | number | seconds value |
| minutes | number | minutes value |
| hours | number | hours value |
Expand Down
7 changes: 7 additions & 0 deletions src/constants/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const SECOND_INTERVAL = 1000;
const PRECISION_COUNTER_LIMIT = 5;

export {
SECOND_INTERVAL,
PRECISION_COUNTER_LIMIT,
};
7 changes: 7 additions & 0 deletions src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ interface TimerSettings {
autoStart?: boolean;
expiryTimestamp: Date;
onExpire?: () => void;
enableMilliseconds?: boolean;
}

interface TimerResult {
totalMilliseconds: number;
totalSeconds: number;
milliseconds: number;
seconds: number;
minutes: number;
hours: number;
Expand All @@ -22,10 +25,13 @@ export function useTimer(settings: TimerSettings): TimerResult
interface StopwatchSettings {
autoStart?: boolean;
offsetTimestamp?: Date;
enableMilliseconds?: boolean;
}

interface StopwatchResult {
totalMilliseconds: number;
totalSeconds: number;
milliseconds: number;
seconds: number;
minutes: number;
hours: number;
Expand All @@ -43,6 +49,7 @@ interface TimeSettings {
}

interface TimeResult {
milliseconds: number;
seconds: number;
minutes: number;
hours: number;
Expand Down
43 changes: 25 additions & 18 deletions src/useStopwatch.js
Original file line number Diff line number Diff line change
@@ -1,39 +1,46 @@
import { useState, useCallback } from 'react';
import { Time } from './utils';
import { useInterval } from './hooks';
import { SECOND_INTERVAL } from './constants';

export default function useStopwatch({ autoStart, offsetTimestamp } = {}) {
const [passedSeconds, setPassedSeconds] = useState(Time.getSecondsFromExpiry(offsetTimestamp, true) || 0);
const [prevTime, setPrevTime] = useState(new Date());
const [seconds, setSeconds] = useState(passedSeconds + Time.getSecondsFromPrevTime(prevTime || 0, true));
export default function useStopwatch({ autoStart, offsetTimestamp, interval: customInterval = SECOND_INTERVAL } = {}) {
const offsetMilliseconds = Time.getMillisecondsFromExpiry(offsetTimestamp) || 0;
const [prevTime, setPrevTime] = useState(new Date() - new Date(offsetMilliseconds));
const [milliseconds, setMilliseconds] = useState(Time.getMillisecondsFromPrevTime(prevTime || 0));
const [isRunning, setIsRunning] = useState(autoStart);
const millisecondsInitialOffset = SECOND_INTERVAL - (milliseconds % SECOND_INTERVAL);
const [interval, setInterval] = useState(customInterval < millisecondsInitialOffset ? customInterval : millisecondsInitialOffset);

useInterval(() => {
setSeconds(passedSeconds + Time.getSecondsFromPrevTime(prevTime, true));
}, isRunning ? 1000 : null);
if (interval !== customInterval) {
setInterval(customInterval);
}

setMilliseconds(Time.getMillisecondsFromPrevTime(prevTime));
}, isRunning ? interval : null);

const start = useCallback(() => {
const newPrevTime = new Date();
setPrevTime(newPrevTime);
setPrevTime(new Date() - new Date(milliseconds));
setIsRunning(true);
setSeconds(passedSeconds + Time.getSecondsFromPrevTime(newPrevTime, true));
}, [passedSeconds]);
}, [milliseconds]);

const pause = useCallback(() => {
setPassedSeconds(seconds);
setMilliseconds(Time.getMillisecondsFromPrevTime(prevTime));
setIsRunning(false);
}, [seconds]);
}, [prevTime]);

const reset = useCallback((offset = 0, newAutoStart = true) => {
const newPassedSeconds = Time.getSecondsFromExpiry(offset, true) || 0;
const newPrevTime = new Date();
const newOffsetMilliseconds = Time.getMillisecondsFromExpiry(offset) || 0;
const newPrevTime = new Date() - new Date(newOffsetMilliseconds);
const newMilliseconds = Time.getMillisecondsFromPrevTime(newPrevTime);
const millisecondsOffset = SECOND_INTERVAL - (newMilliseconds % SECOND_INTERVAL);
setPrevTime(newPrevTime);
setPassedSeconds(newPassedSeconds);
setMilliseconds(newMilliseconds);
setInterval(customInterval < millisecondsOffset ? customInterval : millisecondsOffset);
setIsRunning(newAutoStart);
setSeconds(newPassedSeconds + Time.getSecondsFromPrevTime(newPrevTime, true));
}, []);
}, [customInterval]);

return {
...Time.getTimeFromSeconds(seconds), start, pause, reset, isRunning,
...Time.getTimeFromMilliseconds(milliseconds, false), start, pause, reset, isRunning,
};
}
Loading