6
import { useState } from "react";

export default function App() {
  const [buttonClicked, setButtonClicked] = useState(false);

  console.log('Render');

  return (
    <div className="App">
      <button onClick={() => setButtonClicked(true)}>Click me</button>
      {buttonClicked && <div>Button was clicked</div>}
    </div>
  );
}

This component is not running in the StrictMode. It renders first, so we see one render in the console. When we click the button it re-renders because of the state update. We can see Render in the console once again. But, to my surprise, when we click the button once again it also re-renders the component, and we see Render in the console for the 3rd time.

I thought the state update was checking if the value has changed, so it wouldn't render for the 3rd time.

What's even more weird to me, is when we click the button for the 3rd time, it doesn't rerender the component.

Why is this happening?

Codesandbox: https://codesandbox.io/s/proud-star-rg49x9?file=/src/App.js:0-336

Pluto
  • 4,177
  • 1
  • 3
  • 25

2 Answers2

1

In your code, there is no third render. it's component invoking, not rendering.

For example:

import { useEffect, useState } from "react";

export default function App() {
  const [buttonClicked, setButtonClicked] = useState(false);
  console.log("invoked");

  useEffect(() => {
    console.log("render");
  });

  return (
    <div className="App">
      <button onClick={() => setButtonClicked(true)}>true</button>
      <button onClick={() => setButtonClicked(false)}>false</button>
      {buttonClicked && <div>Button was clicked</div>}
    </div>
  );
}

To test this code, please go to codesandbox.

This code logs in console like this:

invoked // first mount
render  // first mount
invoked // first click true button (if you click false button, there is no effect)
render  // first click true button
invoked // second click true button, after then if you click true button, there is no logging.
invoked // first click false button
render  // first click false button
invoked // second click false button, after then if you click false button, there is no logging.

The console.log statement outside the return is not related to the rendering process. It is just a normal JavaScript statement that runs every time the function is invoked. The component function can be invoked for various reasons, such as props changes, context changes, or parent re-renders.

React may invoke the component function to check for changes in the output, but it will not re-render the component or its children unless there is a difference in the output. So, the console.log statement outside the return will run every time the component function is invoked, but the console.log statement inside the useEffect hook will only run after the initial render and after every re-render.

React will bail out of re-rendering if the state value is the same as before.

According to React documentation:

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.

This means that after clicking the same button twice, React will not re-render the component or its children because there is no change in the output. React will also not invoke the component function again unless there is a reason to check for changes in the output, such as props changes, context changes, or parent re-renders. This is how React optimizes the performance of your application by avoiding unnecessary work.


UPDATED ANSWER

In my example, if you click false button the first time, you can't see any more logs. It's because React already bailed out after the first mount.

You can see this:

invoked // first mount
render  // first mount
Pluto
  • 4,177
  • 1
  • 3
  • 25
  • This is a good explanation can you tell me please why when we initilize the state with `false` and first click the "false" button the component is not invoked? and this only happen when the prev value is updated with the setter function? – Ahmed Sbai Jul 17 '23 at 15:51
  • As I mentioned before, `React will bail out of re-rendering if the state value is the same as before`. So there is no component invoking because there is no change in the output such as changing state, context, props, etc. – Pluto Jul 17 '23 at 16:20
0

You can use memo to skip rendering a component if its props have not changed:

export default React.memo(App);
zoldxk
  • 2,632
  • 1
  • 7
  • 29