Skip to content

Instantly share code, notes, and snippets.

@Wigny
Created June 3, 2025 13:03
Show Gist options
  • Save Wigny/a25664567c3d7c60d145b24833b9b54a to your computer and use it in GitHub Desktop.
Save Wigny/a25664567c3d7c60d145b24833b9b54a to your computer and use it in GitHub Desktop.
import { useCallback, useEffect, useMemo, useState } from 'react';
import dayjs, { Dayjs, duration } from 'dayjs';
import { Duration } from 'dayjs/plugin/duration';
type Status = 'idle' | 'running' | 'paused';
type TimerState = {
status: Status;
startedAt: Dayjs | null;
elapsed: Duration;
};
type Timer = [
{ status: Status; elapsed: Duration },
{ start: () => void; pause: () => void; resume: () => void; reset: () => void },
];
export const useTimer = (): Timer => {
const [state, setState] = useState<TimerState>({ status: 'idle', startedAt: null, elapsed: duration(0) });
const start = useCallback(() => {
setState({
status: 'running',
startedAt: dayjs(),
elapsed: duration(0),
});
}, []);
const pause = useCallback(() => {
setState({
status: 'paused',
startedAt: null,
elapsed: state.elapsed.add(dayjs().diff(state.startedAt ?? dayjs())),
});
}, [state]);
const resume = useCallback(() => {
setState({
...state,
status: 'running',
startedAt: dayjs(),
});
}, [state]);
const reset = useCallback(() => {
setState({
status: 'idle',
startedAt: null,
elapsed: duration(0),
});
}, []);
const timer = useMemo(() => {
return new Proxy(state, {
get(target, prop) {
switch (prop) {
case 'elapsed':
return target.elapsed.add(dayjs().diff(target.startedAt ?? dayjs()));
default:
return target[prop as keyof TimerState];
}
},
});
}, [state]);
useEffect(() => {
reset();
}, [reset]);
return [timer, { start, pause, resume, reset }];
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment