Skip to content
react

How to debounce and throttle API calls in React

Dec 2, 2022Abhishek EH6 Min Read
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!

1npm install -g json-server

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

1{"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:

1json-server -p 4000 db.json

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:

1npx create-react-app react-debounce-throttle

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

index.css
1body {
2 margin: 20px auto;
3 max-width: 400px;
4}
5.search-input {
6 width: 100%;
7}

Update the App.js with the following code:

App.js
1import { useRef, useState } from "react"
2
3function App() {
4 const inputRef = useRef()
5 const [animals, setAnimals] = useState([])
6
7 const handleDebounceSearch = () => {
8 // If there is no search term, do not make API call
9 if (!inputRef.current.value.trim()) {
10 setAnimals([])
11 return
12 }
13 fetch(`http://localhost:4000/animals?q=${inputRef.current.value}`)
14 .then(async response => {
15 if (!response.ok) {
16 console.log("Something went wrong!")
17 } else {
18 const data = await response.json()
19 setAnimals(data)
20 }
21 })
22 .catch(err => {
23 console.error(err)
24 })
25 }
26
27 return (
28 <div>
29 <input
30 type="text"
31 ref={inputRef}
32 onChange={handleDebounceSearch}
33 className="search-input"
34 />
35 {/* Display the result if search term is not empty and results are present */}
36 {inputRef.current?.value && animals.length > 0 && (
37 <ul>
38 {animals.map(animal => {
39 return <li key={animal.id}>{animal.name}</li>
40 })}
41 </ul>
42 )}
43 </div>
44 )
45}
46
47export 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

Adding Debounce

Now let's add debounce to our search functionality:

App.js
1import { useRef, useState } from "react"
2
3function App() {
4 const inputRef = useRef()
5 const [animals, setAnimals] = useState([])
6 const timeout = useRef()
7
8 const handleDebounceSearch = () => {
9 //Clear the previous timeout.
10 clearTimeout(timeout.current)
11
12 // If there is no search term, do not make API call
13 if (!inputRef.current.value.trim()) {
14 setAnimals([])
15 return
16 }
17 timeout.current = setTimeout(() => {
18 fetch(`http://localhost:4000/animals?q=${inputRef.current.value}`)
19 .then(async response => {
20 if (!response.ok) {
21 console.log("Something went wrong!")
22 } else {
23 const data = await response.json()
24 setAnimals(data)
25 }
26 })
27 .catch(err => {
28 console.error(err)
29 })
30 }, 600)
31 }
32
33 return (
34 <div>
35 <input
36 type="text"
37 ref={inputRef}
38 onChange={handleDebounceSearch}
39 className="search-input"
40 />
41 {/* Display the result if search term is not empty and results are present */}
42 {inputRef.current?.value && animals.length > 0 && (
43 <ul>
44 {animals.map(animal => {
45 return <li key={animal.id}>{animal.name}</li>
46 })}
47 </ul>
48 )}
49 </div>
50 )
51}
52
53export 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

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.

App.js
1import { useRef, useState } from "react"
2
3function App() {
4 const inputRef = useRef()
5 const [animals, setAnimals] = useState([])
6
7 const throttling = useRef(false)
8
9 const handleThrottleSearch = () => {
10 if (throttling.current) {
11 return
12 }
13 // If there is no search term, do not make API call
14 if (!inputRef.current.value.trim()) {
15 setAnimals([])
16 return
17 }
18 throttling.current = true
19 setTimeout(() => {
20 throttling.current = false
21 fetch(`http://localhost:4000/animals?q=${inputRef.current.value}`)
22 .then(async response => {
23 if (!response.ok) {
24 console.log("Something went wrong!")
25 } else {
26 const data = await response.json()
27 setAnimals(data)
28 }
29 })
30 .catch(err => {
31 console.error(err)
32 })
33 }, 600)
34 }
35
36 return (
37 <div>
38 <input
39 type="text"
40 ref={inputRef}
41 onChange={handleThrottleSearch}
42 className="search-input"
43 />
44 {/* Display the result if search term is not empty and results are present */}
45 {inputRef.current?.value && animals.length > 0 && (
46 <ul>
47 {animals.map(animal => {
48 return <li key={animal.id}>{animal.name}</li>
49 })}
50 </ul>
51 )}
52 </div>
53 )
54}
55
56export 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

Source code

You can view the complete source code here.

Do follow me on twitter where I post developer insights more often!

Leave a Comment

© 2023 CodingDeft.Com