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
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.
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.
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.
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 componenet . In my case it is not re rendering component. When ever a component is re rendered this.fetchQuote inside ComponetDidMount should be called.
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.
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.
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.
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.
This is an interesting one - I did some digging and found the following related React issues:
Hooks: Calling setState with the SAME value multiple times, evaluates the component function up-to 2 times
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.
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:
but on the second click, App
does not actually rerender:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With