Check out bidbear.io Amazon Advertising for Humans. Now publicly available 🚀

React useEffect Prevent Multiple Renders

Contents

Intro

One of the most common issues with a useEffect hook is that it will repeatedly re-render and fire a function more than once, which is particularly undesirable during async operations. Here is the tried and true method for preventing multiple renders with useEffect.

Boilerplate

useEffect(() => {
  const someAPICall = async () => {
    console.log("🚀 calling api");
    // your async operations go here
  };

  // request tracker
  let loading = false;

  try {
    if (!loading) {
      someAPICall();
    }
  } catch (error) {
    console.error(error);
  } finally {
    // cleanup => request tracker
    return () => (loading = true);
  }
}, []);

Explanation

The key to this method is the request tracker variable loading. This is a variable that is set to false by default, and then set to true in the cleanup function. The cleanup function is called when the component re-renders.

This means that the next time useEffect is called, the request tracker will be true, and the useEffect will not fire. This variable is scoped to this useEffect, so you can use this same pattern in multiple useEffects without worrying about them interfering with each other.

Chaining

This method is particularly useful for chaining useEffects that need to happen in a particular order. For example, if you need to fetch data from an API, and then use that data to fetch more data from another API, you can use this method to ensure that the second useEffect doesn’t fire until the first one is complete by having the first one set a piece of state that the second is watching, like so:

const [idsFetched, setIdsFetched] = useState(false);
const [accountsFetched, setAccountsFetched] = useState(false);

// 1: fetch id array
useEffect(() => {
  const fetchIds = async () => {
    // fetch and store ids...

    // if successful
    setIdsFetched(true);
  };

  // request tracker
  let loading = false;

  try {
    if (!loading && !idsFetched) {
      fetchIds();
    }
  } catch (error) {
    console.error(error);
  } finally {
    // cleanup => request tracker
    return () => (loading = true);
  }
}, [idsFetched, setIdsFetched]);

// 2: fetch accounts using id's
useEffect(() => {
  const fetchAccounts = async () => {
    // fetch and store accounts...

    // if successful
    setAccountsFetched(true);
  };

  // request tracker
  let loading = false;

  try {
    // will only fetch accounts if we have id's and no accounts
    if (!loading && idsFetched && !accountsFetched) {
      fetchAccounts();
    }
  } catch (error) {
    console.error(error);
  } finally {
    // cleanup => request tracker
    return () => (loading = true);
  }
}, [idsFetched, accountsFetched, setAccountsFetched]);

In this example the fetchIds function will fire one time, and then the fetchAccounts function will fire one time.

Another thing you have to be sure of, is that you have a piece of state like accountsFetched that you use a condition on all your async operations to ensure that they only fire once. If you don’t do this, while the async operation won’t make multiple requests simultaneously, it will run repeatedly back to back.

Amazon Ad Analytics For Humans

Advertising reports automatically saved and displayed beautifully for powerful insights.

bidbear.io
portfolios page sunburst chart