Skip to content

use-throttled-fn

A React hook that returns a throttled version of provided function, ensuring it executes at most once per specified time interval, regardless of how frequently it's called.

Features

  • Rate limiting: Ensures function executes at most once per specified interval
  • Immediate execution: First call executes immediately, subsequent calls are throttled
  • Performance optimized: Prevents excessive function calls during rapid user interactions
  • Context preservation: Maintains original function's this context and error behavior
  • Error handling: Preserves original function's error behavior

Problem It Solves

Boilerplate Reduction

Problem: Manually implementing throttling in React components becomes lengthy, little bit complex code with potential performance issues and inconsistent behavior.

tsx
// ❌ Problematic approach which is redundant and verbose
function ScrollTracker() {
   const [scrollPosition, setScrollPosition] = useState(0)
   const lastExecutionTimeRef = useRef(0)
   const THROTTLE_DELAY = 100

   const handleScroll = useCallback(() => {
      const currentTime = Date.now()

      if (currentTime - lastExecutionTimeRef.current >= THROTTLE_DELAY) {
         setScrollPosition(window.scrollY)
         lastExecutionTimeRef.current = currentTime

         // Additional complex logic for tracking scroll performance
         console.log('Scroll position updated:', window.scrollY)
      }
   }, [])

   useEffect(() => {
      const throttledScrollHandler = (event: Event) => {
         handleScroll()
      }

      window.addEventListener('scroll', throttledScrollHandler, { passive: true })

      return () => {
         window.removeEventListener('scroll', throttledScrollHandler)
      }
   }, [handleScroll])

   return <div>Current scroll: {scrollPosition}px</div>
}

Solution:

  • Eliminates repetitive throttling logic

  • Automatic handling of:

    → First call immediate execution

    → Context preservation

    → Error handling

tsx
// ✅ Clean, declarative approach
function ScrollTracker() {
   const [scrollPosition, setScrollPosition] = useState(0)

   const throttledScrollHandler = useThrottledFn({
      callbackToThrottle: () => {
         setScrollPosition(window.scrollY)
         console.log('Scroll position updated:', window.scrollY)
      },
      delay: 100,
   })

   useEffect(() => {
      window.addEventListener('scroll', throttledScrollHandler, { passive: true })
      return () => window.removeEventListener('scroll', throttledScrollHandler)
   }, [throttledScrollHandler])

   return <div>Current scroll: {scrollPosition}px</div>
}
Performance Benefits
  • Reduces execution frequency: Limits function calls to specified intervals, preventing performance bottlenecks
  • Memory efficient: Minimal overhead with simple timestamp tracking

Throttling vs Debouncing

AspectThrottlingDebouncing
Execution timingExecutes at regular intervalsExecutes after inactivity period
First callExecutes immediatelyMay delay first execution
Use caseContinuous events (scroll, resize)User input completion (search, validation)
FrequencyLimited to X times per intervalExecutes only after pause

Parameters

ParameterTypeRequiredDefault ValueDescription
callbackToThrottleThrottledFn-The function to throttle
delaynumber300msMinimum time interval between function executions in milliseconds

Type Definitions

Details
ts
export type ThrottledFn<T extends (...args: any[]) => any> = (...args: Parameters<T>) => void

Return Value(s)

This hook returns the throttled version of provided callback.

Return ValueTypeDescription
throttledFn(...args: Parameters<T>) => voidThrottled version of the original function that limits execution to the specified interval

Common Use Cases

  • API Rate Limiting: Limiting search API calls while typing
  • Mouse Movement Tracking: Tracking mouse coordinate and update on screen
  • Real-time data updates: Managing WebSocket or polling frequency

Usage Examples

API Rate Limiting

tsx
import { useState } from 'react'
import { useThrottledFn } from 'classic-react-hooks'

export default function ApiExample() {
   const [data, setData] = useState<any[]>([])
   const [requestCount, setRequestCount] = useState(0)
   const [isLoading, setIsLoading] = useState(false)

   const throttledFetch = useThrottledFn({
      callbackToThrottle: async () => {
         setIsLoading(true)
         setRequestCount((prev) => prev + 1)

         try {
            const response = await fetch('https://jsonplaceholder.typicode.com/posts?_limit=5')
            const result = await response.json()
            setData(result)
         } catch (error) {
            console.error('Fetch failed:', error)
         } finally {
            setIsLoading(false)
         }
      },
      delay: 2000, // Max 1 request per 2 seconds
   })

   return (
      <div className='p-4'>
         <button
            onClick={throttledFetch}
            disabled={isLoading}
            className='px-4 py-2 bg-blue-500 text-white rounded disabled:bg-gray-400 disabled:cursor-not-allowed'
         >
            {isLoading ? 'Loading...' : 'Fetch Data (max 1/2sec)'}
         </button>
         <p className='mt-2 text-sm'>API calls made: {requestCount}</p>
         <div className='mt-4 space-y-2'>
            {data.map((item) => (
               <div key={item.id} className='my-1 p-2 border border-gray-300 rounded'>
                  <strong className='font-semibold'>{item.title}</strong>
               </div>
            ))}
         </div>
         <p className='mt-4'>
            <small className='text-gray-600'>Click rapidly - API calls are throttled to 1 per 2 seconds</small>
         </p>
      </div>
   )
}

Mouse Movement Tracking

Example
tsx
import { useState, useEffect } from 'react'
import { useThrottledFn } from 'classic-react-hooks'

export default function MouseTracker() {
   const [mousePos, setMousePos] = useState({ x: 0, y: 0 })
   const [updateCount, setUpdateCount] = useState(0)

   const throttledMouseMove = useThrottledFn({
      callbackToThrottle: (event: MouseEvent) => {
         setMousePos({
            x: event.clientX,
            y: event.clientY,
         })
         setUpdateCount((prev) => prev + 1)
      },
      delay: 50, // 20 updates per second max
   })

   useEffect(() => {
      const handleMouseMove = (event: MouseEvent) => {
         throttledMouseMove(event)
      }

      document.addEventListener('mousemove', handleMouseMove)
      return () => document.removeEventListener('mousemove', handleMouseMove)
   }, [throttledMouseMove])

   return (
      <div className='h-72 border-2 border-dashed border-gray-300 p-5'>
         <h3 className='text-lg font-semibold mb-2'>Mouse Position Tracker</h3>
         <p className='mb-1'>
            Position: ({mousePos.x}, {mousePos.y})
         </p>
         <p className='mb-1'>Updates: {updateCount}</p>
         <p>
            <small className='text-gray-600'>Move your mouse around this area</small>
         </p>
      </div>
   )
}