Bug: Setting the state to the same value causes a re-render.
React version: 18.3.1
Steps To Reproduce
- Set the initial state to false.
- Change the state to true.
- Set the state to true once more.
Link to code example: codesandbox
import React, { useState } from "react";
const Child = () => {
console.log("Child");
return null;
};
export default function App() {
const [state, setState] = useState(false);
console.log("App");
return (
<>
<button
onClick={() => {
setState(true);
}}
>
click
</button>
<Child />
</>
);
}
The current behavior
- Print 'App' and 'Child' on the first rendering.
- Clicking the prints 'App' and 'Child'.
- Clicking the once more prints 'App'.
The expected behavior
- Print 'App' and 'Child' on the first rendering.
- Clicking the prints 'App' and 'Child'.
- Clicking the one more time results in no reaction.
I think this issue occurred because the previous current(now alternate)'s lanes was not cleared after reconciliation.
I confirmed that when the button was clicked a second time, the lanes was not NoLanes, so it did not compare with the previous state and immediately scheduled the rendering.
https://github.com/facebook/react/blob/v18.3.1/packages/react-reconciler/src/ReactFiberHooks.new.js#L2257-L2287
Since there were no updates during the rendering of the App component, it bailed out of rendering, so the children were not re-rendered. https://github.com/facebook/react/blob/v18.3.1/packages/react-reconciler/src/ReactFiberBeginWork.new.js#L1047-L1050
However, if the lanes of the previous fiber had been cleared, such rendering would not have occurred.
I thought "If the state doesn't change, no re-rendering occurs", but I'm confused by the above result.
Is this a bug, or is there another reason for this behavior?
Hmm, for your info, this does not happen in the case of preact in the same situation (I tried.)
My Test Code with Preact
Console Input With Google Chrome
index.tsx:15Appindex.tsx:10Childindex.tsx:15Appindex.tsx:10Child
import { render, useState } from "preact/compat";
const Child = () => {
console.log("Child");
return <></>;
}
export default function App() {
const [state, setState] = useState(false);
console.log("App");
return (
<>
<button onClick={() => setState(true)}>click {state ? "true" : "false"}</button>
<Child />
</>
);
}
render(<App />, document.getElementById("render"));
So... I guess it's just a bug or not. 😂
When you call setState(true), even though the state hasn't changed (it's still true), React schedules a re-render for the App component. During this process, React compares the new state with the previous state. Since they are the same, React recognizes that there's no need to re-render the child components. However, because the App component is already in the process of rendering, the code inside the App function, including console.log("App"), still runs. React then decides to skip re-rendering the Child component to optimize performance.
I found a similar behavior and I don't understand if it's a bug.
Here is a simpler example codesandbox
Steps To Reproduce:
- Click on QQQ - the value changes, rendering happens
- Click on QQQ - the value does not change, rendering happens
- Each subsequent click on the QQQ button - the value does not change, rendering does not happen
- Next, we can repeat the same actions with another button
This issue has been automatically marked as stale. If this issue is still affecting you, please leave any comment (for example, "bump"), and we'll keep it open. We are sorry that we haven't been able to prioritize it yet. If you have any new additional information, please include it with your comment!
Closing this issue after a prolonged period of inactivity. If this issue is still present in the latest release, please create a new issue with up-to-date information. Thank you!