Using debounce within the React render cycle

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 useState.

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() hook.

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 🕒.

Hello. I write primarily about programming and technology. I’m also fluent in film references.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store