DEV Community

loading...
Cover image for OhSnap! Optimize Search Performance With Debouncing in React

OhSnap! Optimize Search Performance With Debouncing in React

Gedalya Krycer
Front-End Developer and Designer with a focus on React.
・3 min read

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 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


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


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.

// 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)}
/>
Enter fullscreen mode Exit fullscreen mode

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.


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

// 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 
/>
Enter fullscreen mode Exit fullscreen mode

Result

Single Network Request

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


Discussion (0)