From 17a1995a0195099be96fe4170d5f5871a5dc1caf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Bibiano?= Date: Mon, 2 Mar 2026 11:43:08 -0300 Subject: [PATCH 1/2] feat(useCountdown): add autoStart and onFinish options --- .../src/useCountdown/useCountdown.demo.tsx | 16 +++++- .../src/useCountdown/useCountdown.md | 5 ++ .../src/useCountdown/useCountdown.test.ts | 38 +++++++++++++ .../src/useCountdown/useCountdown.ts | 56 +++++++++++++------ 4 files changed, 95 insertions(+), 20 deletions(-) diff --git a/packages/usehooks-ts/src/useCountdown/useCountdown.demo.tsx b/packages/usehooks-ts/src/useCountdown/useCountdown.demo.tsx index 5bc38ab9..a9da3d69 100644 --- a/packages/usehooks-ts/src/useCountdown/useCountdown.demo.tsx +++ b/packages/usehooks-ts/src/useCountdown/useCountdown.demo.tsx @@ -6,18 +6,30 @@ import { useCountdown } from './useCountdown' export default function Component() { const [intervalValue, setIntervalValue] = useState(1000) + const [message, setMessage] = useState('Running...') + const [count, { startCountdown, stopCountdown, resetCountdown }] = useCountdown({ - countStart: 60, + countStart: 10, + countStop: 0, intervalMs: intervalValue, + autoStart: true, + onFinish: () => setMessage('Finished!'), }) const handleChangeIntervalValue = (event: ChangeEvent) => { setIntervalValue(Number(event.target.value)) } + + const handleReset = () => { + setMessage('Running...') + resetCountdown() + } + return (

Count: {count}

+

{message}

- +
) } diff --git a/packages/usehooks-ts/src/useCountdown/useCountdown.md b/packages/usehooks-ts/src/useCountdown/useCountdown.md index e9ccc295..2cc21bf6 100644 --- a/packages/usehooks-ts/src/useCountdown/useCountdown.md +++ b/packages/usehooks-ts/src/useCountdown/useCountdown.md @@ -4,6 +4,11 @@ A simple countdown implementation. Support increment and decrement. NEW VERSION: A simple countdown implementation. Accepts `countStop`(new), `countStart` (was `seconds`), `intervalMs`(was `interval`) and `isIncrement` as keys of the call argument. Support increment and decrement. Will stop when at `countStop`. +Also supports: + +- `autoStart` to start immediately on mount. +- `onFinish` callback when the counter reaches `countStop`. + Related hooks: - [`useBoolean()`](/react-hook/use-boolean) diff --git a/packages/usehooks-ts/src/useCountdown/useCountdown.test.ts b/packages/usehooks-ts/src/useCountdown/useCountdown.test.ts index 74949749..fd9f35ff 100644 --- a/packages/usehooks-ts/src/useCountdown/useCountdown.test.ts +++ b/packages/usehooks-ts/src/useCountdown/useCountdown.test.ts @@ -181,4 +181,42 @@ describe('useCountdown()', () => { act(result.current[1].resetCountdown) expect(result.current[0]).toBe(60) }) + + it('should auto start when autoStart is true', () => { + const { result } = renderHook(() => + useCountdown({ countStart: 5, countStop: 0, intervalMs: 1000, autoStart: true }), + ) + + act(() => { + vitest.advanceTimersByTime(2000) + }) + + expect(result.current[0]).toBe(3) + }) + + it('should call onFinish when countdown reaches countStop', () => { + const onFinish = vitest.fn() + + renderHook(() => + useCountdown({ + countStart: 3, + countStop: 0, + intervalMs: 1000, + autoStart: true, + onFinish, + }), + ) + + act(() => { + vitest.advanceTimersByTime(3000) + }) + + expect(onFinish).toHaveBeenCalledTimes(1) + + act(() => { + vitest.advanceTimersByTime(3000) + }) + + expect(onFinish).toHaveBeenCalledTimes(1) + }) }) diff --git a/packages/usehooks-ts/src/useCountdown/useCountdown.ts b/packages/usehooks-ts/src/useCountdown/useCountdown.ts index ed33b110..ec7c2850 100644 --- a/packages/usehooks-ts/src/useCountdown/useCountdown.ts +++ b/packages/usehooks-ts/src/useCountdown/useCountdown.ts @@ -14,6 +14,7 @@ type CountdownOptions = { * @default 1000 */ intervalMs?: number + /** * True if the countdown is increment. * @default false @@ -25,6 +26,17 @@ type CountdownOptions = { * @default 0 */ countStop?: number + + /** + * Starts the countdown immediately when the hook mounts. + * @default false + */ + autoStart?: boolean + + /** + * Callback fired once the countdown reaches `countStop`. + */ + onFinish?: () => void } /** The countdown's controllers. */ @@ -49,6 +61,8 @@ type CountdownControllers = { * countStart: 10, * intervalMs: 1000, * isIncrement: false, + * autoStart: true, + * onFinish: () => console.log('Done!'), * }); * ``` */ @@ -57,13 +71,10 @@ export function useCountdown({ countStop = 0, intervalMs = 1000, isIncrement = false, + autoStart = false, + onFinish, }: CountdownOptions): [number, CountdownControllers] { - const { - count, - increment, - decrement, - reset: resetCounter, - } = useCounter(countStart) + const { count, reset: resetCounter, setCount } = useCounter(countStart) /* * Note: used to control the useInterval @@ -75,7 +86,7 @@ export function useCountdown({ value: isCountdownRunning, setTrue: startCountdown, setFalse: stopCountdown, - } = useBoolean(false) + } = useBoolean(autoStart) // Will set running false and reset the seconds to initial value. const resetCountdown = useCallback(() => { @@ -84,17 +95,26 @@ export function useCountdown({ }, [stopCountdown, resetCounter]) const countdownCallback = useCallback(() => { - if (count === countStop) { - stopCountdown() - return - } - - if (isIncrement) { - increment() - } else { - decrement() - } - }, [count, countStop, decrement, increment, isIncrement, stopCountdown]) + setCount(previousCount => { + if (previousCount === countStop) { + stopCountdown() + onFinish?.() + return previousCount + } + + const nextCount = isIncrement ? previousCount + 1 : previousCount - 1 + const willReachStop = isIncrement + ? nextCount >= countStop + : nextCount <= countStop + + if (willReachStop) { + stopCountdown() + onFinish?.() + } + + return nextCount + }) + }, [countStop, isIncrement, onFinish, setCount, stopCountdown]) useInterval(countdownCallback, isCountdownRunning ? intervalMs : null) From b3510de42f307e5bcc6d6c12a030d18263343a66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Bibiano?= Date: Mon, 2 Mar 2026 13:26:43 -0300 Subject: [PATCH 2/2] chore(changeset): add patch release note for useCountdown options --- .changeset/quiet-rabbits-share.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/quiet-rabbits-share.md diff --git a/.changeset/quiet-rabbits-share.md b/.changeset/quiet-rabbits-share.md new file mode 100644 index 00000000..4366db98 --- /dev/null +++ b/.changeset/quiet-rabbits-share.md @@ -0,0 +1,5 @@ +--- +'usehooks-ts': patch +--- + +feat(useCountdown): add `autoStart` and `onFinish` options.