Debounce your Search | React Input Optimization
Debouncing is a optimization technique to limit the amount of times a task occurs. If you’ve ever implemented a search feature in React that automatically filters a list or sends a fetch request while a user inputs every character, this is a technique that can greatly improve the efficiency of your application.
Setup / Installation
For this example we’ll be importing the debounce function from the lodash utility library instead of creating our own version. To do this we’ll add this line of code to the top of the component that will handle the filtering operation.
import debounce from 'lodash.debounce';
Next we’ll have to install the dependency with either npm or yarn, depending on which one you are using.
npm install lodash.debounce
or
yarn add lodash.debounce
Example Breakdown
In the CodeSandbox example above I have gone ahead and created a simple app that displays a list of fruit and a search bar that will filter the list as you type into it.
The search bar is a Controlled Form. The value it displays is taken from state and the input is linked with state through its onChange
synthetic event.
const [searchTerm, setSearchTerm] = useState("");
const handleChange = (e) => { setSearchTerm(e.target.value);};
<input type="text" value={searchTerm} onChange={handleChange} />
Every time a character is entered or deleted from the input the list of fruits is filtered and the list the end user sees is updated.
let listToDisplay = fruits;
if (searchTerm !== "") listToDisplay = fruits.filter((fruit) => { return fruit.includes(searchTerm); });}
The Problem
While this example works fine, you can imagine as the size of the list grows, the longer each search takes. Another scenario could be that we’re sending api requests for each character being searched. Either way this implementation can start to slow the application and you’ll be left wondering how to fix this. In comes debounce
.
Debounce
_.debounce(func, [wait=0], [options={}])
While debounce
takes 3 arguments, we’ll be using the first two, the function we want to be invoked and the time we’re setting to wait.
We’ll also use the .cancel
function that’s built into debounce
to help with cleanup (more on that later).
For more info on the debounce
function refer to the official documentation.
Implementation
The first step will be to work with useMemo
to memoize a return value from our debounce
function. This returned value will persist between re-renders. This step is essential because if we don’t persist this data between re-renders other implementations of debounce
will occur on every re-render and we would essentially have our original example; We’d be filtering a list per character instead after a set time spent after the last character input.
const debouncedResults = useMemo(() => { return debouce(handleChange, 300);}, []);
Next we’ll work with useEffect
to clean up any side effects from debounce
when our component gets unmounted; There’s no reason for a search to run when we’re not on that page or view anymore. This is where we’ll call the .cancel
function on our memoized return value.
useEffect(() => { return () => { debouncedResults.cancel(); };});
Finally we’ll undo some of the previous work. We will make the form uncontrolled by removing its value attribute and setting the onChange
to invoke debouncedResults
. This will enable the form to be call debounce
every time the input changes.
<input type="text" onChange={debouncedResults} /
And there we go, a completed debouncing input!