Throttle Utilities
Throttle and debounce utilities: rate-limit scroll, resize, and position update handlers for smooth tour performance
Performance utilities for rate-limiting function calls. Used internally by tour-kit for smooth scroll/resize handling and analytics batching.
Why Throttle?
High-frequency events like scroll and resize can fire 60+ times per second. Without throttling:
- DOM measurements (
getBoundingClientRect) become expensive - State updates cause excessive re-renders
- Analytics events flood your backend
throttleRAF
RAF-based throttling for smooth 60fps updates during scroll/resize. Coalesces rapid calls to a single execution per animation frame.
Usage
import { throttleRAF } from '@tour-kit/core';
const throttledUpdate = throttleRAF(() => {
// Expensive DOM measurement
element.getBoundingClientRect();
});
// Attach to scroll/resize with passive listeners
window.addEventListener('scroll', throttledUpdate, { passive: true });
// Cleanup
throttledUpdate.cancel();
window.removeEventListener('scroll', throttledUpdate);Parameters
Prop
Type
Return Value
ThrottledFunction<T> & {
cancel: () => void;
}The returned function includes a cancel() method to abort any pending animation frame.
When to Use
- Scroll event handlers
- Resize event handlers
- Any high-frequency DOM operations
throttleRAF automatically uses requestAnimationFrame for optimal browser rendering sync.
throttleTime
Time-based throttling with trailing edge execution. Queues the most recent call and executes after the interval.
Usage
import { throttleTime } from '@tour-kit/core';
const throttled = throttleTime(sendAnalytics, 1000);
// Call multiple times - executes once per second
throttled(event1); // Executes immediately
throttled(event2); // Queued
throttled(event3); // Replaces event2
// After 1 second: event3 is sent
// Force immediate execution of pending call
throttled.flush();
// Cleanup
throttled.cancel();Parameters
Prop
Type
Return Value
ThrottledFunction<T> & {
cancel: () => void;
flush: () => void;
}Includes both cancel() to abort pending execution and flush() to execute immediately.
When to Use
- Analytics event batching
- API calls that shouldn't be too frequent
- Expensive computations on user input
throttleLeading
Leading-edge throttle - fires immediately, then ignores calls for the interval. Useful for immediate user feedback while preventing rapid-fire events.
Usage
import { throttleLeading } from '@tour-kit/core';
const throttled = throttleLeading(onClick, 500);
// First call fires immediately
throttled(); // Executes
throttled(); // Ignored (within 500ms)
throttled(); // Ignored
// After 500ms
throttled(); // Executes againParameters
Prop
Type
Return Value
ThrottledFunction<T> & {
cancel: () => void;
}Includes cancel() to reset the throttle state.
When to Use
- Button click handlers
- Feature usage tracking
- Any action where immediate feedback matters
Comparison Table
| Utility | First Call | During Interval | After Interval |
|---|---|---|---|
throttleRAF | Queued to RAF | Coalesced | Executes latest |
throttleTime | Immediate | Queued (latest) | Executes queued |
throttleLeading | Immediate | Ignored | Immediate again |
TypeScript Types
type AnyFunction = (...args: unknown[]) => void;
interface ThrottledFunction<T extends AnyFunction> {
(...args: Parameters<T>): void;
cancel: () => void;
}
interface ThrottledFunctionWithFlush<T extends AnyFunction>
extends ThrottledFunction<T> {
flush: () => void;
}Real-World Examples
Spotlight Position Updates
import { throttleRAF } from '@tour-kit/core';
import { useCallback, useEffect, useMemo } from 'react';
function useSpotlightPosition(targetRef: RefObject<HTMLElement>) {
const [rect, setRect] = useState<DOMRect | null>(null);
const updateRect = useCallback(() => {
if (targetRef.current) {
setRect(targetRef.current.getBoundingClientRect());
}
}, []);
const throttledUpdate = useMemo(() => throttleRAF(updateRect), [updateRect]);
useEffect(() => {
window.addEventListener('scroll', throttledUpdate, { passive: true });
window.addEventListener('resize', throttledUpdate, { passive: true });
return () => {
throttledUpdate.cancel();
window.removeEventListener('scroll', throttledUpdate);
window.removeEventListener('resize', throttledUpdate);
};
}, [throttledUpdate]);
return rect;
}Feature Usage Tracking
import { throttleLeading } from '@tour-kit/core';
function setupFeatureTracking(featureId: string, onUsage: () => void) {
// Prevent rapid-fire events (1 second cooldown)
const throttledUsage = throttleLeading(onUsage, 1000);
const handler = (event: MouseEvent) => {
if ((event.target as Element).matches(`[data-feature="${featureId}"]`)) {
throttledUsage();
}
};
document.addEventListener('click', handler);
return () => {
throttledUsage.cancel();
document.removeEventListener('click', handler);
};
}Analytics Debouncing
import { throttleTime } from '@tour-kit/core';
const analytics = {
queue: [] as Event[],
track: throttleTime((event: Event) => {
// Batch send to analytics service
fetch('/api/analytics', {
method: 'POST',
body: JSON.stringify(event),
});
}, 5000),
// Ensure events are sent on page unload
flush() {
this.track.flush();
},
};
window.addEventListener('beforeunload', () => analytics.flush());Performance Considerations
Passive Event Listeners
Always use { passive: true } with throttled scroll/resize handlers:
// Good - prevents scroll jank
window.addEventListener('scroll', throttled, { passive: true });
// Avoid - can cause scroll jank
window.addEventListener('scroll', throttled);Cleanup
Always cancel throttled functions in cleanup to prevent memory leaks:
useEffect(() => {
const throttled = throttleRAF(update);
window.addEventListener('scroll', throttled);
return () => {
throttled.cancel(); // Important!
window.removeEventListener('scroll', throttled);
};
}, []);Memoization
Wrap throttled functions in useMemo to prevent recreating on every render:
// Good - stable reference
const throttled = useMemo(() => throttleRAF(update), [update]);
// Avoid - creates new throttled function on every render
const throttled = throttleRAF(update);Related
- useElementPosition - Uses throttleRAF for position updates
- useSpotlight - Uses throttleRAF for rect updates
- Analytics - Uses event batching with throttleTime