Debouncing in React

Debouncing in React

Think of it this way: you need to send Tom the robot down to the grocery store. You need eggs, flour, honey, milk, and bread. Sending Tom to make these runs one after the other has run down its battery and caused a few wears and tears.

To save Tom from future wear and tears we can say "Hey Tom, here are the items to buy, wait while I input them all, then go to the store and execute my orders all at once".

This way, your robot, Tom, doesn't have to break down due to multiple grocery store runs. This is how the concept of debouncing works.

Debouncing is a concept used to control how often a particular function is executed, especially in response to rapid or frequent events such as scroll events, keystrokes, mouse movements, etc. It ensures that a function is only called after a specified period of inactivity, preventing it from being invoked multiple times within a short timeframe.

Importance of Debouncing in React Applications.

In React applications, where user events and interaction drive much of the functionality, debouncing becomes crucial for several reasons:

  • Prevention of Unwanted Side Effects: In cases where event handlers trigger actions such as auto-saving data, filtering search results, or updating UI elements, rapid event firing can lead to unintended side effects or undesirable behavior. Debouncing ensures that these actions are only performed when the user has finished interacting with the interface, reducing the likelihood of errors.

  • Performance Optimization: Certain functions might be triggered unnecessarily and frequently without debouncing, leading to unnecessary computations, API requests, or re-renders. Debouncing helps to optimize performance by reducing the number of function calls and resource usage.

  • Enhancing User Experience: By implementing debouncing, Your applications can provide a smoother and more responsive user experience. It prevents UI elements from flickering or updating excessively during rapid user interactions, making the interface feel more predictable and stable.

Now, let us go save that Robot!

Setting up a React Project

In your terminal, use the command below to create a new React application

npx create-react-app react-debouncing-project

You can just run the next command to navigate to the newly created app directory.

cd react-debouncing-project

Finally, you can just run the command below to start the server.

npm start
//or
yarn start

Create a folder named `components`. Inside this folder, create a file and name it `DebouncedSearch.jsx`. We will write our code in this component and import it into the `App.js` component.

The Axios library will have to be installed as we will need it to fetch data from our API.

npm i axios

In our `DebouncedSearch` component, we will declare the following states.

  //this state variable holds the current value of the search term entered
  // by the user in the input field
  const [searchTerm, setSearchTerm] = useState('');
  // this stores the results fetched from the API based on the user's search term
  const [searchResults, setSearchResults] = useState([]);
  // this is a boolean flag that indicates whether the application is currently
  // fetching search results from the API.
  const [isSearching, setIsSearching] = useState(false);

We will then define our debounce function:

 const debounce = (func, delay) => {
    let timer;
    return function (...args) {
      clearTimeout(timer);
      timer = setTimeout(() => func.apply(this, args), delay);
    };
  };

Next, we declare our function to fetch search results:

const fetchSearchResults = async (term) => {
    try {
      setIsSearching(true);
      const response = await axios.get(
        `https://jsonplaceholder.typicode.com/posts?q=${term}`
      );
      setSearchResults(response.data);
    } catch (error) {
      console.error('Error fetching data:', error);
    } finally {
      setIsSearching(false);
    }
  };

Here, is a debounced version of `fetchSearchResults` , here we apply a debounce of 3 seconds:

//here, we are giving a debounced timing of 3 seconds 
const debouncedSearch = debounce(fetchSearchResults, 3000);

In our next step, we will create an event handler for input change:

const handleInputChange = (event) => {
    const term = event.target.value;
    setSearchTerm(term);
    debouncedSearch(term);
  };

Lastly, we return the following to display our input and data:

return (
    <div>
      <h2>Debounced Search Example</h2>
      <input
        type="text"
        placeholder="Search..."
        value={searchTerm}
        onChange={handleInputChange}
      />
      {isSearching ? (
        <p>Loading...</p>
      ) : (
        <ul>
          {searchResults.map((result) => (
            <li key={result.id}>{result.title}</li>
          ))}
        </ul>
      )}
    </div>
  );

Let us take a look at the full code:

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

const DebouncedSearch = () => {
  const [searchTerm, setSearchTerm] = useState('');
  const [searchResults, setSearchResults] = useState([]);
  const [isSearching, setIsSearching] = useState(false);

  const debounce = (func, delay) => {
    let timer;
    return function (...args) {
      clearTimeout(timer);
      timer = setTimeout(() => func.apply(this, args), delay);
    };
  };

  const fetchSearchResults = async (term) => {
    try {
      setIsSearching(true);
      const response = await axios.get(
        `https://jsonplaceholder.typicode.com/posts?q=${term}`
      );
      setSearchResults(response.data);
    } catch (error) {
      console.error('Error fetching data:', error);
    } finally {
      setIsSearching(false);
    }
  };


  const debouncedSearch = debounce(fetchSearchResults, 3000);

  const handleInputChange = (event) => {
    const term = event.target.value;
    setSearchTerm(term);
    debouncedSearch(term);
  };

  return (
    <div>
      <h2>Debounced Search Example</h2>
      <input
        type="text"
        placeholder="Search..."
        value={searchTerm}
        onChange={handleInputChange}
      />
      {isSearching ? (
        <p>Loading...</p>
      ) : (
        <ul>
          {searchResults.map((result) => (
            <li key={result.id}>{result.title}</li>
          ))}
        </ul>
      )}
    </div>
  );
};

export default DebouncedSearch;

In the image below, even after input, results will not show until after 3000 milliseconds(3 seconds). This is how debouncing works.

Debouncing with Lodash

If you do not want to debounce the conventional way, Lodash is a popular utility library, designed to provide a wide range of helper functions to simplify common programming tasks and improve code readability and efficiency. Lodash offers a comprehensive set of functions, consistent and chainable API, performance optimization, modularity, compatibility, and wide adoption.

Installation

To install the lodash library, the command below in your terminal

npm install lodash
// or
yarn add lodash

Using the previous example, we will simply modify our `DebouncedSearch` component to use lodash's `debounce` function.

import React, { useState } from 'react';
import axios from 'axios';
import _debounce from 'lodash/debounce'; // Import debounce function from lodash

const DebouncedSearch = () => {
  const [searchTerm, setSearchTerm] = useState('');
  const [searchResults, setSearchResults] = useState([]);
  const [isSearching, setIsSearching] = useState(false);

  // Function to fetch search results
  const fetchSearchResults = async (term) => {
    try {
      setIsSearching(true);
      const response = await axios.get(
        `https://jsonplaceholder.typicode.com/posts?q=${term}`
      );
      setSearchResults(response.data);
    } catch (error) {
      console.error('Error fetching data:', error);
    } finally {
      setIsSearching(false);
    }
  };

  // Debounced version of fetchSearchResults using lodash
  const debouncedSearch = _debounce(fetchSearchResults, 3000);

  // Event handler for input change with debouncing
  const handleInputChange = (event) => {
    const term = event.target.value;
    setSearchTerm(term);
    debouncedSearch(term); // Call the debounced function
  };

  return (
    <div>
      <h2>Debounced Search Example</h2>
      <input
        type="text"
        placeholder="Search..."
        value={searchTerm}
        onChange={handleInputChange}
      />
      {isSearching ? (
        <p>Loading...</p>
      ) : (
        <ul>
          {searchResults.map((result) => (
            <p key={result.id}>{result.title}</p>
          ))}
        </ul>
      )}
    </div>
  );
};

export default DebouncedSearch;

Using this method, we get the same result as the previous example.

Hey, remember Tom the Robot? We saved him!

Need further insight on the concept of debouncing? You can check this out https://www.youtube.com/watch?v=PySFIsgXNZ0

Best Tips and Practices when Debouncing

When it comes to debouncing, there are several best practices and tips to ensure that you are effectively and efficiently using it in your application. Let us consider a few.

  1. Test Thoroughly: Thoroughly test your application with various user interactions and edge cases to ensure that debouncing behaves as expected. Test for performance, responsiveness, and correctness under different conditions to identify and address any issues early in the development process.

  2. Choose The Right Delay: You need to determine an appropriate delay for debouncing based on the specific requirements of your application. The delay should be long enough to allow users to complete their actions but short enough to maintain proper responsiveness.

  3. Understand The Use Case: Before you implement debouncing, carefully consider the use case and determine if it is necessary. Debouncing is typically used for scenarios where frequent events can lead to performance issues or undesired behavior, such as user input in search fields or scroll events.

Following these tips and practices can cause you to effectively leverage debouncing to improve the performance, responsiveness, and user experience of your applications while avoiding common issues and pitfalls.

Final Thoughts on Debouncing

In conclusion, debouncing is a valuable technique in web development, particularly in scenarios where rapid or frequent events can lead to performance issues, degraded user experience, or undesired behavior. By delaying the execution of event handlers until a determined period of inactivity, debouncing helps optimize resource usage, improve responsiveness, and enhance the overall usability of web applications.

By employing debouncing effectively and adhering to best practices, you can enhance the overall quality, performance, and usability of your web applications, improving user experience

I do hope this article has been of much use to you. For collaborations and hiring, you can reach out via linkedin.com/in/durosinmi-victory/