As part of React Strict Mode, certain lifecycle functions will be ran twice, such as functions passed to useState, useMemo, or useReducer, or the whole body of a functional component, which might include your useEffect hook. If you’re unfamiliar with using hooks in React, check out our tutorial here.

While you can disable this, and I will still explain how, I’d consider avoiding doing so, and here’s why. If you’d like to skip the explanation, feel free to skip to the last section for a quick solution.

Side Effects

In React, you want your components to be pure, and free of unpredictable side effects. In React, side effects means your functions or components affecting anything outside of themselves.
React expects your functions to be “pure”, e.g. with the same arguments, they have the same results. For this reason it expects to be able to call your hooks twice, and they should have the same result. Even if they have a side-effect like performing an API call, it should cause the same result twice.

This is because outside of strict mode, React might run your hooks multiple times anyway, as it breaks the rendering phase up into pieces, and might pause or restart work. The best way to get around this is either to write your components with this in mind, or to explicitly check for when your hook has been ran.

Working With React

With all that said, that means that the ideal solution to solve this problem isn’t to stop your useEffect running twice, it’s to write your code so that it doesn’t matter if it runs twice.

Events

One of the cases you might run into this issue is hooking into events/subscriptions. Let’s take a look at a real-world example:

export const useMediaQuery = (screen: ScreenSize) => {
    const [matches, setMatches] = useState(false);

    useEffect(() => {
        const query = `(min-width: ${sizes[screen]})`;
        const media = window.matchMedia(query);

        if (media.matches !== matches) {
            setMatches(media.matches);
        }
        const listener = () => setMatches(media.matches);
        window.addEventListener('resize', listener);
    }, [matches, screen]);

    return matches;
};

This is a simple hook which hooks into the “resize” event to check if the screen size matches a given size. The key part is as part of our hook, we have to set up an event listener. Since our effect runs twice, our listener runs twice for every resize:

So how do we fix this? Simple, we can create a cleanup function to return in our useEffect:

        window.addEventListener('resize', listener);
        return () => window.removeEventListener('resize', listener);
    }, [matches, screen]);

Now when our useEffect runs multiple times, after every time the cleanup function is run. This means we never have more than one event listener, since it gets removed each time.

Example 2

You might find that adding a cleanup function is the solution in a lot of cases. Let’s take a look at another example:

export default function StrictMode2() {
    const [notifications, setNotifications] = useState<string[]>([]);
    useEffect(() => {
        if (newUser) setNotifications((n) => [...n, 'Welcome!']);
    }, []);

    return (
        <div className="h-screen w-full">
            <ul className="w-96 bg-stone-800 p-10 text-stone-50">
                {notifications.map((n, i) => (
                    <li key={n + i}>{n}</li>
                ))}
            </ul>
        </div>
    );
}

I’ve made up a mockup of a simple notification bar. If our imaginary user is a new user, we want to send them a welcome notification. Here’s what it looks like:

So what’s happened here? Again our useEffect is running twice, and so our notification gets added twice. Let’s fix this with a cleanup function:

And with a short line, our code is fixed:

    useEffect(() => {
        if (newUser) setNotifications((n) => [...n, 'Welcome!']);
        return () => setNotifications([]);
    }, []);

Workaround

In some situations, you might really only want a hook to run once. This might be useful if for example you’re using the Spotify API, where you send a single-use code in order to receive an access token. To ensure this you can use the useRef hook, which you can read more about here, to keep track of whether or not to run your useEffect.

Here’s a simple example, our fetchData() function simply updates a counter when it has been ran. By default our counter gets updated twice, which isn’t what we want.


function App() {
  const [counter, setCounter] = useState(0);

  const fetchData = () => {
        console.log('Fetching data...');
        setCounter((oldValue) => oldValue+1);
    }

  useEffect(() =>; {
      fetchData();
  },[])

  return (
    <div>
        <h1>Times Fetched:</h1>
      <h1>{counter}</h1>
    </div>
  );
}

To get around this, we use useRef to keep track of a boolean. If it’s true, we’ve already been here, and we can return. If it is false, we can perform our fetch and then update the ref.

function App() {
  const [counter, setCounter] = useState(0);
  const dataFetchedRef = useRef(false);

  const fetchData = () => {
        console.log('Fetching data...');
        setCounter((oldValue) =>; oldValue+1);
    }

  useEffect(() =>; {
      if (dataFetchedRef.current) return;
      dataFetchedRef.current = true;
      fetchData();
  },[])

  return (
    <div>
        <h1>Times Fetched:</h1>
      <h1>{counter}</h1>
    </div>
  );
}

By the way, check out this tutorial if you’d like to see more on some actual data fetching in React.

Disabling React Strict Mode

If you’ve created your React app using create-react-app, you’re likely to have this in your index.js file, or something similar.

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

Simply remove the <React.StrictMode> tags around the <App> tag, and this should disable strict mode for your app! You can also only include this tag in pages where you do want strict mode enabled, to opt-in on a page-by-page basis.

Conclusion

The key takeaway from this article should be that your hooks running twice in React ideally shouldn’t be an issue from your code. It’s better to write your code so that this has no side effects, rather than to work around it. Think of it as React testing your app for you for robustness; it’s better to be able to pass the test than disable it.

Another important thing to note is that you will only run into this problem in development mode – With a production build of your app strict mode will be disabled, and the hooks will not explicitly run twice. Like I said, I’d recommend keeping strict mode enabled, even if it is causing your effects to run twice, since its more likely to help catch actual issues in your app, but the choice is yours!

Hopefully by now I’ve solved your issue, and if so feel free to leave a comment below! Let me know if you have any suggestions, or if you simply liked the article!

Avatar photo
👋 Hey, I'm Omari Thompson-Edwards
Hey, I'm Omari! I'm a full-stack developer from the UK. I'm currently looking for graduate and freelance software engineering roles, so if you liked this article, reach out on Twitter at @marile0n

💬 Leave a comment

Your email address will not be published. Required fields are marked *

We will never share your email with anyone else.

Comments