react-use-timer-hook
Version:
A React hook for managing countdown or count-up timers with pause, reset, and customizable callbacks.
92 lines (91 loc) • 2.97 kB
JavaScript
import { isNull } from '@migudevelop/types-utils';
import { useEffect, useRef } from 'react';
import { getTimeFromMilliseconds, getTimeFromSeconds } from './helpers';
import { useTimerReducer } from './useTimerReducer';
const INTERVAL = 1000; // 1 second
/**
* React hook for a flexible timer supporting countdown, count-up, pause, and granular time tracking.
* @param options UseTimerOptions configuration object
* @returns Timer state and control methods
*/
export function useTimer({ time: initialTime = 0, countUp = false, autoStart = false, onFinish, onTick, onReset } = {}) {
const finishTime = countUp ? initialTime : 0;
const { isRunning, isPaused, pauseStart, totalPauseTime, pauseTime, time, start, pause, pauseTick, reset, tick } = useTimerReducer({
time: countUp ? 0 : initialTime,
finishTime,
autoStart,
countUp
});
const intervalRef = useRef(null);
const pauseIntervalRef = useRef(null);
/**
* Timer interval effect: handles ticking when running
*/
useEffect(() => {
if (!isRunning) {
if (intervalRef.current)
clearInterval(intervalRef.current);
return;
}
intervalRef.current = setInterval(() => {
tick();
onTick?.();
}, INTERVAL);
return () => {
if (intervalRef.current)
clearInterval(intervalRef.current);
};
}, [isRunning, tick, onTick]);
/**
* Pause interval effect: tracks pause duration when paused
*/
useEffect(() => {
if (isRunning || !isPaused) {
if (pauseIntervalRef.current)
clearInterval(pauseIntervalRef.current);
return;
}
pauseIntervalRef.current = setInterval(() => {
pauseTick();
}, INTERVAL);
return () => {
if (pauseIntervalRef.current)
clearInterval(pauseIntervalRef.current);
};
}, [isRunning, pauseTick, isPaused]);
/**
* Reset effect: resets timer if initialTime changes
*/
useEffect(() => {
reset();
onReset?.();
}, [reset, onReset]);
/**
* Syncs pause state: if timer is running and pauseStart is set, update totalPauseTime
*/
useEffect(() => {
if (isRunning && isNull(pauseStart)) {
start();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isRunning]);
/**
* Expire callback: calls onFinish when timer completes
*/
useEffect(() => {
if (time === finishTime && !isRunning) {
onFinish?.();
}
}, [time, onFinish, finishTime, isRunning]);
return {
...getTimeFromSeconds(time),
isRunning,
start,
pause,
reset,
time,
totalPauseTime: getTimeFromMilliseconds(totalPauseTime),
pauseTime: getTimeFromSeconds(pauseTime),
pauseStart
};
}