DEV Community

Cover image for How to debounce and throttle API calls in React
collegewap
collegewap

Posted on • Originally published at codingdeft.com

How to debounce and throttle API calls in React

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.

Setting up a mock server

Let's set up a 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!

npm install -g json-server
Enter fullscreen mode Exit fullscreen mode

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"}]}

Enter fullscreen mode Exit fullscreen mode

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

json-server -p 4000 db.json
Enter fullscreen mode Exit fullscreen mode

Open the URL 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:

npx create-react-app react-debounce-throttle
Enter fullscreen mode Exit fullscreen mode

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

body {
  margin: 20px auto;
  max-width: 400px;
}
.search-input {
  width: 100%;
}
Enter fullscreen mode Exit fullscreen mode

Update the App.js with the following code:

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
Enter fullscreen mode Exit fullscreen mode

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

Adding Debounce

Now let's add debounce to our search functionality:

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
Enter fullscreen mode Exit fullscreen mode

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

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.

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
Enter fullscreen mode Exit fullscreen mode

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

Source code and Demo

You can view the complete source code here.

Top comments (0)