function useAsyncRateLimiter<TFn, TSelected>( fn, options, selector): ReactAsyncRateLimiter<TFn, TSelected>; function useAsyncRateLimiter<TFn, TSelected>( fn, options, selector): ReactAsyncRateLimiter<TFn, TSelected>; Defined in: react-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts:179
A low-level React hook that creates an AsyncRateLimiter instance to limit how many times an async function can execute within a time window.
This hook is designed to be flexible and state-management agnostic - it simply returns a rate limiter instance that you can integrate with any state management solution (useState, Redux, Zustand, Jotai, etc).
Rate limiting allows an async function to execute up to a specified limit within a time window, then blocks subsequent calls until the window passes. This is useful for respecting API rate limits, managing resource constraints, or controlling bursts of async operations.
Unlike the non-async RateLimiter, this async version supports returning values from the rate-limited function, making it ideal for API calls and other async operations where you want the result of the maybeExecute call instead of setting the result on a state variable from within the rate-limited function.
The rate limiter supports two types of windows:
Error Handling:
The hook uses TanStack Store for reactive state management. The selector parameter allows you to specify which state changes will trigger a re-render, optimizing performance by preventing unnecessary re-renders when irrelevant state changes occur.
By default, there will be no reactive state subscriptions and you must opt-in to state tracking by providing a selector function. This prevents unnecessary re-renders and gives you full control over when your component updates. Only when you provide a selector will the component re-render when the selected state values change.
Available state properties:
TFn extends AnyAsyncFunction
TSelected = { }
TFn
AsyncRateLimiterOptions<TFn>
(state) => TSelected
ReactAsyncRateLimiter<TFn, TSelected>
// Default behavior - no reactive state subscriptions const asyncRateLimiter = useAsyncRateLimiter( async (id: string) => { const data = await api.fetchData(id); return data; // Return value is preserved }, { limit: 5, window: 1000 } // 5 calls per second ); // Opt-in to re-render when execution state changes (optimized for loading indicators) const asyncRateLimiter = useAsyncRateLimiter( async (id: string) => { const data = await api.fetchData(id); return data; }, { limit: 5, window: 1000 }, (state) => ({ isExecuting: state.isExecuting }) ); // Opt-in to re-render when results are available (optimized for data display) const asyncRateLimiter = useAsyncRateLimiter( async (id: string) => { const data = await api.fetchData(id); return data; }, { limit: 5, window: 1000 }, (state) => ({ lastResult: state.lastResult, successCount: state.successCount }) ); // Opt-in to re-render when error/rejection state changes (optimized for error handling) const asyncRateLimiter = useAsyncRateLimiter( async (id: string) => { const data = await api.fetchData(id); return data; }, { limit: 5, window: 1000, onError: (error) => console.error('API call failed:', error), onReject: (rateLimiter) => console.log('Rate limit exceeded') }, (state) => ({ errorCount: state.errorCount, rejectionCount: state.rejectionCount }) ); // Opt-in to re-render when execution metrics change (optimized for stats display) const asyncRateLimiter = useAsyncRateLimiter( async (id: string) => { const data = await api.fetchData(id); return data; }, { limit: 5, window: 1000 }, (state) => ({ successCount: state.successCount, errorCount: state.errorCount, settleCount: state.settleCount, rejectionCount: state.rejectionCount }) ); // Opt-in to re-render when execution times change (optimized for window calculations) const asyncRateLimiter = useAsyncRateLimiter( async (id: string) => { const data = await api.fetchData(id); return data; }, { limit: 5, window: 1000 }, (state) => ({ executionTimes: state.executionTimes }) ); // With state management and return value const [data, setData] = useState(null); const { maybeExecute, state } = useAsyncRateLimiter( async (query) => { const result = await searchAPI(query); setData(result); return result; // Return value can be used by the caller }, { limit: 10, window: 60000, // 10 calls per minute onReject: (rateLimiter) => { console.log(`Rate limit exceeded. Try again in ${rateLimiter.getMsUntilNextWindow()}ms`); }, onError: (error) => { console.error('API call failed:', error); } } ); // Access the selected state (will be empty object {} unless selector provided) const { isExecuting, lastResult, rejectionCount } = state; // Default behavior - no reactive state subscriptions const asyncRateLimiter = useAsyncRateLimiter( async (id: string) => { const data = await api.fetchData(id); return data; // Return value is preserved }, { limit: 5, window: 1000 } // 5 calls per second ); // Opt-in to re-render when execution state changes (optimized for loading indicators) const asyncRateLimiter = useAsyncRateLimiter( async (id: string) => { const data = await api.fetchData(id); return data; }, { limit: 5, window: 1000 }, (state) => ({ isExecuting: state.isExecuting }) ); // Opt-in to re-render when results are available (optimized for data display) const asyncRateLimiter = useAsyncRateLimiter( async (id: string) => { const data = await api.fetchData(id); return data; }, { limit: 5, window: 1000 }, (state) => ({ lastResult: state.lastResult, successCount: state.successCount }) ); // Opt-in to re-render when error/rejection state changes (optimized for error handling) const asyncRateLimiter = useAsyncRateLimiter( async (id: string) => { const data = await api.fetchData(id); return data; }, { limit: 5, window: 1000, onError: (error) => console.error('API call failed:', error), onReject: (rateLimiter) => console.log('Rate limit exceeded') }, (state) => ({ errorCount: state.errorCount, rejectionCount: state.rejectionCount }) ); // Opt-in to re-render when execution metrics change (optimized for stats display) const asyncRateLimiter = useAsyncRateLimiter( async (id: string) => { const data = await api.fetchData(id); return data; }, { limit: 5, window: 1000 }, (state) => ({ successCount: state.successCount, errorCount: state.errorCount, settleCount: state.settleCount, rejectionCount: state.rejectionCount }) ); // Opt-in to re-render when execution times change (optimized for window calculations) const asyncRateLimiter = useAsyncRateLimiter( async (id: string) => { const data = await api.fetchData(id); return data; }, { limit: 5, window: 1000 }, (state) => ({ executionTimes: state.executionTimes }) ); // With state management and return value const [data, setData] = useState(null); const { maybeExecute, state } = useAsyncRateLimiter( async (query) => { const result = await searchAPI(query); setData(result); return result; // Return value can be used by the caller }, { limit: 10, window: 60000, // 10 calls per minute onReject: (rateLimiter) => { console.log(`Rate limit exceeded. Try again in ${rateLimiter.getMsUntilNextWindow()}ms`); }, onError: (error) => { console.error('API call failed:', error); } } ); // Access the selected state (will be empty object {} unless selector provided) const { isExecuting, lastResult, rejectionCount } = state; 