useCountdown Hook
A custom React Hook for countdown timer with TypeScript support
Code
import { useState, useEffect, useRef, useCallback } from 'react';
type CountdownState = {
seconds: number;
minutes: number;
hours: number;
days: number;
isRunning: boolean;
isFinished: boolean;
};
function useCountdown(
targetDate: Date | number,
autoStart = true
): {
state: CountdownState;
start: () => void;
pause: () => void;
reset: () => void;
} {
const [state, setState] = useState<CountdownState>({
seconds: 0,
minutes: 0,
hours: 0,
days: 0,
isRunning: autoStart,
isFinished: false,
});
const intervalRef = useRef<NodeJS.Timeout | null>(null);
const targetTimestamp = useRef<number>(
typeof targetDate === 'number' ? targetDate : targetDate.getTime()
);
// Calculate remaining time
const calculateRemainingTime = useCallback(() => {
const now = Date.now();
const diff = targetTimestamp.current - now;
__TOKEN_31__ (diff <= 0) {
return {
seconds: 0,
minutes: 0,
hours: 0,
days: 0,
isFinished: true,
};
}
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
const seconds = Math.floor((diff % (1000 * 60)) / 1000);
return {
days,
hours,
minutes,
seconds,
isFinished: false,
};
}, []);
// Update countdown
const updateCountdown = useCallback(() => {
const remaining = calculateRemainingTime();
setState(prev => ({ ...prev, ...remaining }));
__TOKEN_40__ (remaining.isFinished) {
pause();
}
}, [calculateRemainingTime]);
// Start countdown
const start = useCallback(() => {
__TOKEN_42__ (state.isFinished || intervalRef.current) return;
setState(prev => ({ ...prev, isRunning: true }));
intervalRef.current = setInterval(updateCountdown, 1000);
updateCountdown(); // Immediate update
}, [state.isFinished, updateCountdown]);
// Pause countdown
const pause = useCallback(() => {
__TOKEN_45__ (intervalRef.current) {
clearInterval(intervalRef.current);
intervalRef.current = null;
setState(prev => ({ ...prev, isRunning: false }));
}
}, []);
// Reset countdown
const reset = useCallback(() => {
pause();
const remaining = calculateRemainingTime();
setState({
...remaining,
isRunning: autoStart,
isFinished: remaining.isFinished,
});
__TOKEN_48__ (autoStart && !remaining.isFinished) {
start();
}
}, [pause, calculateRemainingTime, autoStart, start]);
// Initial setup
useEffect(() => {
__TOKEN_49__ (autoStart) {
start();
} else {
updateCountdown();
}
// Cleanup
__TOKEN_51__ () => {
pause();
};
}, [autoStart, start, updateCountdown, pause]);
return { state, start, pause, reset };
}
// Usage example
// const targetDate = new Date();
// targetDate.setDate(targetDate.getDate() + 1); // 1 day from now
// const { state, start, pause, reset } = useCountdown(targetDate);
// <div>
// <p>{state.days}d {state.hours}h {state.minutes}m {state.seconds}s</p>
// <button onClick={start} disabled={state.isRunning || state.isFinished}>Start</button>
// <button onClick={pause} disabled={!state.isRunning}>Pause</button>
// <button onClick={reset}>Reset</button>
// </div>