4

I was studying the React hooks and inserting some console logs in the code to better understand the render flow. Then I started to simulate the setState effects sending the same value to see if React would render it again.

import { useState } from "react";

function ManComponent() {
  /* States */
  const [shirt, setShirt] = useState("Blue Shirt");
  console.log("Rendering man with "+shirt);
  
  /* Actions */
  const changeShirt = (newShirt) => {
    console.log("[Man] Changing shirt from "+shirt+" to "+newShirt);
    setShirt(newShirt);
  };
  
  
  return (
    <div>
      <p>The man is using: {shirt}</p>
      
      <div>
        <button onClick={() => changeShirt("Red Shirt")}>Change to red shirt</button>
        <button onClick={() => changeShirt("Blue Shirt")}>Change to blue shirt</button>
      </div>
    </div>
  );
}

export default function App() {
  console.log("Rendering app");

  return (
    <ManComponent />
  );
}

If I click at "Change to red shirt" three times, I get two logs saying that the component is rendering. It should be just one, since I changed the value just once, right?

The console log shows double rendering with just one state change

My problem is to understand why does the component renders two times if the state just changed once.

P.S: I tried to remove the Strict Mode, and it had no effect. I'm using React 17 version, by the way.

Emile Bergeron
  • 17,074
  • 5
  • 83
  • 129
Nícolas Amarante
  • 165
  • 1
  • 1
  • 7
  • You should use a useEffect hook with the shirt state as a dependency, that way it will only log one time, because it ignores every other change except the changes made to that state. – cyw Sep 11 '21 at 03:04

1 Answers1

1

What's happening

The React documentation addresses exactly this behavior in the useState hook API.

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.


It's not Strict mode

Strict mode was a good guess, since it used to trigger duplicate logs in the render cycle before React 17, though it was updated to silence these duplicate logs to avoid confusion.

Starting with React 17, React automatically modifies the console methods like console.log() to silence the logs in the second call to lifecycle functions. However, it may cause undesired behavior in certain cases where a workaround can be used.

...which caused additional confusion, leading to another update on that strict mode logging behavior (which is not released yet I believe).

The workaround mentioned in the quote above to debug strict mode rendering with logs is to keep a module scoped copy of the log function.

const log = console.log;

const App = () => {
  log('Will not be silenced by React');
  return //...
};
Emile Bergeron
  • 17,074
  • 5
  • 83
  • 129
  • I've been reading all the sources about it. I believe that the behavior is not about a logging problem. The useState hook invokes one more rendering of the component before it compares with actual state. And, it does not happen on the next tries. It's just that I'm trying to understand the React algorithm for the DOM update. – Nícolas Amarante Sep 13 '21 at 13:11
  • @NícolasAmarante the behaviour you're experiencing is the first part of my answer, React bailing out but still re-renders the current component. The rest of the answer is a clarification on why it's **not** the strict mode logging issue. – Emile Bergeron Sep 13 '21 at 13:29