import { useCallback, useEffect, useRef, useState } from 'react'; /** * A throttled version of React's `useState` that limits how often the state is updated. * * When `setValue` is called repeatedly, the state will update immediately if enough time * has passed since the last update. Otherwise, it schedules an update after the remaining * throttle interval. The most recent value is always applied eventually (trailing flush). * * @param initialValue The initial state value. * @param intervalMs The minimum interval (in milliseconds) between state updates. * @returns A tuple of `[state, setValue]`, just like `useState`. */ export function useThrottledState(initialValue: T, intervalMs: number): [T, (value: T, forced?: boolean) => void] { const [state, setState] = useState(initialValue); const lastExecuted = useRef(0); const timeoutRef = useRef>(undefined); const setThrottled = useCallback( (value: T, forced: boolean | undefined = false) => { const now = Date.now(); const timeSinceLast = now - lastExecuted.current; clearTimeout(timeoutRef.current); if (forced || timeSinceLast >= intervalMs) { setState(value); lastExecuted.current = now; } else { timeoutRef.current = setTimeout(() => { setState(value); lastExecuted.current = Date.now(); }, intervalMs - timeSinceLast); } }, [intervalMs], ); useEffect(() => { return () => clearTimeout(timeoutRef.current); }, []); return [state, setThrottled]; }