/ ink / hooks / use-interval.ts
use-interval.ts
 1  import { useContext, useEffect, useRef, useState } from 'react'
 2  import { ClockContext } from '../components/ClockContext.js'
 3  
 4  /**
 5   * Returns the clock time, updating at the given interval.
 6   * Subscribes as non-keepAlive — won't keep the clock alive on its own,
 7   * but updates whenever a keepAlive subscriber (e.g. the spinner)
 8   * is driving the clock.
 9   *
10   * Use this to drive pure time-based computations (shimmer position,
11   * frame index) from the shared clock.
12   */
13  export function useAnimationTimer(intervalMs: number): number {
14    const clock = useContext(ClockContext)
15    const [time, setTime] = useState(() => clock?.now() ?? 0)
16  
17    useEffect(() => {
18      if (!clock) return
19  
20      let lastUpdate = clock.now()
21  
22      const onChange = (): void => {
23        const now = clock.now()
24        if (now - lastUpdate >= intervalMs) {
25          lastUpdate = now
26          setTime(now)
27        }
28      }
29  
30      return clock.subscribe(onChange, false)
31    }, [clock, intervalMs])
32  
33    return time
34  }
35  
36  /**
37   * Interval hook backed by the shared Clock.
38   *
39   * Unlike `useInterval` from `usehooks-ts` (which creates its own setInterval),
40   * this piggybacks on the single shared clock so all timers consolidate into
41   * one wake-up. Pass `null` for intervalMs to pause.
42   */
43  export function useInterval(
44    callback: () => void,
45    intervalMs: number | null,
46  ): void {
47    const callbackRef = useRef(callback)
48    callbackRef.current = callback
49  
50    const clock = useContext(ClockContext)
51  
52    useEffect(() => {
53      if (!clock || intervalMs === null) return
54  
55      let lastUpdate = clock.now()
56  
57      const onChange = (): void => {
58        const now = clock.now()
59        if (now - lastUpdate >= intervalMs) {
60          lastUpdate = now
61          callbackRef.current()
62        }
63      }
64  
65      return clock.subscribe(onChange, false)
66    }, [clock, intervalMs])
67  }