React's useEffect Firing Twice in Dev? The HMR Culprit and a Simple Check

Posted by Aug on March 9, 2024

Abstract:
This post provides a quick developer tip for React and Next.js users: if your useEffect hook appears to run twice during development, Hot Module Reloading (HMR) in conjunction with React’s Strict Mode is likely the cause. It explains why this happens as an intentional development-only behavior to help catch side effects and suggests verifying by testing a production build.

Estimated reading time: 2 minutes

I recently ran into some puzzling behavior while working on animations for my app – specifically, drawing a series of I-Ching style lines. I have an array representing these lines, and I use a loop within a React useEffect hook to draw each one.

The problem? When running my Next.js app in development mode (next dev), the useEffect hook was clearly being triggered twice. Instead of the expected 6 lines, 12 lines would appear on the screen!

For a moment, I thought I was overlooking something obvious in my logic or perhaps going a little nuts. Then, it was pointed out to me (and a quick search confirmed) that Hot Module Reloading (HMR), a feature active in development mode to speed up updates without full page reloads, can sometimes cause useEffect (and other React lifecycle events or initialization code) to run twice. This is especially true in newer versions of React when Strict Mode is enabled (which it is by default in Next.js development). React intentionally does this in Strict Mode during development to help developers catch unintended side effects.

So, here’s a quick word to the wise:

If you’re seeing your useEffect hooks (or similar setup code) executing twice while testing in development mode, don’t immediately assume your code is wrong.

Before you spend too much time debugging your component logic, try this:

  1. Build your application for production.
  2. Run or deploy the production version.
  3. Test the behavior there.

If the double execution disappears in the production build, then HMR (and React’s Strict Mode development behavior) was likely the “culprit” for what you were observing in dev. This doesn’t mean HMR is bad – it’s incredibly useful! But it’s good to be aware of this specific development-only behavior to save yourself some debugging headaches.