Last active
September 22, 2022 10:14
-
-
Save mudge/eb9178a4b6d595ffde8f9cb31744afcf to your computer and use it in GitHub Desktop.
A custom React Hook for a debounced click handler with a given callback and delay.
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
/* | |
* Inspired by Dan Abramov's "Making setInterval Declarative with React Hooks", | |
* this is a custom hook for debouncing a callback (e.g. for click handlers) such | |
* that a callback will not be fired until some delay has passed since the last click. | |
* The callback will automatically be updated with the latest props and state on every | |
* render meaning that users don't need to worry about stale information being used. | |
* | |
* See https://overreacted.io/making-setinterval-declarative-with-react-hooks/ for the | |
* original inspiration. | |
*/ | |
import React, { useState, useEffect, useRef } from 'react'; | |
const useDebounce = (callback, delay) => { | |
const latestCallback = useRef(); | |
const latestTimeout = useRef(); | |
useEffect(() => { | |
latestCallback.current = callback; | |
}, [callback]); | |
return () => { | |
if (latestTimeout.current) { | |
clearTimeout(latestTimeout.current); | |
} | |
latestTimeout.current = setTimeout(() => { latestCallback.current(); }, delay); | |
}; | |
}; | |
const App = () => { | |
const [count, setCount] = useState(0); | |
const handleIncrement = () => setCount(count => count + 1); | |
const handleClick = useDebounce(() => alert(`I've been clicked ${count} times`), 3000); | |
return ( | |
<> | |
<button onClick={handleClick}>Click</button> | |
<button onClick={handleIncrement}>Increment {count}</button> | |
</> | |
); | |
} | |
export default App; |
Phew, thanks for putting my mind at ease on both points.
How do I stop the propagation when the handler is an onSubmit function on form?
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
You’re very welcome! It was a lot of fun to think about.
Fear not:
Date.now()
returns a number rather than aDate
instance, and the spec explicitly says “leap seconds are ignored”, so stuff like daylight savingsand leap seconds[0] shouldn’t be a problem (although I guess that depends on what exactly “ignored” means). Of course we’re exposed to all the usual issues that can occur if the system clock is set backwards or forwards in the middle of our code, but I think that’s the price we pay for involving global time.Fear not:
setTimeout()
’sdelay
argument is already clamped to zero (see timer initialisation step 10) so we don’t need to do this ourselves.[0] Having thought about this, I suspect “leap seconds are ignored” probably means that it is a problem. If that were important then I guess we could use
performance.now()
as our clock and fall back toDate.now()
only when it’s unsupported, or of course switch back to thecallCount
Lamport clock and avoid global time altogether. Pragmatically it seems likely that leap seconds aren’t significant enough to worry about.