All files / hooks useThrottle.ts

95.23% Statements 20/21
83.33% Branches 5/6
100% Functions 6/6
95.23% Lines 20/21

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77                                                                    238x 238x 238x     238x 238x       238x 127x 127x           238x   16x 16x   16x   13x 13x 3x   2x 2x 2x 2x 2x                  
/**
 * useThrottle Hook
 * 
 * Throttles a function to execute at most once per specified interval.
 * Useful for high-frequency events like scroll, resize, or touch move.
 * 
 * Uses a ref pattern to ensure the latest callback is always called
 * without recreating the throttled function on every render.
 * 
 * @module hooks/useThrottle
 * @category Performance
 * @korean 쓰로틀 훅
 */
 
import { useCallback, useRef, useLayoutEffect, useEffect } from 'react';
 
/**
 * Hook to throttle a callback function
 * 
 * @param callback - Function to throttle
 * @param delay - Minimum delay between executions in milliseconds
 * @returns Throttled function
 * 
 * @example
 * ```tsx
 * const handleTouchMove = useThrottle((event: TouchEvent) => {
 *   // Handle touch move
 * }, 16); // ~60fps
 * ```
 */
export function useThrottle<T extends (...args: never[]) => void>(
  callback: T,
  delay: number
): T {
  const lastRunRef = useRef<number>(0);
  const timeoutRef = useRef<NodeJS.Timeout | null>(null);
  const callbackRef = useRef(callback);
  
  // Keep callback ref up to date
  useLayoutEffect(() => {
    callbackRef.current = callback;
  });
 
  // Cleanup pending timeout on unmount
  useEffect(() => {
    return () => {
      Iif (timeoutRef.current) {
        clearTimeout(timeoutRef.current);
      }
    };
  }, []);
 
  return useCallback(
    (...args: Parameters<T>) => {
      const now = Date.now();
      const timeSinceLastRun = now - lastRunRef.current;
 
      if (timeSinceLastRun >= delay) {
        // Execute immediately if enough time has passed
        lastRunRef.current = now;
        callbackRef.current(...args);
      } else if (!timeoutRef.current) {
        // Schedule execution for later
        const timeUntilNext = delay - timeSinceLastRun;
        timeoutRef.current = setTimeout(() => {
          lastRunRef.current = Date.now();
          timeoutRef.current = null;
          callbackRef.current(...args);
        }, timeUntilNext);
      }
    },
    [delay]
  ) as T;
}
 
export default useThrottle;