Created
October 12, 2025 23:41
-
-
Save ryanflorence/fd25a7e28716538ebc55386f02d2dd20 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { | |
| useCallback, | |
| useEffect, | |
| useMemo, | |
| useRef, | |
| useState, | |
| type DependencyList, | |
| } from "react"; | |
| import { | |
| DynamicInterval, | |
| type DynamicIntervalOptions, | |
| type TaskFn, | |
| } from "./dynamic-interval"; | |
| export type UseDynamicIntervalOptions = DynamicIntervalOptions; | |
| export type UseDynamicIntervalReturn = { | |
| info: DynamicInterval["info"]; | |
| start: () => void; | |
| pause: () => void; | |
| resume: () => void; | |
| stop: () => void; | |
| runNow: () => void; | |
| resetBackoff: () => void; | |
| instance: DynamicInterval | null; | |
| }; | |
| /** | |
| * React hook wrapper for DynamicInterval. | |
| * Creates and manages a DynamicInterval instance with reactive info state and lifecycle controls. | |
| * | |
| * Recreate behavior is controlled by the deps array. Pass deps to recreate the scheduler when inputs change. | |
| */ | |
| export function useDynamicInterval( | |
| options: UseDynamicIntervalOptions, | |
| deps: DependencyList = [], | |
| ): UseDynamicIntervalReturn { | |
| const diRef = useRef<DynamicInterval | null>(null); | |
| // Keep the latest task without forcing a re-instantiation of the scheduler | |
| const taskRef = useRef<TaskFn>(options.task); | |
| useEffect(() => { | |
| taskRef.current = options.task; | |
| }, [options.task]); | |
| const notifyInfoUpdate = useCallback(() => { | |
| const inst = diRef.current; | |
| if (inst) { | |
| // Queue on next macrotask to ensure DynamicInterval has updated its internal state after a tick | |
| setTimeout(() => { | |
| const current = diRef.current; | |
| if (current) setInfo(current.info); | |
| }, 0); | |
| } | |
| }, []); | |
| const wrappedTask = useMemo<TaskFn>(() => { | |
| return async () => { | |
| try { | |
| await taskRef.current(); | |
| } finally { | |
| notifyInfoUpdate(); | |
| } | |
| }; | |
| }, [notifyInfoUpdate]); | |
| const initialInfo: DynamicInterval["info"] = useMemo(() => { | |
| return { | |
| name: options.name, | |
| paused: true, | |
| stopped: false, | |
| running: false, | |
| lastDelayMs: null, | |
| lastOutcome: null, | |
| runCount: 0, | |
| } as DynamicInterval["info"]; | |
| }, [options.name]); | |
| const [info, setInfo] = useState<DynamicInterval["info"]>(initialInfo); | |
| useEffect(() => { | |
| const inst = new DynamicInterval({ ...options, task: wrappedTask }); | |
| diRef.current = inst; | |
| setInfo(inst.info); | |
| return () => { | |
| try { | |
| inst.stop(); | |
| } finally { | |
| diRef.current = null; | |
| } | |
| }; | |
| // eslint-disable-next-line react-hooks/exhaustive-deps | |
| }, deps); | |
| const start = useCallback(() => { | |
| const inst = diRef.current; | |
| if (!inst) return; | |
| inst.start(); | |
| setInfo(inst.info); | |
| }, []); | |
| const pause = useCallback(() => { | |
| const inst = diRef.current; | |
| if (!inst) return; | |
| inst.pause(); | |
| setInfo(inst.info); | |
| }, []); | |
| const resume = useCallback(() => { | |
| const inst = diRef.current; | |
| if (!inst) return; | |
| inst.resume(); | |
| setInfo(inst.info); | |
| }, []); | |
| const stop = useCallback(() => { | |
| const inst = diRef.current; | |
| if (!inst) return; | |
| inst.stop(); | |
| setInfo(inst.info); | |
| }, []); | |
| const runNow = useCallback(() => { | |
| const inst = diRef.current; | |
| if (!inst) return; | |
| inst.runNow(); | |
| setInfo(inst.info); | |
| }, []); | |
| const resetBackoff = useCallback(() => { | |
| const inst = diRef.current; | |
| if (!inst) return; | |
| inst.resetBackoff(); | |
| setInfo(inst.info); | |
| }, []); | |
| return { | |
| info, | |
| start, | |
| pause, | |
| resume, | |
| stop, | |
| runNow, | |
| resetBackoff, | |
| instance: diRef.current, | |
| }; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment