Make your React Apps more performant using Debouncing & Throttling! 🔥🚀
Tags: javascript, webdev, react, programming
Published: 2022-06-28 13:23:30

---
title: Make your React Apps more performant using Debouncing & Throttling! 🔥🚀
published: true
date: 2022-06-28 12:14:58 UTC
tags: javascript, webdev, react, programming
canonical_url: https://thecodingcookbook.hashnode.dev/make-your-react-apps-more-performant-using-debouncing-and-throttling
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/68kdwggeabnav447p3jb.png
---

Well, hello there! 👋 I see you have come here to learn more about how to make your React apps performant and optimize them using Debouncing and Throttling, which is great because that means you really do care about your app's performance. Kudos for that! 👏

Do note that this blog assumes that you have a **basic understanding of how React works** and that you are familiar with **React Hooks**.

Before we jump in, let's understand why would you want to optimize your React app's performance?

Suppose you have a very simple React app with an input bar to search cities like the below,

![Debouncing-Throttling-demo-1.gif](https://cdn.hashnode.com/res/hashnode/image/upload/v1656333065703/-Af0jIB_I.gif)

As you can see, this app is super laggy and the UX of this app is 💩. We are just making a very simple search that filters cities from a list of cities based on user input.

PS:- You can give it a try if you want (Please do it at your own risk, you don't wanna hang your computer!) - https://codesandbox.io/s/debouncing-example-demo-0dyb16?file=/src/App.jsx

### Now, you might ask why's this React app so laggy?

![Why's the app super laggy?](https://media.giphy.com/media/dXICCcws9oxxK/giphy.gif)

If you would have noticed carefully from the above demo of the app, we are filtering the cities from the list of cities that we have on every keystroke made by the user (notice the keystrokes on the Virtual keyboard in the demo).

See now, that's not a performant app at all and needs to be optimized to provide a better user experience.

Let's have a look at two ways to optimize such apps and make them better!

![Let's make our React apps better](https://media.giphy.com/media/BpGWitbFZflfSUYuZ9/giphy.gif)

## What is Debouncing & Throttling?

There are many scenarios that make your app less performant like making API calls on each user keystroke on an input search bar, performing compute-heavy operations on button clicks, window resizing, or frequent scrolls on a scrollbar.

Basically, any scenario in which you are making expensive (in terms of computing or execution time) function calls on events or user actions that can hamper the performance of your apps.

Now, let's understand **Debouncing** & **Throttling**.

**Debouncing** : In debouncing, we try to reduce the number of expensive function calls by calling them **only if the time difference between two consecutive event triggers** (user actions) is **greater than or equal** to a **specified delay**. This **delay** can be adjusted depending on the **use case** or the kind of **user experience** you are trying to design for your app.

**Throttling** : In throttling, we try to **rate-limit** the number of expensive function calls by calling them every time **only after a certain time limit** has passed from the **last function call**. Again, this time limit can be adjusted depending on your use case.

Debouncing & Throttling can be very useful to handle **rate-limit errors** caused by **rate-limiting** on certain APIs that your apps might be consuming, as we are trying to reduce the number of such expensive function calls using these optimizations.

Now that you have some idea about Debouncing & Throttling, let's dive deeper into each concept using a simple example that illustrates some of their common use cases.

![let's do this!](https://media.giphy.com/media/xT39Db8zIOODTppk08/giphy-downsized-large.gif)

### Optimizing apps using Debouncing

Let's go back to the very first example that we saw, where we had a simple search bar that filters the cities from a list of cities based on the user input.

We can use **debouncing** in this case to reduce the number of function calls to filter the cities from the list.

But first, let's look at the initial code from the demo.

**Initial Code** -

```
import "./styles.css";
import React, { useState } from "react";
import cities from "cities-list";
import { v4 as uuidv4 } from "uuid";

// An array of city names
const citiesArray = Object.keys(cities);

export default function App() {
  const [cityInput, setCityInput] = useState("");
  const [filteredCities, setFilteredCities] = useState([]);

  // Function that filters cities from the list based on user input
  const cityFilter = (query) => {
    console.log(query);
    if (!query) return setFilteredCities([]);

    setFilteredCities(
      citiesArray.filter((city) =>
        city.toLowerCase().includes(query.toLowerCase())
      )
    );
  };

  return (
    <div className="App">
      <h1 className="app-header">Find cities</h1>
      <div className="city-input">
        <input
          type="text"
          value={cityInput}
          onChange={(e) => {
            setCityInput(e.target.value);
            cityFilter(e.target.value);
          }}
        />
      </div>
      <div>
        {filteredCities.map((city) => {
          return <div key={uuidv4()}>{city}</div>;
        })}
      </div>
    </div>
  );
}

```

The above code snippet represents a **simple React component** with an **input search bar** , and a **container that displays the filtered cities**.

```
// Function that filters cities from the list based on user input
  const cityFilter = (query) => {
    console.log(query);
    if (!query) return setFilteredCities([]);

    setFilteredCities(
      citiesArray.filter((city) =>
        city.toLowerCase().includes(query.toLowerCase())
      )
    );
  };

```

The function `cityFilter` takes a **user search query** as an input parameter and filters the cities from a list of cities (fetched from an npm package called `cities-list`). Currently, this function runs on every single keystroke made by the user on the search bar.

Now, let's write a **debounced version** of the above `cityFilter` function to make it more optimal. We will be using `setTimeout` in JavaScript to achieve this.

```
// `timer` to help while clearing setTimeout 
// inside `debouncedCityFilter` function
let timer;

// Debounced version of the `cityFilter` func to filter cities 
// based on user search query
  const debouncedCityFilter = (query) => {
    clearTimeout(timer);
    if (!query) return setFilteredCities([]);

    timer = setTimeout(() => {
      console.log(query);

      setFilteredCities(
        citiesArray.filter((city) =>
          city.toLowerCase().includes(query.toLowerCase())
        )
      );
    }, 500);
  };

```

According to the concept of debouncing, we make function calls only if the **time difference between two consecutive event triggers** (user actions) is **greater than or equal** to a specified delay.

In the above code snippet, we set the state to get the filtered cities using `setFilteredCities()` which is called inside a `setTimeout` with a delay of `500ms`(this delay can be adjusted according to the use case). So whenever a user keystroke is recorded on the input search bar, the `debouncedCityFilter` function is called which triggers `setTimeout` and sets the state using `setFilteredCities()` after `500ms`.

However, if another keystroke made by the user is recorded just within this time delay of `500ms`, the previous `setTimeout` needs to be cleared to avoid filtering the cities and set the state. For this, we use `clearTimeout` that takes the `id` returned by the `setTimeout` function.

Now, this `id` needs to be preserved so that it is available whenever we need to use `clearTimeout` to clear the timer. We use a quite popular concept called [Closures](https://www.freecodecamp.org/news/lets-learn-javascript-closures-66feb44f6a44/) in JavaScript to be able to access this `id` inside the `debouncedCityFilter` function. Hence, if you'd have noticed we have defined a `timer` variable outside the `debouncedCityFilter` function for use inside this function.

By simply debouncing the `cityFilter` function, we are able to reduce the number of function calls and hence able to improve the performance significantly of our React app.

Let's have a look at what our React component code looks like after making these changes.

**Final Code** -

```
import "./styles.css";
import React, { useState } from "react";
import cities from "cities-list";
import { v4 as uuidv4 } from "uuid";

// An array of city names
const citiesArray = Object.keys(cities);

// `timer` to help while clearing setTimeout 
// inside `debouncedCityFilter` function
let timer;

export default function App() {
  const [cityInput, setCityInput] = useState("");
  const [filteredCities, setFilteredCities] = useState([]);

  // Function that filters cities from the list based on user input
  const cityFilter = (query) => {
    console.log(query);
    if (!query) return setFilteredCities([]);

    setFilteredCities(
      citiesArray.filter((city) =>
        city.toLowerCase().includes(query.toLowerCase())
      )
    );
  };

  // Debounced version of the `cityFilter` func to filter 
  // cities based on user search query
  const debouncedCityFilter = (query) => {
    clearTimeout(timer);
    if (!query) return setFilteredCities([]);

    timer = setTimeout(() => {
      console.log(query);

      setFilteredCities(
        citiesArray.filter((city) =>
          city.toLowerCase().includes(query.toLowerCase())
        )
      );
    }, 500);
  };

  return (
    <div className="App">
      <h1 className="app-header">Find cities</h1>
      <div className="city-input">
        <input
          type="text"
          value={cityInput}
          onChange={(e) => {
            setCityInput(e.target.value);
            debouncedCityFilter(e.target.value);
          }}
        />
      </div>
      <div>
        {filteredCities.map((city) => {
          return <div key={uuidv4()}>{city}</div>;
        })}
      </div>
    </div>
  );
}

```

Now, have a look at how debouncing has improved the performance of this component significantly! 🚀

{% embed https://codesandbox.io/embed/debouncing-example-demo-0dyb16?fontsize=14&hidenavigation=1&theme=dark %}

If you want to handle more edge cases for debouncing such functions, then you can check out [Lodash](https://lodash.com/) which has a `debounce` method that covers most of the edge cases involved to make such functions more optimal.

Now, let's look at a simple example that uses Throttling to make it more performant.

### Optimizing apps using Throttling

Let's suppose, you have a simple React component consisting of a `button` that on clicking **calls an API** to fetch some data related to all the currencies of different countries.

**Initial Code** -

```
import "./styles.css";
import React, { useState } from "react";
import axios from "axios";
import { v4 as uuid } from "uuid";

export default function App() {
  const [currencyData, setCurrencyData] = useState({});
  const [clickCounter, setClickCounter] = useState(0);

  const getCurrencyData = async () => {
    console.log("Fetching data ....");

    const { data } = await axios.get(
      "https://cdn.jsdelivr.net/gh/fawazahmed0/currency-api@1/latest/currencies.json"
    );

    // Fetching only 15 currencies for now
    const countryCurrencies = {};
    const currencyObjKeys = Object.keys(data).slice(0, 15);

    currencyObjKeys.forEach((key) => {
      countryCurrencies[key] = data[key];
    });

    setCurrencyData({ ...countryCurrencies });
  };

  return (
    <div className="App">
      <h1>Currencies of different Countries</h1>
      <button
        className="currency-btn"
        onClick={() => {
          setClickCounter((clickCount) => clickCount + 1);
          getCurrencyData();
        }}
      >
        Click to get all currencies
      </button>
      <span>Btn clicked - {clickCounter} times</span>
      <div className="currencies">
        {Object.keys(currencyData).map((currency) => {
          return (
            <div key={uuid()}>
              {currency}: {currencyData[currency]}
            </div>
          );
        })}
      </div>
    </div>
  );
}

```

The code snippet above is our simple component with two states - `currencyData` & `clickCounter`. On button click, we update the `clickCounter` state to reflect the total number of button clicks made so far and also call the `getCurrencyData()` function to make an API call to fetch the currency data.

Let's look at what this component looks like!

![Debouncing-&-Throttling-demo-2.gif](https://cdn.hashnode.com/res/hashnode/image/upload/v1656412297772/DeZ8Hoab5.gif)

As you might have noticed above, each button click triggers an API call. Now, imagine that your app was used by hundreds or thousands of users, the number of API calls would be humongous! Your Back-End servers could face a huge pool of requests coming from each user due to so many clicks. Also, if you are consuming any external paid API or service, then the endpoints might start throwing errors because of **rate-limiting** on the API endpoints.

Even if say you were not making any API calls on such button clicks but rather performing some **compute-heavy** operation, it would hamper your app's performance severely!

Now, that's a bit of a problem 😅 ![Time to solve some problems!](https://media.giphy.com/media/1Y7ChRtbWnYONjDidg/giphy.gif)

Let's try and solve this problem using Throttling!

We will throttle the `getCurrencyData` function that makes an API call on each button click.

Currently, the code for `getCurrencyData` looks like this,

```
const getCurrencyData = async () => {
    console.log("Fetching data ....");

    const { data } = await axios.get(
      "https://cdn.jsdelivr.net/gh/fawazahmed0/currency-api@1/latest/currencies.json"
    );

    // Fetching only 15 currencies for now
    const countryCurrencies = {};
    const currencyObjKeys = Object.keys(data).slice(0, 15);

    currencyObjKeys.forEach((key) => {
      countryCurrencies[key] = data[key];
    });

    setCurrencyData({ ...countryCurrencies });
  };

```

Now, we will write a function `throttledGetCurrencyData` that will throttle and use the `getCurrencyData` function to reduce the number of calls made to it.

```
// A flag to control the function calls to the `getCurrencyData` function
let shouldFuncBeCalled = true;

const throttledGetCurrencyData = async () => {
    if (shouldFuncBeCalled) {
      await getCurrencyData();
      shouldFuncBeCalled = false;

      setTimeout(() => {
        shouldFuncBeCalled = true;
      }, 500);
    }
  };

```

The `throttledGetCurrencyData` function calls the `getCurrencyData` function only if the `shouldFuncBeCalled` flag is set to `true`. Once this function is called, we delay the next function call to the `getCurrencyData` function by using `setTimeout` with some specific delay (This delay limit can be adjusted as per your use case).

This way we only allow function calls to happen after a certain amount of time has passed from the last function call. This way we can avoid making the UI slow or crossing the rate limits defined for any API that your app might be consuming.

Let's look at how the app is working now.

![Debouncing-&-Throttling-demo-3.gif](https://cdn.hashnode.com/res/hashnode/image/upload/v1656414439574/RSIAQ7IHe.gif)

As you can see from the console, the number of API calls has been reduced quite significantly even after clicking the button so many times!

Check out the CodeSandbox below to see what the code for our component looks like after using Throttling.

{% embed https://codesandbox.io/embed/throttling-demo-solution-70o8ig?fontsize=14&hidenavigation=1&theme=dark %}

If you want to handle more edge cases for throttling such functions, then you can check out [Lodash](https://lodash.com/) which has a `throttle` method that covers most of the edge cases involved to make such functions more optimal.

## Debouncing vs Throttling, when to use what?

Now that we understand how Debouncing and Throttling work, let's understand some of the differences and when to use Debouncing or Throttling.

**Throttling** enforces that a function must be called every time after a certain amount of time (or delay) has passed from the last function call.

Whereas, **Debouncing** enforces that a function must only be called if a certain amount of time (or delay) has passed without it being called. If this time has not been passed the **debounce timer** keeps **resetting** and the **function call is avoided**.

### When to use what?

- **Search bar** : Use **debouncing** to avoid searching every time a user hits a keystroke. **Throttling** is not convenient here to use in this scenario, as you don't want to make your user wait too long for fetching the search results (in the worst case if the previous function call was made just when the user stops typing).

- **Shooting game** : Use **throttling** on mouse click as shooting a pistol takes a few secs to register and it helps in avoiding the user to shoot until the previous shot has been registered. **Debouncing** will not shoot a bullet until a certain amount of time has passed when the pistol was not fired.

You can also check out this amazing [Stackoverflow post](https://stackoverflow.com/questions/25991367/difference-between-throttling-and-debouncing-a-function) to understand the differences between **Debouncing** & **Throttling** and when to use what.

## Conclusion

**Debouncing** & **Throttling** are just a few ways you can make your React apps more performant & each technique has its own set of pros and cons depending on the use cases. In this blog, we first talked about **Why should we care about our React app's performance** , then we understood **how we can use Debouncing & Throttling** to optimize our app's performance, and finally saw a **major difference between the two techniques** and **when we to use which technique**.

That's it from me folks, thank you so much for reading this blog! 🙌 I hope this blog was helpful and gave you an insight on how you can make your React apps more performant. Now, go ahead and make your apps even more amazing! 🚀

![Until next time!](https://media.giphy.com/media/d3yvDeQ9fES0JTfG/giphy.gif)

> Feel free to reach out to me:
> [Twitter](https://twitter.com/DevanshYtweets)
> [Linkedin](https://www.linkedin.com/in/devansu-yadav/)
> [GitHub](https://github.com/Devansu-Yadav)
> You can also reach out to me over mail: [devansuyadav@gmail.com](mailto:devansuyadav@gmail.com)