Debounce behaviour is helpful when we want additional control over when a function is called. When implemented properly, we can call a debounced function any number of times, but it will only be invoked after a specified amount of time has passed from its last call. This is helpful in scenarios such as waiting for the user to finish typing before sending out a network request.
You may encounter difficulties when implementing this with React, as additional re-renders may disrupt timed callback behaviour.
Let’s see what we can do about this 😎
❌ What does not work: using debounce directly inside the render
In our example below, we are trying to capture the user’s input to our analytics service after they have finished typing.
If we try to use debounce directly, you’ll notice that it will be fired with every keystroke — since this causes the component to re-render from
✅ What works: wrapping debounce in a useCallback hook
Using the same example, we can get things to work by wrapping the debounced
trackAnalytics() function in a
useCallback() optimises the rendering behaviour by memoizing the function across renders. We can achieve a similar memoized function by using a
useRef() hook, such as in this example:
const debouncedFn = useRef(debounce(trackAnalytics, 500)).current;
The main takeaway here is to always be conscious of where your logic falls in React’s render cycle, especially if it includes timed side effects 🕒.