Why does useEffect run two times?

By

Learn why useEffect runs two times in React 18, caused by strict mode in development, and how to stop it by removing the StrictMode wrapper component.

~~~

useEffect runs two times on mount because your app runs in strict mode, in development. React 18 changed strict mode to mount your component, unmount it, then mount it again, on purpose. Production builds are not affected.

React 18 released in March 2022 introduced this behavior. I didn’t even realize it at first, reading the React 18 launch post, buried along a lot of new features.

If your application is behaving strangely after updating to React 18, this is likely the cause.

Just in development mode, but this is the mode everyone builds their application on.

And just in strict mode, but that’s now the default for applications built using Vite, create-react-app or Next.js.

It’s not a problem of your code, it’s how things work now in React.

What happens exactly

On mount, React runs your effect, then runs its cleanup function, then runs the effect again.

If your effect returns no cleanup, the setup code just runs twice. A subscription gets created two times. A fetch fires two times.

Why does React do this?

React wants to surface effects that are missing a cleanup function. Future React features will unmount and remount components while preserving their state, so your effects must survive being set up and torn down more than once.

The double run in development makes broken effects visible immediately, instead of in production.

So the real fix is writing the cleanup. Take an interval:

useEffect(() => {
  const id = setInterval(() => {
    console.log('tick')
  }, 1000)

  return () => clearInterval(id)
}, [])

Without the return, strict mode leaves you with two intervals ticking. With it, the first interval is cleared before the second run, and everything works.

How to disable this behavior

The only way to disable this behavior is to disable strict mode.

Strict mode is important so this is a temporary workaround until you can fix any issue this change introduced.

In Vite, go to src/main.jsx and remove the <React.StrictMode> wrapper component, from:

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import './index.css'

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

to:

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import './index.css'

ReactDOM.createRoot(document.getElementById('root')).render(
  <App />
)

You can do it in Next.js by adding the option

reactStrictMode: false

in your next.config.js file.

In create-react-app you can go in your index.js file and remove the StrictMode higher order component, from:

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

import App from './App';

const rootElement = document.getElementById('root');
const root = createRoot(rootElement);

root.render(
  <StrictMode>
    <App />
  </StrictMode>
);

to

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

import App from './App';

const rootElement = document.getElementById('root');
const root = createRoot(rootElement);

root.render(
  <App />
);

Remember this only silences the symptom. If an effect misbehaves when it runs twice in development, it will also misbehave when React remounts it for real. Add the cleanup, then turn strict mode back on.

Tagged: React · All topics
~~~

Related posts about react: