Writing a Custom React useDebounce Hook with Typescript
Nick Scialli
August 30, 2019
Introduction
In the past year, React released Hooks, which enable us to use state and other features in functional components rather than writing class components. We can also write custom hooks, which lets us abstract component logic into reusable functions. In this post, we’ll write a custom useDebounce
hook using Typescript!
What is Debouncing?
Debouncing is a programming technique that enables us to defer the execution of a potentially-expensive operation rather than executing it constantly. A great example where this is helpful is filtering large lists based on user input. Rather than filtering on every keystroke, we can use debounce functionality to only apply the filter if the user hasn’t typed anything for a few seconds.
In the following screenshot, we show a large list of cities being filtered only after the user pauses typing for one second.
How Should Our Custom Hook Work?
There are a number of ways we could write our hook, but let’s make its usage look relatively similar to that of the setState
hook:
const [value, setValue] = useState<string>('');
The difference is that we’ll want:
- To specify the debounce time
- To retrieve both the current value and the debounced value.
So it should look something like this:
const [debouncedValue, value, setValue] = useDebounce<string>('', 1000);
We could then use the useEffect
hook to take some action based on debouncedValue
changing:
const [debouncedValue, value, setValue] = useDebounce<string>('', 1000);
useEffect(() => {
// Do something with the debounced value
// e.g., setCities(cities.filter(el => el.name.includes(debouncedValue)))
}, [debouncedValue]);
Writing the Hook
useDebounce Types
To start off with, let’s create our useDebounce
function and make sure we’ve defined its argument and return types correctly. We’ll want to make sure our hook takes a generic that provides the type for the initial value, value, and debounced value. Our two arguments, initalValue
and time
, will by the generic type and number
type, respectively. Finally, our function will return a three element array. The first element (our debounced value) will be the generic type, the second element (our up-to-date/current value) will also be the generic type, and the final element will be our setted, a React.Dispatch
type.
function useDebounce<T>(
initialValue: T,
time: number
): [T, T, React.Dispatch<T>] {}
Writing a Version Without Debouncing
Our hook will need to maintain two pieces of state: the up-to-date/current value and the debounced value. We will use the useState
hook to maintain these states. To make a really simple version right now, we’re going to use the useEffect
hook to determine when the up-to-date value changes and just immediately change the debounced value. In the future, we’ll deal with adding a delay to this process.
import { useState, useEffect } from 'react';
function useDebounce<T>(
initialValue: T,
time: number
): [T, T, React.Dispatch<T>] {
const [value, setValue] = useState<T>(initialValue);
const [debouncedValue, setDebouncedValue] = useState<T>(initialValue);
useEffect(() => {
setDebouncedValue(value);
}, [value]);
return [debouncedValue, value, setValue];
}
Adding Debounce Functionality
We can finally add our debouncing functionality. To do this, we can use a setTimeout
within the useEffect
. What’s key here is returning a function from useEffect
that cancels the timeout. This function is essentially the componentWillUnmount
equivalent for our effect!
import { useState, useEffect } from 'react';
function useDebounce<T>(
initialValue: T,
time: number
): [T, T, React.Dispatch<T>] {
const [value, setValue] = useState<T>(initialValue);
const [debouncedValue, setDebouncedValue] = useState<T>(initialValue);
useEffect(() => {
const debounce = setTimeout(() => {
setDebouncedValue(value);
}, time);
return () => {
clearTimeout(debounce);
};
}, [value, time]);
return [debouncedValue, value, setValue];
}
Conclusion
That’s it! We can now use our custom hook to accomplish debouncing.
Nick Scialli is a senior UI engineer at Microsoft.