How to stop useEffect run twice since React 18

Jul 25, 2023#react#how-to

This is a common question among React developers who use the useEffect hook. How to fix the bug useEffect runs twice on component mount (StrictMode, development).

The useEffect hook lets you perform side effects in function components, such as fetching data, updating the document title, or subscribing to events. You can pass a second argument to useEffect, which is an array of dependencies, to control when the effect runs. If you pass an empty array, the effect will only run once after the first render.

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

function MyComponent() {
  const [data, setData] = useState(null);

  useEffect(() => {
    async function fetchData() {
      try {
        const response = await fetch('https://jsonplaceholder.typicode.com/posts/1');
        if (response.ok) {
          const data = await response.json();
          setData(data);
        } else {
          throw new Error('Something went wrong');
        }
      } catch (error) {
        console.error(error);
      }
    }

    fetchData();
  }, []);

  // ...
}

However, sometimes you may notice that the effect runs twice, even if you pass an empty array. This can be confusing and frustrating, especially if your effect involves expensive or duplicate operations. There are several possible reasons why this happens, and some ways to avoid or fix it.

When Strict Mode is on, in development, React runs setup and cleanup one extra time before the actual setup.

import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';

const root = createRoot(document.getElementById('root'));
root.render(
  <StrictMode>
    <App />
  </StrictMode>
);

One reason why useEffect may run twice is that you are using StrictMode in your app. StrictMode is a special component that helps you find potential problems in your code by activating additional checks and warnings. One of these checks is to intentionally render your components twice in development mode, to detect any unintended side effects or state mutations. This means that your useEffect hook will also run twice in development mode, but not in production mode. You can read more about StrictMode and its benefits on the official React documentation.

If you want to avoid running your effect twice in development mode, you can disable StrictMode for your app or for specific components.

You can also use a custom hook to run your effect only once.

// https://github.com/facebook/react/issues/24502#issuecomment-1118867879

useEffect(() => {
  let ignore = false;
  fetchStuff().then(res => {
    if (!ignore) setResult(res)
  })
  return () => { ignore = true }
}, [])

Fetching data in useEffect is fine but require a lot of boilerplate codes. Using a data fetching library is preferred as these solutions will simplify the logic of data fetching in your project with tons of amazing built-in features:

  • Transport and protocol agnostic (REST, GraphQL, etc)
  • Caching, refetching, polling, realtime
  • Pagination, load more, infinite scrolling
  • Requests and responses interception
  • Revalidation on focus, network recovery
  • Performance optimizations
  • Request cancellation

Here is an example of using React Query (34.7k ⭐) to fetch data from an API:

import React from 'react';
import { useQuery } from 'react-query';

function MyComponent() {
  // Use the useQuery hook to fetch data from an API endpoint
  const { data, isLoading, error } = useQuery('posts', () =>
    fetch('https://jsonplaceholder.typicode.com/posts').then(res => res.json())
  );

  // ...
}

There’s some more great information about this written by React core team: