Lesson 9. Debouncing a user input 4. Another debouncing implementation
Let’s see another way of implementing debouncing and why it is necessary in some cases.
Imagine we have a hook useTime at our disposal (we are not allowed to modify
it), which returns the current time, and we want to display this time in our
component. But the hook returns a new time every 10 milliseconds, while we just
want to update the displayed value every 250 milliseconds.
Here is the first version of our component without any debouncing.
Result
As you can see, the displayed time is updated way too fast, and we are just displaying it; imagine if we did some processing at each update.
Can you see why the previous solution for debouncing cannot apply here? It would a little bit like that:
useEffect(() => {let timeout = setTimeout(() => {setDebouncedTime(time)}, 250)return () => clearTimeout(timeout)}, [time])
Problem: time is updated every 10 milliseconds, meaning that our timeouts
would be systematically cancelled, and our debouncedTime would never be
updated.
To solve our problem, we won’t cancel the timeout when time changes. Instead,
we will start a new timeout only if the previous one is done. To do that, we
need to store the ID of the current timeout.
And since this stored ID must persist from one rendering to another, but we don’t need to trigger a new effect each time is updated, we’ll use a ref.
const timeoutRef = useRef(null)useEffect(() => {if (!timeoutRef.current) {timeoutRef.current = setTimeout(() => {setDebouncedTime(time)timeoutRef.current = null}, 250)}}, [time])
Notice that we don’t return a clean-up function to clear the timeout. Indeed we
don’t want to cancel the timeout each time time is updated. But still, we need
to be sure we don’t update the state calling setDebouncedTime when the
component is unmounted.
To make sure of that, we can declare another useEffect without any value in
the dependency array. The clean-up function we return will be executed only when
the component is unmounted for good, so we can clear any remaining timeout
there:
useEffect(() => {return () => {if (timeoutRef.current) {clearTimeout(timeoutRef.current)}}}, [])
Here is the full version of our component with debouncing:
Result