Does useEffect really make React render twice?

29 MAY 2022
REACT
Does useEffect really make React render twice?

It's not a bug, it is a feature of React 18 in strict mode, ie. in development build only.

React 18 introduced a new development-only check to Strict Mode, which automatically unmounts and remounts every component when a component mounts for the first time; it restores the previous state on the second mount. Because useEffect with empty dependency array runs at every mount, it gets to run twice in strict mode.

As in the code sample below, the strict mode is typically enabled at the root of the application, although it can be enabled at different levels of the application tree as well.

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>
);

You may remove Strict mode from your app, however it is recommended that you keep it as it enforces the best-practices for React. React's strict mode documentation explains those best-practices in detail. To demonstrate the usefulness of enabling the strict mode, let's take a simple component like below:

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

export default function App() {
  const [count1, setCount1] = useState(0);
  const [count2, setCount2] = useState(0);

  useEffect(() => {
    setInterval(() => {
      setCount1((c) => c + 1);
    }, 1000);
  }, []);

  useEffect(() => {
    const interval = setInterval(() => {
      setCount2((c) => c + 1);
    }, 1000);
    return () => clearInterval(interval);
  }, []);

  return (
    <div>
      <p>Count1: {count1}</p>
      <p>Count2: {count2}</p>
    </div>
  );
}

In this component, both counts are incremented by 1 at every second. The difference is that count1 effect doesn't clean its interval, whereas count2 effect does.

Running this code on Stackblitz shows that the count1 increments at twice the rate of the latter. Because it doesn't clear its interval, strict mode's unmount and remount leaves the initial interval as leak and it continues to increment the counter. So, count1 state is set by two interval callback functions, therefore it increments at twice the rate of count2.

As this example shows, enabling React's strict mode helped us catch a side-effect issue by checking how it behaved in the unmount/mount scenario. We couldn't detect this issue if we hadn't enabled the strict mode; you can easily check this by removing the StrictMode from your application tree. In this case, we only observed the issue visually by inspecting the counters, however in some other cases that violate React's best practices, the strict mode will dump warnings in developer console.

Near Max

© 2024 Near Max Ltd

Near Max Ltd is a limited company registered under 09222812 in England and Wales. Registered address: Highwoods, Chinnor Hill, Chinnor, Oxfordshire, OX39 4BD.

Get in touch

info@nearmax.co.uk

Highwoods, Chinnor Hill, Oxfordshire, OX39 4BD, United Kingdom