Skip to content

Instantly share code, notes, and snippets.

@johnloven
Created March 1, 2021 23:57
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save johnloven/32da29cd73611ddd5f7235573c6a450f to your computer and use it in GitHub Desktop.
Save johnloven/32da29cd73611ddd5f7235573c6a450f to your computer and use it in GitHub Desktop.
import { useRef, useEffect, useCallback } from "react";
// Rate limit a function but make sure the last call in a series of calls is run.
function useRateLimitedCallback(timeout, callback) {
// Save the callback in a ref to allow using a un-memoized callback parameter without changing identity of the returned callback function
const callbackRef = useRef(callback);
useEffect(() => {
callbackRef.current = callback;
});
const scheduledCallbackTimoutRef = useRef(null);
const unpauseTimoutRef = useRef(null);
const pausedRef = useRef(false);
// Clear all scheduled timeouts on unmount.
useEffect(() => {
return () => {
clearTimeout(unpauseTimoutRef.current);
clearTimeout(scheduledCallbackTimoutRef.current);
}
}, []);
// These two code blocks do very different things, functionally complementing each other.
// Either code block can be used without the other. However, we want both.
// We save all scheduled timeouts in refs to be able to clear them on unmount.
return useCallback(() => {
// After calling the callback, run the callback.
// Pause 'timeout' before allowing the callback to run again.
// This ratelimits the callback.
if (!pausedRef.current) {
pausedRef.current = true;
callbackRef.current();
unpauseTimoutRef.current = setTimeout(() => {
pausedRef.current = false;
}, timeout);
}
// After calling the callback, schedule it to run in 'timeout'.
// Everytime the callback is called, clear the previous scheduled run.
// This makes sure the "last" called callback in a continuous series of calls is run even though it's called when paused.
clearTimeout(scheduledCallbackTimoutRef.current);
scheduledCallbackTimoutRef.current = setTimeout(() => {
callbackRef.current();
}, timeout);
}, [timeout]);
}
export default useRateLimitedCallback;
@kreegr
Copy link

kreegr commented Mar 2, 2021

Very nice! Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment