-
-
Save awran5/113359eb1f98d1469f7ebbcf04e54da1 to your computer and use it in GitHub Desktop.
✨ Declarative useTimeout (setTimeout), useInterval (setInterval) and useThrottledCallback (useCallback combined with setTimeout) hooks for React (in Typescript)
This file contains 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 React, { useEffect, useRef } from 'react'; | |
/** | |
* Use setInterval with Hooks in a declarative way. | |
* | |
* @see https://stackoverflow.com/a/59274004/3723993 | |
* @see https://overreacted.io/making-setinterval-declarative-with-react-hooks/ | |
*/ | |
export function useInterval( | |
callback: React.EffectCallback, | |
delay: number | null, | |
): React.MutableRefObject<number | null> { | |
const intervalRef = useRef<number | null>(null); | |
const callbackRef = useRef(callback); | |
// Remember the latest callback: | |
// | |
// Without this, if you change the callback, when setInterval ticks again, it | |
// will still call your old callback. | |
// | |
// If you add `callback` to useEffect's deps, it will work fine but the | |
// interval will be reset. | |
useEffect(() => { | |
callbackRef.current = callback; | |
}, [callback]); | |
// Set up the interval: | |
useEffect(() => { | |
if (typeof delay === 'number') { | |
intervalRef.current = window.setInterval(() => callbackRef.current(), delay); | |
// Clear interval if the components is unmounted or the delay changes: | |
return () => window.clearInterval(intervalRef.current || 0); | |
} | |
}, [delay]); | |
// In case you want to manually clear the interval from the consuming component...: | |
return intervalRef; | |
} |
This file contains 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 { useCallback, useEffect, useRef } from 'react'; | |
export function useThrottledCallback<A extends any[]>( | |
callback: (...args: A) => void, | |
delay: number, | |
deps?: readonly any[], | |
): (...args: A) => void { | |
const timeoutRef = useRef<number>(); | |
const callbackRef = useRef(callback); | |
const lastCalledRef = useRef(0); | |
// Remember the latest callback: | |
// | |
// Without this, if you change the callback, when setTimeout kicks in, it | |
// will still call your old callback. | |
// | |
// If you add `callback` to useCallback's deps, it will also update, but it | |
// might be called twice if the timeout had already been set. | |
useEffect(() => { | |
callbackRef.current = callback; | |
}, [callback]); | |
// Clear timeout if the components is unmounted or the delay changes: | |
useEffect(() => window.clearTimeout(timeoutRef.current), [delay]); | |
return useCallback((...args: A) => { | |
// Clear previous timer: | |
window.clearTimeout(timeoutRef.current); | |
function invoke() { | |
callbackRef.current(...args); | |
lastCalledRef.current = Date.now(); | |
} | |
// Calculate elapsed time: | |
const elapsed = Date.now() - lastCalledRef.current; | |
if (elapsed >= delay) { | |
// If already waited enough, call callback: | |
invoke(); | |
} else { | |
// Otherwise, we need to wait a bit more: | |
timeoutRef.current = window.setTimeout(invoke, delay - elapsed); | |
} | |
}, deps); | |
} |
This file contains 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 React, { useEffect, useRef } from 'react'; | |
/** | |
* Use setInterval with Hooks in a declarative way. | |
* | |
* @see https://stackoverflow.com/a/59274004/3723993 | |
* @see https://overreacted.io/making-setinterval-declarative-with-react-hooks/ | |
*/ | |
export function useThrottledRAF( | |
callback: React.EffectCallback, | |
delay: number | null, | |
): [React.MutableRefObject<number | null>, React.MutableRefObject<number | null>] { | |
const intervalRef = useRef<number | null>(null); | |
const rafRef = useRef<number | null>(null); | |
const callbackRef = useRef(callback); | |
// Remember the latest callback: | |
// | |
// Without this, if you change the callback, when setInterval ticks again, it | |
// will still call your old callback. | |
// | |
// If you add `callback` to useEffect's deps, it will work fine but the | |
// interval will be reset. | |
useEffect(() => { | |
callbackRef.current = callback; | |
}, [callback]); | |
// Set up the interval: | |
useEffect(() => { | |
if (typeof delay === 'number') { | |
intervalRef.current = window.setInterval(() => { | |
rafRef.current = window.requestAnimationFrame(() => { | |
callbackRef.current(); | |
}); | |
}, delay); | |
// Clear interval and RAF if the components is unmounted or the delay changes: | |
return () => { | |
window.clearInterval(intervalRef.current || 0); | |
window.cancelAnimationFrame(rafRef.current || 0); | |
}; | |
} | |
}, [delay]); | |
// In case you want to manually clear the interval or RAF from the consuming component...: | |
return [intervalRef, rafRef]; | |
} |
This file contains 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 React, { useEffect, useRef } from 'react'; | |
/** | |
* Use setTimeout with Hooks in a declarative way. | |
* | |
* @see https://stackoverflow.com/a/59274757/3723993 | |
* @see https://overreacted.io/making-setinterval-declarative-with-react-hooks/ | |
*/ | |
export function useTimeout( | |
callback: React.EffectCallback, | |
delay: number | null, | |
): React.MutableRefObject<number | null> { | |
const timeoutRef = useRef<number | null>(null); | |
const callbackRef = useRef(callback); | |
// Remember the latest callback: | |
// | |
// Without this, if you change the callback, when setTimeout kicks in, it | |
// will still call your old callback. | |
// | |
// If you add `callback` to useEffect's deps, it will work fine but the | |
// timeout will be reset. | |
useEffect(() => { | |
callbackRef.current = callback; | |
}, [callback]); | |
// Set up the timeout: | |
useEffect(() => { | |
if (typeof delay === 'number') { | |
timeoutRef.current = window.setTimeout(() => callbackRef.current(), delay); | |
// Clear timeout if the components is unmounted or the delay changes: | |
return () => window.clearTimeout(timeoutRef.current || 0); | |
} | |
}, [delay]); | |
// In case you want to manually clear the timeout from the consuming component...: | |
return timeoutRef; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment