Spaces:
Running
Running
| "use client"; | |
| import { useState, useRef, useCallback, useEffect } from "react"; | |
| interface UseTimerOptions { | |
| initialSeconds: number; | |
| onTimeUp: () => void; | |
| autoStart?: boolean; | |
| } | |
| export function useTimer({ initialSeconds, onTimeUp, autoStart = false }: UseTimerOptions) { | |
| const [seconds, setSeconds] = useState(initialSeconds); | |
| const [isRunning, setIsRunning] = useState(autoStart); | |
| const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null); | |
| const onTimeUpRef = useRef(onTimeUp); | |
| useEffect(() => { | |
| onTimeUpRef.current = onTimeUp; | |
| }, [onTimeUp]); | |
| const stop = useCallback(() => { | |
| setIsRunning(false); | |
| if (intervalRef.current) { | |
| clearInterval(intervalRef.current); | |
| intervalRef.current = null; | |
| } | |
| }, []); | |
| const start = useCallback(() => { | |
| setIsRunning(true); | |
| }, []); | |
| const reset = useCallback((newSeconds?: number) => { | |
| stop(); | |
| setSeconds(newSeconds ?? initialSeconds); | |
| }, [stop, initialSeconds]); | |
| // Tick the timer — pure updater, no side effects | |
| useEffect(() => { | |
| if (!isRunning) return; | |
| intervalRef.current = setInterval(() => { | |
| setSeconds((prev) => { | |
| if (prev <= 1) return 0; | |
| return prev - 1; | |
| }); | |
| }, 1000); | |
| return () => { | |
| if (intervalRef.current) clearInterval(intervalRef.current); | |
| }; | |
| }, [isRunning, stop]); | |
| // Fire side effects when timer reaches 0 | |
| useEffect(() => { | |
| if (isRunning && seconds === 0) { | |
| const timer = setTimeout(() => { | |
| stop(); | |
| onTimeUpRef.current(); | |
| }, 0); | |
| return () => clearTimeout(timer); | |
| } | |
| }, [seconds, isRunning, stop]); | |
| return { seconds, isRunning, start, stop, reset }; | |
| } | |