Skip to content
Open
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
11 changes: 4 additions & 7 deletions packages/core/src/bundle/hooks/useInterval/useInterval.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ import { useEffect, useRef, useState } from 'react';
*/
export const useInterval = (...params) => {
const callback = params[0];
const interval = (typeof params[1] === 'number' ? params[1] : params[1].interval) ?? 1000;
const interval = (typeof params[1] === 'number' ? params[1] : params[1]?.interval) ?? 1000;
const options = typeof params[1] === 'object' ? params[1] : params[2];
const immediately = options?.immediately ?? true;
const [active, setActive] = useState(immediately ?? true);
const [active, setActive] = useState(immediately);
const intervalIdRef = useRef(undefined);
const internalCallbackRef = useRef(callback);
internalCallbackRef.current = callback;
Expand All @@ -39,11 +39,8 @@ export const useInterval = (...params) => {
};
}, [active, interval]);
const pause = () => setActive(false);
const resume = () => {
if (interval <= 0) return;
setActive(true);
};
const toggle = (value = !active) => setActive(value);
const resume = () => setActive(true);
const toggle = () => setActive((prev) => !prev);
return {
active,
pause,
Expand Down
58 changes: 14 additions & 44 deletions packages/core/src/bundle/hooks/useStopwatch/useStopwatch.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,74 +22,44 @@ const getStopwatchTime = (time) => {
*
* @overload
* @param {number} [initialTime=0] The initial time of the timer
* @param {boolean} [options.enabled=true] The enabled state of the timer
* @param {boolean} [options.immediately=false] Start the stopwatch immediately
* @returns {UseStopwatchReturn} An object containing the current time and functions to interact with the timer
*
* @example
* const { seconds, minutes, start, pause, reset } = useStopwatch(1000, { enabled: false });
* const { seconds, minutes, start, pause, reset } = useStopwatch(1000, { immediately: false });
*
* @overload
* @param {number} [options.initialTime=0] -The initial time of the timer
* @param {boolean} [options.enabled=true] The enabled state of the timer
* @param {number} [options.initialTime=0] The initial time of the timer
* @param {boolean} [options.immediately=false] Start the stopwatch immediately
* @returns {UseStopwatchReturn} An object containing the current time and functions to interact with the timer
*
* @example
* const { seconds, minutes, start, pause, reset } = useStopwatch({ initialTime: 1000, enabled: false });
* const { seconds, minutes, start, pause, reset } = useStopwatch({ initialTime: 1000, immediately: false });
*/
export const useStopwatch = (...params) => {
const initialTime = (typeof params[0] === 'number' ? params[0] : params[0]?.initialTime) ?? 0;
const options = typeof params[0] === 'number' ? params[1] : params[0];
const immediately = options?.immediately ?? false;
const [time, setTime] = useState(getStopwatchTime(initialTime));
const [paused, setPaused] = useState(!immediately && !initialTime);
const [count, setCount] = useState(initialTime);
const [paused, setPaused] = useState(!immediately);
useEffect(() => {
setCount(initialTime);
}, [initialTime]);
useEffect(() => {
if (paused) return;
const onInterval = () => {
setTime((prevTime) => {
const updatedCount = prevTime.count + 1;
if (updatedCount % 60 === 0) {
return {
...prevTime,
minutes: prevTime.minutes + 1,
seconds: 0,
count: updatedCount
};
}
if (updatedCount % (60 * 60) === 0) {
return {
...prevTime,
hours: prevTime.hours + 1,
minutes: 0,
seconds: 0,
count: updatedCount
};
}
if (updatedCount % (60 * 60 * 24) === 0) {
return {
...prevTime,
days: prevTime.days + 1,
hours: 0,
minutes: 0,
seconds: 0,
count: updatedCount
};
}
return {
...prevTime,
seconds: prevTime.seconds + 1,
count: updatedCount
};
});
setCount((prevCount) => prevCount + 1);
};
const interval = setInterval(() => onInterval(), 1000);
const interval = setInterval(onInterval, 1000);
return () => clearInterval(interval);
}, [paused]);
const time = getStopwatchTime(count);
return {
...time,
paused,
pause: () => setPaused(true),
start: () => setPaused(false),
reset: () => setTime(getStopwatchTime(initialTime)),
reset: () => setCount(initialTime),
toggle: () => setPaused((prevPause) => !prevPause)
};
};
12 changes: 5 additions & 7 deletions packages/core/src/hooks/useInterval/useInterval.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,15 @@ export const useInterval = ((...params: any[]): UseIntervalReturn => {
const interval =
((typeof params[1] === 'number'
? params[1]
: (params[1] as UseIntervalOptions & { interval?: number }).interval) as number) ?? 1000;
: (params[1] as (UseIntervalOptions & { interval?: number }) | undefined)
?.interval) as number) ?? 1000;
const options =
typeof params[1] === 'object'
? (params[1] as (UseIntervalOptions & { interval?: number }) | undefined)
: (params[2] as UseIntervalOptions | undefined);
const immediately = options?.immediately ?? true;

const [active, setActive] = useState<boolean>(immediately ?? true);
const [active, setActive] = useState<boolean>(immediately);

const intervalIdRef = useRef<ReturnType<typeof setInterval>>(undefined);
const internalCallbackRef = useRef(callback);
Expand All @@ -76,12 +77,9 @@ export const useInterval = ((...params: any[]): UseIntervalReturn => {

const pause = () => setActive(false);

const resume = () => {
if (interval <= 0) return;
setActive(true);
};
const resume = () => setActive(true);

const toggle = (value = !active) => setActive(value);
const toggle = () => setActive((prev) => !prev);

return {
active,
Expand Down
54 changes: 54 additions & 0 deletions packages/core/src/hooks/useStopwatch/useStopwatch.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,3 +167,57 @@ it('Should cleanup interval on unmount', () => {
expect(clearIntervalSpy).toHaveBeenCalled();
clearIntervalSpy.mockRestore();
});

it('Should correctly transition from 59 seconds to 1 minute', () => {
const { result } = renderHook(useStopwatch);

act(() => result.current.start());

act(() => vi.advanceTimersByTime(59_000));

expect(result.current.seconds).toBe(59);
expect(result.current.minutes).toBe(0);

act(() => vi.advanceTimersByTime(1_000));

expect(result.current.seconds).toBe(0);
expect(result.current.minutes).toBe(1);
});

it('Should correctly transition from 59 minutes 59 seconds to 1 hour', () => {
const { result } = renderHook(useStopwatch);

act(() => result.current.start());

act(() => vi.advanceTimersByTime(3_599_000));

expect(result.current.hours).toBe(0);
expect(result.current.minutes).toBe(59);
expect(result.current.seconds).toBe(59);

act(() => vi.advanceTimersByTime(1_000));

expect(result.current.hours).toBe(1);
expect(result.current.minutes).toBe(0);
expect(result.current.seconds).toBe(0);
});

it('Should correctly transition from 23:59:59 to 1 day', () => {
const { result } = renderHook(useStopwatch);

act(() => result.current.start());

act(() => vi.advanceTimersByTime(86_399_000));

expect(result.current.days).toBe(0);
expect(result.current.hours).toBe(23);
expect(result.current.minutes).toBe(59);
expect(result.current.seconds).toBe(59);

act(() => vi.advanceTimersByTime(1_000));

expect(result.current.days).toBe(1);
expect(result.current.hours).toBe(0);
expect(result.current.minutes).toBe(0);
expect(result.current.seconds).toBe(0);
});
66 changes: 16 additions & 50 deletions packages/core/src/hooks/useStopwatch/useStopwatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,6 @@ export interface UseStopwatchReturn {
hours: number;
/** The minute count of the stopwatch */
minutes: number;
/** The over state of the stopwatch */
over: boolean;
/** The paused state of the stopwatch */
paused: boolean;
/** The second count of the stopwatch */
Expand Down Expand Up @@ -62,19 +60,19 @@ interface UseStopwatch {
*
* @overload
* @param {number} [initialTime=0] The initial time of the timer
* @param {boolean} [options.enabled=true] The enabled state of the timer
* @param {boolean} [options.immediately=false] Start the stopwatch immediately
* @returns {UseStopwatchReturn} An object containing the current time and functions to interact with the timer
*
* @example
* const { seconds, minutes, start, pause, reset } = useStopwatch(1000, { enabled: false });
* const { seconds, minutes, start, pause, reset } = useStopwatch(1000, { immediately: false });
*
* @overload
* @param {number} [options.initialTime=0] -The initial time of the timer
* @param {boolean} [options.enabled=true] The enabled state of the timer
* @param {number} [options.initialTime=0] The initial time of the timer
* @param {boolean} [options.immediately=false] Start the stopwatch immediately
* @returns {UseStopwatchReturn} An object containing the current time and functions to interact with the timer
*
* @example
* const { seconds, minutes, start, pause, reset } = useStopwatch({ initialTime: 1000, enabled: false });
* const { seconds, minutes, start, pause, reset } = useStopwatch({ initialTime: 1000, immediately: false });
*/
export const useStopwatch = ((...params: any[]) => {
const initialTime =
Expand All @@ -89,63 +87,31 @@ export const useStopwatch = ((...params: any[]) => {

const immediately = options?.immediately ?? false;

const [time, setTime] = useState(getStopwatchTime(initialTime));
const [paused, setPaused] = useState(!immediately && !initialTime);
const [count, setCount] = useState(initialTime);
const [paused, setPaused] = useState(!immediately);

useEffect(() => {
setCount(initialTime);
}, [initialTime]);

useEffect(() => {
if (paused) return;
const onInterval = () => {
setTime((prevTime) => {
const updatedCount = prevTime.count + 1;

if (updatedCount % 60 === 0) {
return {
...prevTime,
minutes: prevTime.minutes + 1,
seconds: 0,
count: updatedCount
};
}

if (updatedCount % (60 * 60) === 0) {
return {
...prevTime,
hours: prevTime.hours + 1,
minutes: 0,
seconds: 0,
count: updatedCount
};
}

if (updatedCount % (60 * 60 * 24) === 0) {
return {
...prevTime,
days: prevTime.days + 1,
hours: 0,
minutes: 0,
seconds: 0,
count: updatedCount
};
}

return {
...prevTime,
seconds: prevTime.seconds + 1,
count: updatedCount
};
});
setCount((prevCount) => prevCount + 1);
};

const interval = setInterval(() => onInterval(), 1000);
const interval = setInterval(onInterval, 1000);
return () => clearInterval(interval);
}, [paused]);

const time = getStopwatchTime(count);

return {
...time,
paused,
pause: () => setPaused(true),
start: () => setPaused(false),
reset: () => setTime(getStopwatchTime(initialTime)),
reset: () => setCount(initialTime),
toggle: () => setPaused((prevPause) => !prevPause)
};
}) as UseStopwatch;