OhSnap! Optimize Search Performance With Debouncing in React
Tags: react, todayilearned, javascript, webdev
Published: 2021-02-01 08:10:41

*The "OhSnap!" series explores bite-sized tips that you can apply today.*

---

#TL;DR
Querying an API with a search field in a efficient way is not as hard as you might think. Today we will learn how to [quickly denounce API calls](#solution) with the `useRef()` and `useState()` React hooks.

##The Scenario

We have made an incredibly reactive search feature for our React movie app. ([See the full app](#))

As we type, React looks at the input value and immediately queries the OMDB API, returning a list of movies. The effect is a "live search" that does not require a separate "submit" button to execute. Pretty amazing! 

![Live Searching](https://dev-to-uploads.s3.amazonaws.com/i/ddjtdcsxybc67a4ss5mc.gif)



---

##The Problem
What we are not seeing, is that React is making a network call to the API ***with every keystroke***. 

This means finding "Harry Potter and the Deathly Hallows Part 1" will require ***43 separate network requests***.  

In addition to the performance issues, having new information appear with every keystroke can be too reactive and perhaps chaotic. 

![Multi Network Requests](https://dev-to-uploads.s3.amazonaws.com/i/dm92pn5mp2ipogym9g44.gif)

---

##Why Is This Happening?
In our case with React, we are capturing the input values via an `onChange` function and saving it to state. Then our `useEffect` calls the API each and every time the state is updated.

```jsx
// State that captures the user's input value
const [userSearch, setUserSearch] = useState(null);

// Makes an API request whenever the search state is updated
useEffect(() => {
  const query = `https://www.omdbapi.com/?s=${userSearch}&apikey=yourKey`;

  axios.get(query)
    .then(res => {
      // Execute next steps
    })

}, [userSearch]);

// JSX for the search field
<input
  type="text"
  value={userSearch}
  onChange={event => setUserSearch(event.target.value)}
/>
```

While this does allow for the "live" searching effect, it is costing us a lot of performance. There needs to be a better solution because this feature is too cool to abandon for an `onClick` workaround. 


---

<a name="solution"></a>
##The Solution
This is where "Debouncing" comes in. Debouncing delays a function from operating until a specific condition has happened. (Usually, an amount of time elapsing.)

This allows for a resources heavy action to be delayed (API call) until all the conditions (user typing) are completed.

There are lots of ways to do this, including creating a custom hook. I'll include some links at the bottom to explore these further. However, the version I want to show today I really like for just how simple it is. 

#### Debounced New Code
```jsx
// State that captures the user's input value
const [userSearch, setUserSearch] = useState(null);

// Holds a reference the current input field value
const inputRef = useRef(null);

// Makes an API request whenever the search state is updated
useEffect(() => {

  // Debounce Effect
  // Code within the setTimeout runs every 0.5 seconds
  const timer = setTimeout(() => {

    // 1. The conditional checks the input's current value
    // 2. It compares it against the userSearch state
    // 3. If they match then the user has stopped typing and the API is ready to be called 

    if(inputRef.current.value === userSearch) {
      const query = `https://www.omdbapi.com/?s=${userSearch}&apikey=yourKey`;

      axios.get(query)
        .then(res => {
          // Execute next steps
        })
    }
  }, 500)

  // Every time the useEffect runs it creates a new setTimeout function
  // Returning this cleanup function will run before each new render and remove the old timer
  return () => {
    clearTimeout(timer)
  }  

}, [userSearch, inputRef]);

// JSX for the search field
<input
  type="text"
  value={userSearch}
  onChange={event => setUserSearch(event.target.value)}
  ref={inputRef} // <--- ref grabs the input element 
/>
```


#### Result

![Single Network Request](https://dev-to-uploads.s3.amazonaws.com/i/2hs2axee9rpf7j76udbm.gif) 

And just like that your API request waits until your done typing before it fires. It massively saves on performance and visually less chaotic. Happy Coding! 


---

### Resources 

* [Debounce in JavaScript — Improve Your Application’s Performance](https://levelup.gitconnected.com/debounce-in-javascript-improve-your-applications-performance-5b01855e086)

* [Debouncing with React Hooks](https://dev.to/gabe_ragland/debouncing-with-react-hooks-jci)


---