Optimizing UseEffect Hook for API Calls: Reduce Server Load and Rerenders with AbortController

Optimizing UseEffect Hook for API Calls: Reduce Server Load and Rerenders with AbortController

Table of Contents

  • Introduction

  • Problem with Example Code

  • Solution with Example Code

  • Benefits

  • Conclusion

Introduction

The useEffect hook is one of the most powerful hooks in React. It allows us to perform side effects in functional components, such as data fetching, DOM manipulation, and subscriptions. However, improper use of useEffect can lead to performance issues, such as unnecessary re-renders and redundant API calls.

In this article, we will discuss how to optimize the useEffect hook while making API calls in it. We will also provide a full code example to demonstrate the solution.

Problem with Example Code

Consider the following simple React component:

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

const ExampleComponent = () => {
  const [id, setId] = useState(0);
  const [text, setText] = useState("");

  const handleClick = () => {
    setId(Math.floor(Math.random() * 100));
  };

  useEffect(() => {
    async function fetchData() {
      const response = await fetch(`/api/posts/${id}`);
      const data = await response.json();
      setText(data.text);
    }

    fetchData();
  }, [id]);

  return (
    <div>
      <button onClick={handleClick}>Show New Post</button>
      <p>{text}</p>
    </div>
  );
};

This component has two state variables: id and text. The handleClick function updates the id state to a random value between 0 and 100. The useEffect hook is used to fetch a new post from the API whenever the id state changes.

The problem with this code is that it can trigger unnecessary API calls. For example, if the user clicks the "Show New Post" button five times in quick succession, the useEffect hook will be triggered five times, resulting in five API calls. This can be inefficient, especially if the API calls are expensive.and also a bad User experience, as the text visibly updates 5 times before it reaches its final State.

Solution with Example Code:

To optimize the useEffect hook for API calls, we can use a technique called request cancellation. Request cancellation allows us to abort a fetch request before it completes.

To implement request cancellation, we can use the AbortController interface. The AbortController interface allows us to create a controller object that can be used to abort one or more fetch requests.

Here is an example of how to use the AbortController interface to optimize the useEffect hook for API calls:

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

const ExampleComponent = () => {
  const [id, setId] = useState(0);
  const [text, setText] = useState("");
  const controller = new AbortController();


  const handleClick = () => {
    setId(Math.floor(Math.random() * 100));
  };

  useEffect(() => {
    async function fetchData() {
      const response = await fetch(`/api/posts/${id}`, {
        signal: controller.signal,
      });
      const data = await response.json();
      setText(data.text);
    }

    fetchData();

    return () => {
      controller.abort();
    };
  }, [id]);

  return (
    <div>
      <button onClick={handleClick}>Show New Post</button>
      <p>{text}</p>
    </div>
  );
};

The key difference between this code and the previous example is that we are passing the controller.signal object to the fetch call. This tells fetch to abort the request if the controller is aborted.

Also, we have added a cleanup function to the useEffect hook. this function runs every time the component is unmounted, but in this case we aren't unmounting the component on state change, however this cleanup function also runs before you go for another round of that useEffect. So, every time the dependency changes and a new useEffect hook is run, this cleanup function is first run for the previous useEffect hook.

In this cleanup function, we can simply return controller.abort(). The abort() method allows us to cancel the fetch call when needed. So, if the user clicks the button multiple times in quick succession, the abort() method will cancel the fetch calls of all the previous useEffect hooks, one by one, except for the useEffect hook triggered by the last button click. This ensures that all pending fetch requests are cancelled.

Benefits:

This example code has several benefits:

  • It prevents unnecessary API calls. If the user clicks the "Show New Post" button multiple times in quick succession, only the last API call will be made.

  • It improves performance by reducing the number of re-renders.

  • It reduces the load on the server by preventing redundant API calls.

Conclusion:

By following the best practices outlined in this blog post, you can optimize the useEffect hook for API calls to improve the performance and scalability of your React applications.

Let's connect: