Code with Kristian • I make videos and write about software development and programming tools
Debouncing with React Hooks
Learn how to build a useDebounce React Hook!
Today I'm going to show you how to build a useDebounce React Hook that makes it super easy to debounce API calls to ensure that they don't execute too frequently. I've also put together a demo that uses our hook. It searches the Marvel Comic API and uses useDebounce to prevent API calls from being fired on every keystroke.

Pretty nifty huh? Okay, now on to the code!
First let's figure out how we want our hook to be used and we can let that guide or actual implementation of the hook logic. Rather than debounce the calling of our API request we're going to design this hook to debounce any value within our component's render function. We're then going to combine this with useEffect
to fire off a new API request whenever that input value changes. This code example assumes some familiarity with the useState
and useEffect
hooks, which you can learn about in the React Hook docs.
import React, { useState, useEffect } from 'react';
// Usage
function App() {
// State and setter for search term
const [searchTerm, setSearchTerm] = useState('');
// State and setter for search results
const [results, setResults] = useState([]);
// State for search status (whether there is a pending API request)
const [isSearching, setIsSearching] = useState(false);
// Now we call our hook, passing in the current searchTerm value.
// The hook will only return the latest value (what we passed in)
// if it's been more than 500ms since it was last called.
// Otherwise, it will return the previous value of searchTerm.
// The goal is to only have the API call fire when user stops typing
// so that we aren't hitting our API rapidly.
const debouncedSearchTerm = useDebounce(searchTerm, 500);
// Here's where the API call happens
// We use useEffect since this is an asynchronous action
useEffect(
() => {
// Make sure we have a value (user has entered something in input)
if (debouncedSearchTerm) {
// Set isSearching state
setIsSearching(true);
// Fire off our API call
searchCharacters(debouncedSearchTerm).then(results => {
// Set back to false since request finished
setIsSearching(false);
// Set results state
setResults(results);
});
} else {
setResults([]);
}
},
// This is the useEffect input array
// Our useEffect function will only execute if this value changes ...
// ... and thanks to our hook it will only change if the original ...
// value (searchTerm) hasn't changed for more than 500ms.
[debouncedSearchTerm]
);
// Pretty standard UI with search input and results
return (
<div>
<input
placeholder="Search Marvel Comics"
onChange={e => setSearchTerm(e.target.value)}
/>
{isSearching && <div>Searching ...</div>}
{results.map(result => (
<div key={result.id}>
<h4>{result.title}</h4>
<img
src={`${result.thumbnail.path}/portrait_incredible.${
result.thumbnail.extension
}`}
/>
</div>
))}
</div>
);
}
// API search function
function searchCharacters(search) {
const apiKey = 'f9dfb1e8d466d36c27850bedd2047687';
const queryString `apikey=${apiKey}&titleStartsWith=${search}`;
return fetch(
`https://gateway.marvel.com/v1/public/comics?${queryString}`,
{
method: 'GET'
}
)
.then(r => r.json())
.then(r => r.data.results)
.catch(error => {
console.error(error);
return [];
});
}
Okay, so that looks pretty good! Now let's build the actual hook so that our app works.
import React, { useState, useEffect } from 'react';
// Our hook
function useDebounce(value, delay) {
// State and setters for debounced value
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(
() => {
// Set debouncedValue to value (passed in) after the specified delay
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
// Return a cleanup function that will be called every time
// useEffect is re-called. useEffect will only be re-called
// if value changes (see the inputs array below).
// This is how we prevent debouncedValue from changing if value is
// changed within the delay period.
// To put it in context, if the user is typing within our app's
// search box, we don't want the debouncedValue to update until
// they've stopped typing for more than 500ms.
return () => {
clearTimeout(handler);
};
},
// Only re-call effect if value changes
// You could also add the "delay" var to inputs array if you
// need to be able to change that dynamically.
[value]
);
return debouncedValue;
}
And there you have it! We now have a debounce hook that we can use to debounce any value right in the body of our component. Debounced values can then be included in useEffect
's input array, instead of the non-debounced values, to limit the frequency of that effect being called.
Here's the Marvel Comic Search demo on CodeSandbox.
If you enjoyed this post be sure to check out useHooks, where I share new React Hook examples every week. You can also find me on Twitter.