Skip to main content
Check out bidbear.io Automated Amazon Reports 🚀

React useEffect Prevent Multiple Renders

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.

Automated Amazon Reports

Automatically download Amazon Seller and Advertising reports to a private database. View beautiful, on demand, exportable performance reports.

bidbear.io
bidbear-application-screenshot