Debouncing patterns for React Events

A comparison of debouncing techniques in various contexts

RC
5 min readJun 12, 2021

This post will help you understand how to use debounce technique to control how many times a function is executed within a given time frame. Specifically, if that function is a callback to a user event, then there are nuances to make note of in that specific scenario.

We will unfold debounce as a concept and then illustrate its usage in various contexts of a React application as follows:

  1. Why do we need debouncing?
  2. Debouncing in a React class component
  3. Debouncing in a React functional component
  4. Debouncing using React hooks

In each of the scenarios, we will walk-through working code examples to help you understand how to take care of debouncing in each case.

Why do we need debouncing?

Let us design a simple stock search tool that where you can lookup a stock by searching for the ticker or part of the company name.

The final end result should look like below where the results start showing up as the user types progressively on the search box.

Raw lookup without debouncing

Typical Implementation:

  • We will wire an onChange handler to the input text box. As the user types, it will trigger the handler repeatedly with the contents of the search box.
  • The handler will call an external API asynchronously with the text as parameter to fetch the results. If the input is walmart , the network calls that would be fired in the following sequence. Remember that the results might not be fetched in same order. Network calls are unpredictable, so it is possible that the second call resolves later than the last one and now you have stale data to process.
https://ticker-2e1ica8b9.now.sh/keyword/w
https://ticker-2e1ica8b9.now.sh/keyword/wa
https://ticker-2e1ica8b9.now.sh/keyword/wal
https://ticker-2e1ica8b9.now.sh/keyword/walm
...
  • Also every network call will yield a payload that will be continuously rendered below the text box (all in a quick fashion on the UI)

What can we do better?

  • We are fetching results in an inefficient way trying to guess the results even before the user has completed entering the input.
  • This condition is exacerbated by the fact that we are making an external API call to fetch the results and we are making numerous network calls discarding the results instantaneously.
  • Remember that the old result will be overridden with new results, one network call after another.

This is a classic use-case for applying the debounce pattern. Let’s check out the definition of “debounce” explained well here.

Debouncing enforces that a function not be called again until a certain amount of time has passed without it being called. As in “execute this function only if 100 milliseconds have passed without it being called”.

So, in our case, we would have an event handler function that responds to the user input to the text box. That function is responsible for making the API network call with the user input as a parameter to fetch the results. We should “slow down” that event handler to not call for every user keystroke but only when user pauses after she is done typing. Which is exactly what debouncing does for us.

Note: For the curious, Debouncing should not be confused with its close cousin Throttling . This visual demo will help you understand the difference better.

So, by debouncing the event handler function, we can specify a time interval till which the function will keep accumulating invocations to it and when the time interval expires, it will execute the latest invocation.

# Within interval (user might be typing)
onInput('w') <- wait
onInput('wa') <- wait
onInput('wal') <- wait
onInput('walm') <- wait
# time interval exceeded (user may have paused)
onInput('walm') <- execute

The code without any debouncing technique applied (as a react class component to start with) is presented for your reference below. The onInput event handler function is prime candidate for debouncing.

class component example

If you are inclined to play with the code, jump on to the sandbox link

Debouncing a React class component

Debounce as a function is available with many popular libraries or standalone. We will use the debounce helper available as part of the popular Lodash library.

The relevant part of the code change is shown below:

class component with debounce example

You can notice that in the class constructor, we are decorating the onInput callback function with the debouncing helper and a time interval of 500ms.

Now, the application behavior would look more polished compare to where we started at the top of the post:

Lookup with debouncing

If you monitor the external network calls, it might be reduced by a considerable percentage now, only happening when the user input is paused. As seen in the snapshot above, I am guessing only twice. Related code available here.

Now, that we understand what debouncing can do for us, let’s clean up the legacy React code and move on to how the cool kids write components now…

Debouncing a React functional component

Applying debouncing effect on a callback handler of a functional component is conceptually similar and checkout the refactoring below.

Debouncing using a functional component

On close examination, you might obviously question why not debounce the onInput function directly and why introduce another debounceHandler function.

This is recommended especially in latest versions of React because of the way event pooling is handled. Please read about Synthetic Events to know more about why it is better to persist the event before passing it onto a separate debounceHandler. Code available here.

Debouncing as a hook

Perhaps the cleanest way to applying debouncing is to model it as a React Hook. I prefer the hook version because it is easier to write a reusable decorator that can be tested in isolation without having to setup the entire component. It’s fairly simple to write the hook from scratch. Or you can use the useDebouncedCallback hook that does that.

The above hook plays well with functional components and is a lot more succinct. Code for the above setup is available for reference.

Let us know in the comments if you have used other techniques to achieve what is discussed in this post. Cheers!

Learn More

--

--

RC

Full Stack Engineer @ Pro.com , Ex-ThoughtWorker • Ruby,JavaScript, iOS, Frontend, Devops https://rcdexta.com