Have you heard about Envirotechnical? Let's fight climate change one byte at a time :) Learn More →

logo
Published on

React useEffect double rendering fix and why you should stop crying about it

Authors
Table of Contents

Why does React 18 double renders my useEffect in development with Strict mode?

React 18 brings a lot of new candies to the frontend store but what most people seem to lose their mind about is the fact that, when in Strict mode during development, React double renders the components with a mount and unmount trick. That's f$£king intended and it's there to help you out.

Repeat after me:

  1. It's not gonna be there during production
  2. It helps to discover bugs in the mounting/unmounting phase during development instead of production
  3. Dan Abramov said it's cool and he's cool so that's enough reason.
React 18 double rendering with empty dependency array

So, should you just leave it at that? Yes, that's actually what you are intended to do. No weird useRef abominations to fix a problem that doesn't exist.

Quite a few things we're going to discuss in this article are freely taken from Dan Abramov's Overreacted which I invite you to read with care and appreciation. The explanations that he gives (in a very lengthy article) are useful to understand the inner workings of useEffect and why we should detach from the class mental model.

A comprehensive guide to understand React 18 useEffect hook

You've been working with React hooks since version 16.8 and learnt that whenever you wanted to simulate the componentDidMount lifecycle without using classes, you would have had to lean toward the useEffect hook.

Now, what you might have read from the official React documentation is this very important tip:

If you’re familiar with React class lifecycle methods, you can think of useEffect Hook as componentDidMount, componentDidUpdate, and componentWillUnmount combined.

It does tell you that you might think of useEffect as a way to resemble the class lifecycle methods but it's not a f$%kin class anymore. It's all functional baby.

So, what does that mean for us, the developers? It's simple, really. It just means that we can help our brains with the fact that useEffect can simulate parts of the class component lifecycle but it's not the same as one.

import { useEffect } from 'react'

export default function App() {
  useEffect(() => {
    console.log(`I'm mounting!`)
    return () => console.log(`And now I'm unmounting`)
  }, [])

  return (
    <div className="App">
      <h1>Hello 404 Readers!</h1>
    </div>
  )
}

Before version 18 of React, we would use useEffect with an empty dependency array as the second parameter of our useEffect hook and that would work almost exactly as the componentDidMount lifecycle method.

But what about from version 18 and onward? What's the weird thing happening behind the development curtains?

Let's go and see how our codesandbox behaves! Make sure you open the console to see the logs!

Edit nostalgic-banzai-pcstz7

In the codesandbox you will find the code that we just saw and you will notice that in the console something weird is happening. The rendering happens twice, even though we explicitly asked for what we thought would be the same as componentDidMount.

But Lorenzo, we want it to work just as it did before! How can we do that!?

It's already working as it did before. What you are seeing (this double rendering happening in the console) is actually a helpful way for React to make sure that your application state doesn't break with just a mounting and unmounting of the components.

React is, in all effects (pun intended), simulating a page transition that would mount and unmount components based on what you wrote.

import { StrictMode } from 'react' // <--- if you want to stop double rendering even while in development remove this
import { createRoot } from 'react-dom/client'

import App from './App'

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

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

If, for some weird and unexplicable reasons, you need to turn off this behaviour of double rendering in React, you should know that it can be removed by avoiding the StrictMode encapsulation of your app.

You should also take care about the fact that this behaviour is ONLY seen in development mode with StrictMode active. StrictMode is not enabled during production and gets removed during the build process. No builds were harmed during the filming of this release.

I'm not even sure this is a scenario but if you need to keep StrictMode and avoid double renderings, you should check out this video from Jack Herrington about React useEffect hotfix.

As a side note, and this is explained very well in Overreacted, you should always be thoughtful about your dependencies and thinking in dependency will be very beneficial to your React programming skills because you're going to always think about what's needed and what's not.

How to fetch data with useEffect with React 18

For the data fetching part I went and asked Dan Abramov directly and he was very clear about this:

The general recommendation for data fetching is to use your framework’s mechanism — if you use a framework. This is because frameworks (like Next and Remix) decouple fetching from rendering. For example, fetching in effects means your app can’t produce useful HTML with server rendering — effects don’t run on the server.

If you don’t have a framework then the next option is some client caching layer. Like react query, Apollo, etc. Since there’s a cache, remounting doesn’t produce any observably different behavior.

If you don’t have a framework and you don’t have a cache, then you’re left with raw effects. Sure, if you don’t deduplicate requests (by writing your own cache) then you’ll have double fetching in development. You can pass abort signal to prevent the second request from getting used but it doesn’t really matter whether you do it or not.

My main point is that you’re focusing on the wrong problem. The double request in development doesn’t create any issues (if it leads to bugs you should fix them anyway because they reveal race conditions in your code). The actual thing you should probably care about fixing is that fetching data without a framework or at least a cache isn’t very efficient. But this has nothing to do with React 18 (or even React itself) and has always been true.

  • Dan Abramov, May 2022

I quoted what he told me in its entirety because I think it was very explicative, concise and clear.

Instead of using raw useEffects go for an opinionated library like React Query or if you are using Redux Toolkit you will find that it also has a RTK Query that comes out of the box and is ready to use along with Redux.

This also makes a lot of sense if you think about the fact that React is a library, not a framework. So just make sure to pick the right tools for the job and you are all set for a great start!

The goodbye

I hope you found this article useful and to your liking and if you have any requests, drop a message on one of my social media accounts or open an issue/start a discussion on github, on this repository!

As always you can find me on Twitter, listen to my Podcast on Spotify and add me on LinkedIn to talk professionally (yeah, right)