How to debounce and throttle API calls in React
Tags: react, javascript
Published: 2021-05-17 05:04:59

You might have come across some websites when you search for something and it starts stuttering while the autosuggestion is being displayed. This happens since the browser is trying to update the autosuggestion list as well as the input box with the key typed by the user.

We can prevent the stuttering to an extent by means of "debounce" and "throttling".

# What are "debounce" and "throttling"?

- Performing the search only after `x` milliseconds (or seconds) after the user has stopped typing is called "debounce". It prevents unnecessary network calls from being made.

- Performing the search every `x` milliseconds (or seconds) while the user is typing is called "throttling". If the user is typing a long-phrase and we are using "debounce",
  then they might feel that the autosuggestion is not working (since we wait for the user to finish typing to make any API calls). In such cases, we make use of throttling such that the autosuggestion updates at a fixed time interval.

> If you feel that the API response is slow,
> you can improve the user experience by canceling previous requests (if we are yet to get a response).
> I have written a tutorial on it, you can read it [here](https://www.codingdeft.com/posts/axios-cancel-previous-request-react/).

# Setting up a mock server

Let's set up a [json-server](https://github.com/typicode/json-server), which will serve as a backend API for our demonstration.
Let's install `json-server` globally so that we can use it in the future!

```bash
npm install -g json-server
```

Create a `db.json` file in the place of your choice with the following JSON data:

```
{"animals":[{"id":0,"name":"Aardvark"},{"id":1,"name":"Albatross"},{"id":2,"name":"Alligator"},{"id":3,"name":"Alpaca"},{"id":4,"name":"Ant"},{"id":5,"name":"Anteater"},{"id":6,"name":"Antelope"},{"id":7,"name":"Ape"},{"id":8,"name":"Armadillo"},{"id":9,"name":"Donkey"},{"id":10,"name":"Baboon"},{"id":11,"name":"Badger"},{"id":12,"name":"Barracuda"},{"id":13,"name":"Bat"},{"id":14,"name":"Bear"},{"id":15,"name":"Beaver"},{"id":16,"name":"Bee"},{"id":17,"name":"Bison"},{"id":18,"name":"Boar"},{"id":19,"name":"Buffalo"},{"id":20,"name":"Butterfly"},{"id":21,"name":"Camel"},{"id":22,"name":"Capybara"},{"id":23,"name":"Caribou"},{"id":24,"name":"Cassowary"},{"id":25,"name":"Cat"},{"id":26,"name":"Caterpillar"},{"id":27,"name":"Cattle"},{"id":28,"name":"Chamois"},{"id":29,"name":"Cheetah"},{"id":30,"name":"Chicken"},{"id":31,"name":"Chimpanzee"},{"id":32,"name":"Chinchilla"},{"id":33,"name":"Chough"},{"id":34,"name":"Clam"},{"id":35,"name":"Cobra"},{"id":36,"name":"Cockroach"},{"id":37,"name":"Cod"},{"id":38,"name":"Cormorant"},{"id":39,"name":"Coyote"},{"id":40,"name":"Crab"},{"id":41,"name":"Crane"},{"id":42,"name":"Crocodile"},{"id":43,"name":"Crow"},{"id":44,"name":"Curlew"},{"id":45,"name":"Deer"},{"id":46,"name":"Dinosaur"},{"id":47,"name":"Dog"},{"id":48,"name":"Dogfish"},{"id":49,"name":"Dolphin"},{"id":50,"name":"Dotterel"},{"id":51,"name":"Dove"},{"id":52,"name":"Dragonfly"},{"id":53,"name":"Duck"},{"id":54,"name":"Dugong"},{"id":55,"name":"Dunlin"},{"id":56,"name":"Eagle"},{"id":57,"name":"Echidna"},{"id":58,"name":"Eel"},{"id":59,"name":"Eland"},{"id":60,"name":"Elephant"},{"id":61,"name":"Elk"},{"id":62,"name":"Emu"},{"id":63,"name":"Falcon"},{"id":64,"name":"Ferret"},{"id":65,"name":"Finch"},{"id":66,"name":"Fish"},{"id":67,"name":"Flamingo"},{"id":68,"name":"Fly"},{"id":69,"name":"Fox"},{"id":70,"name":"Frog"},{"id":71,"name":"Gaur"},{"id":72,"name":"Gazelle"},{"id":73,"name":"Gerbil"},{"id":74,"name":"Giraffe"},{"id":75,"name":"Gnat"},{"id":76,"name":"Gnu"},{"id":77,"name":"Goat"},{"id":78,"name":"Goldfinch"},{"id":79,"name":"Goldfish"},{"id":80,"name":"Goose"},{"id":81,"name":"Gorilla"},{"id":82,"name":"Goshawk"},{"id":83,"name":"Grasshopper"},{"id":84,"name":"Grouse"},{"id":85,"name":"Guanaco"},{"id":86,"name":"Gull"},{"id":87,"name":"Hamster"},{"id":88,"name":"Hare"},{"id":89,"name":"Hawk"},{"id":90,"name":"Hedgehog"},{"id":91,"name":"Heron"},{"id":92,"name":"Herring"},{"id":93,"name":"Hippopotamus"},{"id":94,"name":"Hornet"},{"id":95,"name":"Horse"},{"id":96,"name":"Human"},{"id":97,"name":"Hummingbird"},{"id":98,"name":"Hyena"},{"id":99,"name":"Ibex"},{"id":100,"name":"Ibis"},{"id":101,"name":"Jackal"},{"id":102,"name":"Jaguar"},{"id":103,"name":"Jay"},{"id":104,"name":"Jellyfish"},{"id":105,"name":"Kangaroo"},{"id":106,"name":"Kingfisher"},{"id":107,"name":"Koala"},{"id":108,"name":"Kookabura"},{"id":109,"name":"Kouprey"},{"id":110,"name":"Kudu"},{"id":111,"name":"Lapwing"},{"id":112,"name":"Lark"},{"id":113,"name":"Lemur"},{"id":114,"name":"Leopard"},{"id":115,"name":"Lion"},{"id":116,"name":"Llama"},{"id":117,"name":"Lobster"},{"id":118,"name":"Locust"},{"id":119,"name":"Loris"},{"id":120,"name":"Louse"},{"id":121,"name":"Lyrebird"},{"id":122,"name":"Magpie"},{"id":123,"name":"Mallard"},{"id":124,"name":"Manatee"},{"id":125,"name":"Mandrill"},{"id":126,"name":"Mantis"},{"id":127,"name":"Marten"},{"id":128,"name":"Meerkat"},{"id":129,"name":"Mink"},{"id":130,"name":"Mole"},{"id":131,"name":"Mongoose"},{"id":132,"name":"Monkey"},{"id":133,"name":"Moose"},{"id":134,"name":"Mosquito"},{"id":135,"name":"Mouse"},{"id":136,"name":"Mule"},{"id":137,"name":"Narwhal"},{"id":138,"name":"Newt"},{"id":139,"name":"Nightingale"},{"id":140,"name":"Octopus"},{"id":141,"name":"Okapi"},{"id":142,"name":"Opossum"},{"id":143,"name":"Oryx"},{"id":144,"name":"Ostrich"},{"id":145,"name":"Otter"},{"id":146,"name":"Owl"},{"id":147,"name":"Oyster"},{"id":148,"name":"Panther"},{"id":149,"name":"Parrot"},{"id":150,"name":"Partridge"},{"id":151,"name":"Peafowl"},{"id":152,"name":"Pelican"},{"id":153,"name":"Penguin"},{"id":154,"name":"Pheasant"},{"id":155,"name":"Pig"},{"id":156,"name":"Pigeon"},{"id":157,"name":"Pony"},{"id":158,"name":"Porcupine"},{"id":159,"name":"Porpoise"},{"id":160,"name":"Quail"},{"id":161,"name":"Quelea"},{"id":162,"name":"Quetzal"},{"id":163,"name":"Rabbit"},{"id":164,"name":"Raccoon"},{"id":165,"name":"Rail"},{"id":166,"name":"Ram"},{"id":167,"name":"Rat"},{"id":168,"name":"Raven"},{"id":169,"name":"Red deer"},{"id":170,"name":"Red panda"},{"id":171,"name":"Reindeer"},{"id":172,"name":"Rhinoceros"},{"id":173,"name":"Rook"},{"id":174,"name":"Salamander"},{"id":175,"name":"Salmon"},{"id":176,"name":"Sand Dollar"},{"id":177,"name":"Sandpiper"},{"id":178,"name":"Sardine"},{"id":179,"name":"Scorpion"},{"id":180,"name":"Seahorse"},{"id":181,"name":"Seal"},{"id":182,"name":"Shark"},{"id":183,"name":"Sheep"},{"id":184,"name":"Shrew"},{"id":185,"name":"Skunk"},{"id":186,"name":"Snail"},{"id":187,"name":"Snake"},{"id":188,"name":"Sparrow"},{"id":189,"name":"Spider"},{"id":190,"name":"Spoonbill"},{"id":191,"name":"Squid"},{"id":192,"name":"Squirrel"},{"id":193,"name":"Starling"},{"id":194,"name":"Stingray"},{"id":195,"name":"Stinkbug"},{"id":196,"name":"Stork"},{"id":197,"name":"Swallow"},{"id":198,"name":"Swan"},{"id":199,"name":"Tapir"},{"id":200,"name":"Tarsier"},{"id":201,"name":"Termite"},{"id":202,"name":"Tiger"},{"id":203,"name":"Toad"},{"id":204,"name":"Trout"},{"id":205,"name":"Turkey"},{"id":206,"name":"Turtle"},{"id":207,"name":"Viper"},{"id":208,"name":"Vulture"},{"id":209,"name":"Wallaby"},{"id":210,"name":"Walrus"},{"id":211,"name":"Wasp"},{"id":212,"name":"Weasel"},{"id":213,"name":"Whale"},{"id":214,"name":"Wildcat"},{"id":215,"name":"Wolf"},{"id":216,"name":"Wolverine"},{"id":217,"name":"Wombat"},{"id":218,"name":"Woodcock"},{"id":219,"name":"Woodpecker"},{"id":220,"name":"Worm"},{"id":221,"name":"Wren"},{"id":222,"name":"Yak"},{"id":223,"name":"Zebra"}]}

```

Navigate to the directory where `db.json` is placed and run the below command:

```bash
json-server -p 4000 db.json
```

Open the URL [http://localhost:4000/animals](http://localhost:4000/animals) in the browser and you should be able to see the response.

# Project Setup

Create a react app by running the following command:

```bash
npx create-react-app react-debounce-throttle
```

Now update the `index.css` with the following styles to align the search box:

```css
body {
  margin: 20px auto;
  max-width: 400px;
}
.search-input {
  width: 100%;
}
```

Update the `App.js` with the following code:

```jsx
import { useRef, useState } from "react"

function App() {
  const inputRef = useRef()
  const [animals, setAnimals] = useState([])

  const handleDebounceSearch = () => {
    // If there is no search term, do not make API call
    if (!inputRef.current.value.trim()) {
      setAnimals([])
      return
    }
    fetch(`http://localhost:4000/animals?q=${inputRef.current.value}`)
      .then(async response => {
        if (!response.ok) {
          console.log("Something went wrong!")
        } else {
          const data = await response.json()
          setAnimals(data)
        }
      })
      .catch(err => {
        console.error(err)
      })
  }

  return (
    <div>
      <input
        type="text"
        ref={inputRef}
        onChange={handleDebounceSearch}
        className="search-input"
      />
      {/* Display the result if search term is not empty and results are present */}
      {inputRef.current?.value && animals.length > 0 && (
        <ul>
          {animals.map(animal => {
            return <li key={animal.id}>{animal.name}</li>
          })}
        </ul>
      )}
    </div>
  )
}

export default App
```

In the above code, we are having a search box, and whenever the user types in it, we are calling our API endpoint with the search term.
When we are getting a successful response, we are setting the results to `animals` state and displaying them in a list.

Now if I run the application and search for `cat`, I'll see a network call made for each keypress:

![Without Debounce](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cv3agmfsalfbfnpbweq4.png)

# Adding Debounce

Now let's add debounce to our search functionality:

```jsx
import { useRef, useState } from "react"

function App() {
  const inputRef = useRef()
  const [animals, setAnimals] = useState([])
  const timeout = useRef()

  const handleDebounceSearch = () => {
    //Clear the previous timeout.
    clearTimeout(timeout.current)

    // If there is no search term, do not make API call
    if (!inputRef.current.value.trim()) {
      setAnimals([])
      return
    }
    timeout.current = setTimeout(() => {
      fetch(`http://localhost:4000/animals?q=${inputRef.current.value}`)
        .then(async response => {
          if (!response.ok) {
            console.log("Something went wrong!")
          } else {
            const data = await response.json()
            setAnimals(data)
          }
        })
        .catch(err => {
          console.error(err)
        })
    }, 600)
  }

  return (
    <div>
      <input
        type="text"
        ref={inputRef}
        onChange={handleDebounceSearch}
        className="search-input"
      />
      {/* Display the result if search term is not empty and results are present */}
      {inputRef.current?.value && animals.length > 0 && (
        <ul>
          {animals.map(animal => {
            return <li key={animal.id}>{animal.name}</li>
          })}
        </ul>
      )}
    </div>
  )
}

export default App
```

Here we have wrapped the API call inside a timeout callback, which will be called after 600ms. So if the user is typing, we clear the timer and extend the it by another 600ms, so that the API call happens only after 600ms after the user has stopped typing.

Now if you search again, you will see that only one call is made:

![With Debounce](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ur3pnglhylyjp9hxxcy2.png)

# Adding throttle

As we discussed earlier, the drawback with debounce is that,
if the user is typing a long-phrase then he might not see the autosuggestion.
The solution for it is throttling. In throttling, we call the API every fixed interval.

```jsx
import { useRef, useState } from "react"

function App() {
  const inputRef = useRef()
  const [animals, setAnimals] = useState([])

  const throttling = useRef(false)

  const handleThrottleSearch = () => {
    if (throttling.current) {
      return
    }
    // If there is no search term, do not make API call
    if (!inputRef.current.value.trim()) {
      setAnimals([])
      return
    }
    throttling.current = true
    setTimeout(() => {
      throttling.current = false
      fetch(`http://localhost:4000/animals?q=${inputRef.current.value}`)
        .then(async response => {
          if (!response.ok) {
            console.log("Something went wrong!")
          } else {
            const data = await response.json()
            setAnimals(data)
          }
        })
        .catch(err => {
          console.error(err)
        })
    }, 600)
  }

  return (
    <div>
      <input
        type="text"
        ref={inputRef}
        onChange={handleThrottleSearch}
        className="search-input"
      />
      {/* Display the result if search term is not empty and results are present */}
      {inputRef.current?.value && animals.length > 0 && (
        <ul>
          {animals.map(animal => {
            return <li key={animal.id}>{animal.name}</li>
          })}
        </ul>
      )}
    </div>
  )
}

export default App
```

Similar to debounce, here also we have a timeout function, which will be called only when the `throttling` is set to `false`.
After every timeout, we are setting `throttling` to `true` and calling the API.

Now, if you check the application you will see that the API calls are made at regular intervals (600ms):

![With Throttle](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c252ma2eydicptb52mno.png)

# Source code and Demo

You can view the complete source code [ here](https://github.com/collegewap/react-debounce-throttle).