Debouncing in React using custom Hook

Debouncing in React using custom Hook

ยท

3 min read

You might have came across to the situation where you have to make certain api calls depending upon user interactivity. Such as user input in search bar or maybe when user is scrolling through content-loading webpages. Well, that's where debouncing comes into picture ๐Ÿ˜€ . We cannot call api on every input of the user, I mean obviously we can.. but it won't be efficient ๐Ÿง. Debouncing is a programming practice used to ensure that time-consuming tasks do not fire so often, in order to enhance the performance of the web page. In other words, it limits the rate at which a function gets invoked.

There are may libraries nowadays which provide debouncing functionality like Lodash. We could have use the Lodash but it is too expensive. So let's try to make our own custom hook for debouncing โœ๐Ÿป .

We're going to design this hook to debounce the user input and make an api call. The goal is to only have the API call when user stops typing.

import React, { useState, useEffect } from 'react';
import useDebounce from './use-debounce';

// Usage
const  App=()=> {
  // state for the searched term
  const [searchItem, setSearchItem] = useState('');
  // state for storing the results
  const [results, setResults] = useState([]);
  // state for checking if api call is completed
  const [isSearching, setIsSearching] = useState(false);

  // Now we call our hook, passing in the current searchItem 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 searchItem.

  const debouncedSearchTerm = useDebounce(searchItem, 500);

  // Here's where the API call happens
  // We use useEffect since this is an asynchronous action
  useEffect(
    () => {
      // Checks if user has the value
      if (debouncedSearchTerm) {
        setIsSearching(true);
        // Call the api
        callSearchApi(debouncedSearchTerm).then(results => {
          setIsSearching(false);
          setResults(results);
        });
      } else {
        setResults([]);
      }
    },
    // It will only change if the original value (searchItem) hasn't changed for more than 500ms.
    [debouncedSearchTerm]
  );

  return (
    <div>
      <input
        placeholder="Search"
        onChange={e => setSearchItem(e.target.value)}
      />
      {isSearching && <div>Loading...</div>}
      {results.map(result => (
        <div key={result.id}>
          <h3>{result.name}</h3>
        </div>
      ))}
    </div>
  );
}

// API search function
const  callSearchApi=(search)=> {
  return fetch(
    `apiendpoint/search?name=search`,
    {
      method: 'GET'
    }
  )
    .then(res => res.json())
    .then(res => res.data.results)
    .catch(err => {
      console.error(err);
      return [];
    });
}

Let's build the custom hook to handle debouncing.

import React, { useState, useEffect } from 'react';

export default function useDebounce(value, delay) {
  const [debouncedValue, setDebouncedValue] = useState(value);

  useEffect(
    () => {
      // Seting debouncedValue to value 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 . 
      // This is how we prevent debouncedValue from changing if value is changed within the delay period. Timeout gets cleared and restarted.
      // Now,if the user is typing in search box, we don't want the debouncedValue to update until they've stopped typing for more than 500ms.
      return () => {
        clearTimeout(handler);
      };
    },
    [value] 
  );

  return debouncedValue;
}

And that's it, you are good to go ๐Ÿ‘๐Ÿป . Now you can use debouncing effect anywhere in your code.