Created
March 1, 2021 23:57
-
-
Save johnloven/32da29cd73611ddd5f7235573c6a450f to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Very nice! Thanks!