Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Component rerenders when state has not changed on the second click

I have a tabs component that changes state every time a different tab is clicked.

Component Parent

import { useState } from "react";
import "./styles.scss";
import MemoizedTab from "./tab";

export default function App() {
  const [selectTab, setSelectTab] = useState("a");
  console.log("parent render");
  return (
    <div className="App">
      <div className="tab-list">
        <MemoizedTab
          tab={"a"}
          title={"First Title"}
          setSelectTab={setSelectTab}
        />
        <MemoizedTab
          tab={"b"}
          title={"Second Title"}
          setSelectTab={setSelectTab}
        />
        <MemoizedTab
          tab={"c"}
          title={"Third Title"}
          setSelectTab={setSelectTab}
        />
      </div>
      {selectTab === "a" && <div>this is a</div>}
      {selectTab === "b" && <div>this is b</div>}
      {selectTab === "c" && <div>this is c</div>}
    </div>
  );
}

Component Child

import { memo } from "react";

const MemoizedTab = memo(({ title, tab, setSelectTab }) => {
  console.log("child render");
  const handleClick = (tab) => {
    setSelectTab(tab);
  };
  return <p onClick={() => handleClick(tab)}>{title}</p>;
});

export default MemoizedTab;

After the initial render the parent has a state of "a". When I click on "First Title" which sets the state to "a" again, nothing renders as expected.

When I first click on "Second Title" which sets the state to "b", I get a console log of "parent renders", which is as expected. But when I click on "Second Title" for the second time, I get a console log of "parent renders" again even though the state didn't change. Nothing logs on the third or fourth clicks. It's always the second.

This same behavior happens when I click on "Third Title" which sets the state to "c".

Why does my parent re-render on the second click after state transitions?

Codesandbox Link

like image 489
dev_el Avatar asked Aug 24 '21 01:08

dev_el


People also ask

How do you stop a component from rendering even after state has been updated?

By default, when we call this method, the component re-renders once it receives new props, even though the props have not changed. To prevent the render method from being called, set the return to false, which cancels the render. This method gets called before the component gets rendered.

Does React Rerender if state doesn't change?

If the value doesn't change, React will not trigger a re-render. This hook internally runs an interval every 500 milliseconds which is absolutely scary because inside we are always calling setDay . In these cases, React doesn't trigger a re-render because the state did not change.

Do components re-render when state changes?

React components automatically re-render whenever there is a change in their state or props. A simple update of the state, from anywhere in the code, causes all the User Interface (UI) elements to be re-rendered automatically.

How can you prevent re rendering a total component if it hasn't changed?

Memoization using useMemo() and UseCallback() Hooks Memoization enables your code to re-render components only if there's a change in the props. With this technique, developers can avoid unnecessary renderings and reduce the computational load in applications.

When ever the state is changed it should re render the component?

When ever the state is changed it should re render the componenet . In my case it is not re rendering component. When ever a component is re rendered this.fetchQuote inside ComponetDidMount should be called.

How do I force a React component to rerender?

React components re-render on their own whenever there are some changes in their props or state. Simply updating the state, from a random place in the code, causes the User Interface (UI) elements that get re-rendered automatically. In class components, you have the option to call force update to force a rerender.

Is it possible to re-render tocompare with settocompare?

However when update the toCompare with setToCompare as in the below function - the re-render won't fire. And moving it to a different component didn't work either.

Does handlequote function change state using setState?

My point is handleQuote function does change state using setState to when a state is changed it should re render the Component. But it is not doing the same !!! I have used stateless functional component that is why i have not used this. I m changing my isLoaded response to false or true in fetchQuote func I don t think React works that way.


Video Answer


2 Answers

React is not actually re-rendering your component. You can verify this by moving the console.log inside the componentDidUpdate hook.

useEffect(()=>{
   console.log('Parent re-rendered!')
})

This console.log won't get logged by the second time you click on any of the tabs.

While the console.log in your provided example does get printed, React eventually bails out of the state update. This is actually an expected behaviour in React. The following extract is from the docs:

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.

like image 64
Juan Chaher Avatar answered Oct 23 '22 12:10

Juan Chaher


This is an interesting one - I did some digging and found the following related React issues:

  1. Hooks: Calling setState with the SAME value multiple times, evaluates the component function up-to 2 times

    • This is the same issue that you are describing, from a comment there by Sebastian Markbåge it appears React requires the additional render to confirm that the two versions are the same:

      This is a known quirk due to the implementation details of concurrency in React. We don't cheaply know which of two versions is currently committed. When this ambiguity happens we have to over render once and after that we know that both versions are the same and it doesn't matter.

  2. useState not bailing out when state does not change

    • This describes the same underlying concept - setting state to the same value may result in the functional component to running again. The key takeaway from them is the last comment from Dan Abramov on the second issue:

      React does not offer a strong guarantee about when it invokes render functions. Render functions are assumed to be pure and there should be absolutely no difference for correctness regardless of how many times they're called. If calling it an extra time causes a bug, it's a problem with the code that needs to be fixed.

      From the performance point of view, the cost of re-running a single function is usually negligible. If it's not, you should add some useMemos to the parts that are expensive. React does guarantee that if a bailout happens, it will not go updating child components. Even though it might in some cases re-run the component function itself.

So React doesn't gaurntee when it will invoke the functional component, and in this case it appears to run it the additional time checking that the result is the same.

The good news is that although it runs the function an additional time, from what I can tell it doesn't appear to actually rerender on the second invocation - if you use the React profiler, looking at the Flamegraph you can see the first time App renders due to the hook change: enter image description here

but on the second click, App does not actually rerender: enter image description here

like image 1
smashed-potatoes Avatar answered Oct 23 '22 12:10

smashed-potatoes