Feb 17 2021

Using debounce for a search component built in React

by Dillon RaphaelFeb 02 2021

Recently I worked on a search component that would communicate with an API that uses ElasticSearch for indexing. It's common to have a search input, that as you type will auto query and update the list on the frontend.

So I used a text input, and inside the onChange event would pass the event.target.value as a parameter to the API url.

It would look something like this (abbreviated for readability):

const searchQueryFunction = (queryText) => {

	const res = await fetch(`/products.json?_page=${page}&_limit=10&_q=${queryText}&_total=50`, {
    method: 'GET',
    headers: {
     'Access-Control-Allow-Origin': '*'

  const responseData = await res.json()

	setItems({items: responseData.data})

<input type="text" placeholder="Search..." onChange=((e) => searchQueryFunction(e.target.value)) />

But I quickly found out how taxing this was for the browser and server. As I would search for something like "Minivan", it would query each letter. This is where a debouncer comes in. There are libraries like lodash that offer a function, but wanted to keep my package.json light.

A debouncer is just a fancy setTimeout, so I created a Debouncer class that takes 2 parameters. A function and time.

export default class Debouncer {
  timeout: null | ReturnType<typeof setTimeout>;
  n: number;
  func: Function;

  constructor(func, n) {
    this.timeout = null;
    this.n = n || 500;
    this.func = func;

  execute = (e) => {
    this.timeout = setTimeout(() => {
    }, this.n);

  cancel = () => {
    if (this.timeout) clearTimeout(this.timeout);

Inside the React component, initialize the Debouncer and pass it the searchQueryFunction

const debouncer = new Debouncer((e) => searchQueryFunction(e))

Then create a new function that will execute the searchQueryFunction through the debouncer

const execDebouncer = (e) => debouncer.execute(e)

Now inside the input onChange event, well replace calling the searchQueryFunction directly and use the new execDebouncer

<input type="text" onChange={(e) => execDebouncer(e)} />

But.. you'll notice some errors in the log:

"This synthetic event is reused for performance reasons. If you're seeing this, you're accessing the method currentTarget on a released/nullified synthetic event. This is a no-op function. If you must keep the original synthetic event around, use event.persist()"

This is because the event is lost, while the function is being debounced for 500ms. To fix this, simply add e.persist() inside the execDebouncer function

const execDebouncer = (e) => {
  return debouncer.execute(e)

I put together a little example, with an included mock api:

Recommended Posts