4

Consider the following code:

import React from "react";

function App() {
  console.log("render");

  setTimeout(() => {
    console.log("time is up");
  }, 2000);

  return <div>nothing to see here</div>;
}

export default App;

I expected the following output:

render
time is up

But the real output in the Chrome console is:

enter image description here

Note the 2 before time is up, showing us that time is up was output twice.

I don't understand why time is up is output twice. Can anyone explain this?

jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
stefan.at.kotlin
  • 15,347
  • 38
  • 147
  • 270
  • What is `timers`? Is that just an import for the global `setTimeout`, or something else? – jonrsharpe Jan 17 '21 at 21:03
  • @jonrsharpe Visual Studio Code did that for me. I removed it, but observe the same behaviour. – stefan.at.kotlin Jan 17 '21 at 21:05
  • When I run that locally I see both `render` *and* `time is up` twice, could not reproduce. – jonrsharpe Jan 17 '21 at 21:05
  • 2
    @stefan.at.wpf Could you please verify from your index.js that you're not running in React.StrictMode? – Prashant Vishwakarma Jan 17 '21 at 21:19
  • @PrashantVishwakarma I was running in strict mode and after having removed the strict mode, *time is up* is only shown once as expected. I don't understand why strict mode changes that though. Can you explain/post as answer? – stefan.at.kotlin Jan 17 '21 at 21:22
  • @jonrsharpe Try with strict mode (default of create react app). Now the question is, why does strict mode cause this. – stefan.at.kotlin Jan 17 '21 at 21:23
  • Are you asking why they both happen twice in strict mode (read https://reactjs.org/docs/strict-mode.html), or why they happen a different number of times (could not reproduce)? – jonrsharpe Jan 17 '21 at 21:25
  • @jonrsharpe Wondering about the different number of times (1 x render, 2 x *time is up*) – stefan.at.kotlin Jan 17 '21 at 21:26
  • Looks like React patches `console.log` when it appears directly in the function body in strict mode: https://github.com/facebook/react/pull/18547. – jonrsharpe Jan 17 '21 at 21:34

3 Answers3

6

The component is rendered twice because CRA sets React's strict mode by default, which among other things tries to help you detect side effects (emphasis mine):

Strict mode can’t automatically detect side effects for you, but it can help you spot them by making them a little more deterministic. This is done by intentionally double-invoking the following functions:

  • Class component constructor, render, and shouldComponentUpdate methods
  • Class component static getDerivedStateFromProps method
  • Function component bodies
  • State updater functions (the first argument to setState)
  • Functions passed to useState, useMemo, or useReducer

So far, this is covered by posts like:


However, you might then expect both "render" and "time is up" to be logged twice. The reason that doesn't happen, which as far as I can find hasn't been covered yet on SO, is that React was updated to deliberately suppress the duplicate logs:

This disables console.log by temporarily patching the global console object during the second render pass when we double render in strict mode in DEV.

This applies only to console.logs directly in the functions mentioned above, which doesn't include setTimeout callbacks, so the second console.log("render") is swallowed but the second console.log("time is up") is not.

jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
0

React.StrictMode is a feature intended to ease devs spot problems related to React Lifecycle

It only happens when you run in development mode and renders with extra rules like rendering components more than once.

I think the documentation does a great job explaining this. StrictMode: React

Understandably, it gets irritating for people notice for the first time!

0

I believe the problem here is that you didn't put the setTimeout inside a useEffect. This means that when the app rerenders another timeout will get started and cause the issue you are finding. Try something like this.

import React, {useEffect} from "react";

function App() {
  console.log("render");

    useEffect(() => {
      const timer = setTimeout(() => {
        console.log('time is up');
      }, 2000);

      return () => {
        clearTimeout(timer);
      }
    }, []);

  return <div>nothing to see here</div>;
}

export default App;
Leonardo Drici
  • 749
  • 3
  • 11
  • 32