skip to Main Content
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 rerenders 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 rerenders 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

2

Answers


  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
    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.

    Login or Signup to reply.
  2. You can use memo to skip rendering a component if its props have not changed:

    export default React.memo(App);
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search