useCountdown
A hook that allows to count down from a given number.
Example
import useCountdown from "@/hooks/useCountdown";
export default function UseCountdownExample() {
const [count, { startCountdown, stopCountdown, resetCountdown }] =
useCountdown({
countStart: 1000,
intervalMs: 10,
});
return (
<div className="flex flex-col items-center justify-center gap-4">
<div className="text-3xl font-bold">{count}</div>
<div className="flex gap-4">
<button onClick={startCountdown}>Start</button>
<button onClick={stopCountdown}>Stop</button>
<button onClick={resetCountdown}>Reset</button>
</div>
</div>
);
}
Code
import { useCallback, useEffect, useRef, useState } from "react";
type CountdownOptions = {
countStart: number;
intervalMs?: number;
isIncrement?: boolean;
countStop?: number;
};
type CountdownControllers = {
startCountdown: () => void;
stopCountdown: () => void;
resetCountdown: () => void;
};
/**
* A hook that allows to count down from a given number.
* @param {CountdownOptions} options - Options for the hook.
* @returns {[number, CountdownControllers]} An array of two elements:
* 1. The current count.
* 2. An object with functions to control the countdown.
*/
export default function useCountdown({
countStart,
countStop = 0,
intervalMs = 1000,
isIncrement = false,
}: CountdownOptions): [number, CountdownControllers] {
const savedCallback = useRef<Function>(() => {});
const [count, setCount] = useState(countStart);
const increment = () => setCount(count + 1);
const decrement = () => setCount(count - 1);
const resetCounter = () => setCount(countStart);
const [isCountdownRunning, setIsCountdownRunning] = useState(false);
const startCountdown = () => setIsCountdownRunning(true);
const stopCountdown = () => setIsCountdownRunning(false);
// Will set running false and reset the seconds to initial value.
const resetCountdown = useCallback(() => {
stopCountdown();
resetCounter();
}, [stopCountdown, resetCounter]);
const countdownCallback = useCallback(() => {
if (count === countStop) {
stopCountdown();
return;
}
if (isIncrement) {
increment();
} else {
decrement();
}
}, [count, countStop, decrement, increment, isIncrement, stopCountdown]);
useEffect(() => {
savedCallback.current = countdownCallback;
});
const delay = isCountdownRunning ? intervalMs : null;
useEffect(() => {
if (delay !== null) {
const interval = setInterval(() => savedCallback.current(), delay || 0);
return () => clearInterval(interval);
}
return undefined;
}, [delay]);
return [count, { startCountdown, stopCountdown, resetCountdown }];
}