Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is a ReactJS component using Hooks rendered once or twice depending on developer console is open or not?

The following code prints out the same time twice inside the console of codesandbox.io website (that version uses StrictMode) and also in the snippet below (not using StrictMode):

const { useState, useEffect } = React;

function useCurrentTime() {
  const [timeString, setTimeString] = useState("");

  useEffect(() => {
    const intervalID = setInterval(() => {
      setTimeString(new Date().toLocaleTimeString());
    }, 100);
    return () => clearInterval(intervalID);
  }, []);

  return timeString;
}

function App() {
  const s = useCurrentTime();
  console.log(s);

  return <div className="App">{s}</div>;
}

ReactDOM.render(<App />, document.getElementById("root"));
<div id="root"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.development.js"></script>

Demo: https://codesandbox.io/s/gallant-bas-3lq5w?file=/src/App.js (using StrictMode)

Here's a snippet using production libs; it still logs twice:

const { useState, useEffect } = React;

function useCurrentTime() {
  const [timeString, setTimeString] = useState("");

  useEffect(() => {
    const intervalID = setInterval(() => {
      setTimeString(new Date().toLocaleTimeString());
    }, 100);
    return () => clearInterval(intervalID);
  }, []);

  return timeString;
}

function App() {
  const s = useCurrentTime();
  console.log(s);

  return <div className="App">{s}</div>;
}

ReactDOM.render(<App />, document.getElementById("root"));
<div id="root"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>

However, when I open up the Developer's Console, I see each time printed out only once, and also in the codesandbox.io's console, I see it printed once.

And then if I create a standalone React app using create-react-app, and use the above code, and each time is printed twice.

How is this behavior understood, for printed out once or twice depending on different situations? My thinking was, if the state changes, then App is re-rendered, with that new string, once, so it is printed out once. What is strange is especially why does it printed out twice but when Dev Console is open, then it is once?

like image 394
nonopolarity Avatar asked Mar 08 '21 07:03

nonopolarity


1 Answers

As far as I can tell, the double-call we see to App is expected behavior. From the useState documentation:

Bailing out of a state update

If you update a State Hook to the same value as the current state, React will bail out without rendering the children or firing effects. (React uses the Object.is comparison algorithm.)

Note that React may still need to render that specific component again before bailing out. That shouldn’t be a concern because React won’t unnecessarily go “deeper” into the tree. If you’re doing expensive calculations while rendering, you can optimize them with useMemo.

The key bits there are "Note that React may still need to render that specific component again before bailing out..." and "...React won’t unnecessarily go “deeper” into the tree..."

And indeed, if I update your example so that it uses a child component to show the time string, we only see that child component get called once per timeString value rather than twice as App is, even though the child component isn't wrapped in React.memo:

const { useState, useEffect } = React;

function useCurrentTime() {
    const [timeString, setTimeString] = useState("");
  
    useEffect(() => {
        const intervalID = setInterval(() => {
            setTimeString(new Date().toLocaleTimeString());
        }, 100);
        return () => clearInterval(intervalID);
    }, []);
  
    return timeString;
}

function ShowTime({timeString}) {
    console.log("ShowTime", timeString);
    return <div className="App">{timeString}</div>;
}

function App() {
    const s = useCurrentTime();
    console.log("App", s);
  
    return <ShowTime timeString={s} />;
}

ReactDOM.render(<App />, document.getElementById("root"));
<div id="root"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>

When I run that, I see:

App 09:57:14
ShowTime 09:57:14
App 09:57:14
App 09:57:15
ShowTime 09:57:15
App 09:57:15
App 09:57:16
ShowTime 09:57:16
App 09:57:16
App 09:57:17
ShowTime 09:57:17
App 09:57:17
App 09:57:18
ShowTime 09:57:18
App 09:57:18
App 09:57:19
ShowTime 09:57:19
App 09:57:19
App 09:57:20
ShowTime 09:57:20
App 09:57:20

Note how although App is called twice for each value of timeString, ShowTime is only called once.

I should note that this is more automatic than it was with class components. The equivalent class component would update 10 times per second if you didn't implement shouldComponentUpdate. :-)

Some of what you were seeing over on CodeSandbox may well have been due to StrictMode, more on that here. But the two calls to App for each timeString value are just React doing its thing.

like image 104
T.J. Crowder Avatar answered Oct 14 '22 06:10

T.J. Crowder