Skip to main content

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.

Recent Work

Free desktop AI Chat client, designed for developers and businesses. Unlocks advanced model settings only available in the API. Includes quality of life features like custom syntax highlighting.

Learn More
slide-6
slide-5
slide-2
slide-1
slide-3
slide-4
Technologies Used
TypeScript
Electron
React

BidBear

bidbear.io

Bidbear is a report automation tool. It downloads Amazon Seller and Advertising reports, daily, to a private database. It then merges and formats the data into beautiful, on demand, exportable performance reports.

Learn More
slide-1
slide-2
slide-5
slide-3
slide-4

Technologies Used

Front End
JavaScript
Docker
React
Redux
Vite
Next
Docusaurus
Stripe
Sentry
D3
React-Flow
TipTap
Back End
JavaScript
Python
AWS CognitoCognito
AWS API GatewayAPI Gateway
AWS LambdaLambda
AWS AthenaAthena
AWS GlueGlue
AWS Step FunctionsStep Functions
AWS SQSSQS
AWS DynamoDBDynamo DB
AWS S3S3
AWS CloudwatchCloudWatch
AWS CloudFrontCloudFront
AWS Route 53Route 53
AWS EventBridgeEventBridge