Often, we want to wait until our user finishes an action to execute an asynchronous effect. A great example of this is executing a search after a user finishes typing rather than doing so on each key stroke. This prevents us from jarring UI changes or firing many unnecessary and potentially expensive fetch requests.
In this post, we'll write a custom React hook that debounces any effect!
Writing Our Hook
Our hook should look just like a useEffect
hook, with the exception that it should take an additional time
parameter for the amount of time we want to debounce for. Therefore, the parameters should be:
- The effect function
- The dependency array
- The debounce time
Achieving the debounce behavior
To debounce, we'll use a setTimeout
with the user-provided time
. The catch is that, if our effect re-runs before the timeout executes, we'll want to cancel the timeout and start a new one. We can accomplish that by using a cleanup function with clearTimeout
. Our hook, therefore, is as follows:
import { useEffect } from "react";
function useDebouncedEffect(fn, deps, time) {
const dependencies = [...deps, fn, time]
useEffect(() => {
const timeout = setTimeout(fn, time);
return () => {
clearTimeout(timeout);
}
}, dependencies);
}
Seeing the Hook in Action
In this example, we'll simply set some state on a debounced delay based on when a user stops typing in a textbox. Here's the code!
function App() {
const [text, setText] = useState("")
const [debounced, setDebounced] = useState("")
useDebouncedEffect(() => {
setDebounced(text);
}, [text], 1000)
return (
<div className="App">
<input onChange={e => {
setText(e.target.value)
}} value={text}
/>
<p>{debounced}</p>
</div>
);
}
And when we try it in action... it works!
Top comments (3)
Nice example. It was fun playing around with it.
It works in this simplified case but if their is someother higher priority update going on in the same component it will not work. The issue is that the instance of
fn
is always new which triggers theuseEffect
insideuseDebouncedEffect
. I think usinguseCallback
for that would fix the issue.I hadn’t thought about this! I’m going to try it out an amend the post later :). Thank you!
Not sure in every case people gonna run into this but their is a chance.