React useEffect runs twice on mount

Programming
Published on October 11, 2024

useEffect Running Twice on Mount During Development Is by Design

This is a personal memo from a React beginner.

This is a specification from React 18. useEffect behaves this way for all components only during development when StrictMode is enabled.

Imgur

When useEffect fetches information from an API once on mount, it's fetched twice. This was due to an added feature of StrictMode in React 18.

when running in “strict mode“ React will intentionally double-render components for in order to flush out unsafe side effects.

What This Means

Imagine the ChatRoom component is part of a large application with many different screens. The user initially opens the ChatRoom page. The component mounts, and the connect() side effect is called. Then, let's say they navigate to another screen, like the settings page. The ChatRoom component unmounts. After that, if the user clicks "Back," the ChatRoom mounts again. This results in a second connection being established. However, the initial connection was not torn down. As the user navigates through pages in the application, connections accumulate.

Such bugs are easily missed without extensive manual testing. To help quickly find these issues, React re-mounts every component immediately after its initial mount during development. By seeing "Connecting..." logged twice, you are more likely to notice the real problem: your code is not closing the connection when the component unmounts. To fix this problem, return a cleanup function from useEffect.

In other words, code that behaves strangely due to multiple mounts is bad code (a bug), so React intentionally runs it twice during development to help you identify it.

Countermeasures and Philosophy

useEffect runs twice on mount during development, but only once in a production environment. Since this is the correct behavior according to React, it's necessary to implement code that aligns with this philosophy. It seems to be saying that "multiple components can run in parallel asynchronously, and re-mounting after initial mount can occur," so handle it appropriately.

  • "Disabling StrictMode" is probably not a good idea.
  • Implement a cleanup function to perform cleanup.

Typically, you handle this by implementing a cleanup function. What a cleanup does is stop or undo everything the effect was doing. Most users won't notice the difference between the single effect (in production) and the effect → cleanup → effect sequence (in development).

React always calls the cleanup function before the effect is re-executed. It also performs cleanup when the component finally unmounts (is removed).

useEffect(() => {
	connect();
	return () => {  // ★★ Cleanup process
		disconnect();();
	};
}, []);
useEffect(() => {
	let ignore = false;
	async function startFetching() {
		const json = await fetchTodos(userId);
		if (!ignore) {
			setTodos(json);
		}
	}
	startFetching();
	return () => {  // ★★ Cleanup process
		ignore = true;
	};
}, [userId]);

How to perform cleanup needs to be considered for each specific process.

References

React + TypeScript: React 18でコンポーネントのマウント時にuseEffectが2度実行されてしまう useEffectがマウント時に2回実行される React(公式)Synchronizing with Effects