DEV Community

Pandeyashish17
Pandeyashish17

Posted on

The Ultimate Guide to Not Screwing Up Your useEffect Hooks. How to Avoid Blunders and Become a React Pro

Are you ready to become a hook-wielding ninja and master the art of cleaning up your useEffect hooks, including aborting API calls? With this ultimate guide, you'll learn all the tricks and techniques for keeping your function components running smoothly, avoiding memory leaks, and canceling ongoing network requests.

First, let's start with a quick review. What is the useEffect hook? It's a hook in React that allows you to perform side effects in your function components. It's a great way to add features like data fetching, subscriptions, or manually changing the DOM.

So, how do you use the useEffect hook with a cleanup function to abort an API call like a boss? It's easy! Just follow these simple steps:

1) Import the hook from the 'react' package.

import { useEffect } from 'react';

Enter fullscreen mode Exit fullscreen mode

2) Call the hook inside your function component, and provide a function that contains the side effect code as the first argument.

useEffect(() => {
  // side effect code goes here
});

Enter fullscreen mode Exit fullscreen mode

3) Optionally, provide a second argument as an array of dependencies. This tells the hook to only re-run the side effect when the dependencies change.

useEffect(() => {
  // side effect code goes here
}, [dependency1, dependency2]);

Enter fullscreen mode Exit fullscreen mode

4) Make sure to clean up any side effects that have the potential to create memory leaks, by returning a cleanup function from the side effect function. This function will be called when the component unmounts or the hook re-runs.

useEffect(() => {
  // side effect code goes here

  return () => {
    // cleanup code goes here
  };
});

Enter fullscreen mode Exit fullscreen mode

5) To cancel an ongoing network request, use a boolean flag to track whether the component has been unmounted or the hook has re-run. If the flag is true, don't update the state or set the loading flag to false, effectively aborting the API call and the side effect.

Here's an example of how you can use the useEffect hook with a cleanup function to fetch data from an API and display it in your component, while also handling errors and the ability to cancel the request:

import { useEffect, useState } from 'react';

function Example() {
  const [data, setData] = useState(null);
  const [isLoading, setIsLoading] = useState(true);
  const [isError, setIsError] = useState(false);

  useEffect(() => {
    let didCancel = false;

    async function fetchData() {
      try {
        const response = await fetch('https://my-api.com/data');
        if (!didCancel) {
          const data = await response.json();
          setData(data);
          setIsLoading(false);
        }
      } catch (error) {
        if (!didCancel) {
          setIsError(true);
          setIsLoading(false);
        }
      }
    }
    fetchData();

    return () => {
      didCancel = true;
    };
  }, []);

  return (
    <div>
      {isError && <div>An error occurred while fetching data!</div>}
      {isLoading ? (
        <div>Loading...</div>
      ) : (
        data && data.map(item => <div key={item.id}>{item.name}</div>)
      )}
    </div>
  );
}

Enter fullscreen mode Exit fullscreen mode

In this example, we use a boolean flag (didCancel) to track whether the component has been unmounted or the hook has re-run. If the flag is true, we don't update the state or set the loading flag to false, effectively aborting the API call and the side effect.

With the useEffect hook and a little bit of cleanup magic, you'll be able to add all sorts of amazing features to your function components without worrying about memory leaks or ongoing network requests. So why wait? Start cleaning up your useEffect hooks like a boss and take your React skills to the next level!

Top comments (2)

Collapse
 
brense profile image
Rense Bakker

👍 keep in mind though that your boolean flag will reset if the side effect is executed more than once due, to a change in the dependencies of the useEffect hook. Its probably better to use a ref instead to store the flag:

import { useEffect, useState, useRef } from 'react'

function Example() {
  const [data, setData] = useState(null)
  const [isLoading, setIsLoading] = useState(true)
  const [isError, setIsError] = useState(false)
  const cancelledRef = useRef(false)

  useEffect(() => {
    (async () => {
      try {
        const response = await fetch(`https://my-api.com/products/${productId}`)
        if(!cancelledRef.current){
          setData(data)
          setIsLoading(false)
        }
      } catch(error){
        if(!cancelledRef.current){
          setIsError(true)
          setIsLoading(false)
        }
      }
    })();

    return () => {
      cancelledRef.current = true
    }
  }, [productId])

  return (
    <div>
      {isError && <div>An error occurred while fetching data!</div>}
      {isLoading ? (
        <div>Loading...</div>
      ) : (
        data && data.map(item => <div key={item.id}>{item.name}</div>)
      )}
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
ashishpandey profile image
Pandeyashish17

Thank you for your comment! You're right that a boolean flag will reset if the side effect is executed more than once due to a change in the dependencies of the useEffect hook. In this example, I used a boolean flag to keep things simple and illustrate the basic concept of canceling an ongoing network request in the useEffect hook.

However, you're also correct that it is probably better to use a ref instead to store the flag, as it is a more robust solution.