Table of Contents
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:
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:
1import { useRef, useState } from "react"23function App() {4 const inputRef = useRef()5 const [animals, setAnimals] = useState([])67 const handleDebounceSearch = () => {8 // If there is no search term, do not make API call9 if (!inputRef.current.value.trim()) {10 setAnimals([])11 return12 }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 }2627 return (28 <div>29 <input30 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}4647export 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:
Adding Debounce
Now let's add debounce to our search functionality:
1import { useRef, useState } from "react"23function App() {4 const inputRef = useRef()5 const [animals, setAnimals] = useState([])6 const timeout = useRef()78 const handleDebounceSearch = () => {9 //Clear the previous timeout.10 clearTimeout(timeout.current)1112 // If there is no search term, do not make API call13 if (!inputRef.current.value.trim()) {14 setAnimals([])15 return16 }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 }3233 return (34 <div>35 <input36 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}5253export 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:
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.
1import { useRef, useState } from "react"23function App() {4 const inputRef = useRef()5 const [animals, setAnimals] = useState([])67 const throttling = useRef(false)89 const handleThrottleSearch = () => {10 if (throttling.current) {11 return12 }13 // If there is no search term, do not make API call14 if (!inputRef.current.value.trim()) {15 setAnimals([])16 return17 }18 throttling.current = true19 setTimeout(() => {20 throttling.current = false21 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 }3536 return (37 <div>38 <input39 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}5556export 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):
Source code
You can view the complete source code here.
Do follow me on twitter where I post developer insights more often!