Comments (7)
In React, when you update the state using
useState
, React doesn't immediately update the state and re-render the component. Instead, it schedules the state update and re-rendering to occur asynchronously. This means that when you callsetCount(1)
in your first scenario orsetCount("2")
in your second scenario, React doesn't update the state and re-render the component immediately.React batches state updates for performance reasons. When multiple
setState
calls are made within the same synchronous event, React will batch them together and perform a single re-render at the end of the event. This is why you're seeing unexpected behavior in your console logs.In your first scenario, when you click the button, React schedules the state update to 1, but before it re-renders the component, it logs the current count value, which is still 0. Then it re-renders the component with the updated count value of 1.
In your second scenario, similarly, React schedules the state update to "2" and logs the current count value, which is still "1" before re-rendering the component. Then it re-renders the component with the updated count value of "2".
This behavior is expected in React due to its asynchronous nature of state updates and re-renders. If you want to perform any action after the state has been updated, you should use
useEffect
hook with appropriate dependencies.But you can still try memoizing the state. However as far as I know this is expected behavior :)
const memoizedCount = useMemo(() => count, [count]);
But I dont understand the async behaviour you mentioned. I know applying next rerender with new changes somehow asynchronus because of optimization. But I think it is not related with this. Because if you click once then wait for a minute, rerender will already be done and whereever the state value inside react closure should already be updated with next value until next click. Lets put some time between two clicks. React still rerender with same immutable value. Are you sure about your explanation? Other hand, I couldnt find any deep dive explanation about state closure implementation inside react. If you know implementation detail, please let me know.
from react.
In React, when you update the state using useState
, React doesn't immediately update the state and re-render the component. Instead, it schedules the state update and re-rendering to occur asynchronously. This means that when you call setCount(1)
in your first scenario or setCount("2")
in your second scenario, React doesn't update the state and re-render the component immediately.
React batches state updates for performance reasons. When multiple setState
calls are made within the same synchronous event, React will batch them together and perform a single re-render at the end of the event. This is why you're seeing unexpected behavior in your console logs.
In your first scenario, when you click the button, React schedules the state update to 1, but before it re-renders the component, it logs the current count value, which is still 0. Then it re-renders the component with the updated count value of 1.
In your second scenario, similarly, React schedules the state update to "2" and logs the current count value, which is still "1" before re-rendering the component. Then it re-renders the component with the updated count value of "2".
This behavior is expected in React due to its asynchronous nature of state updates and re-renders. If you want to perform any action after the state has been updated, you should use useEffect
hook with appropriate dependencies.
But you can still try memoizing the state. However as far as I know this is expected behavior :)
const memoizedCount = useMemo(() => count, [count]);
from react.
@leadq maybe you can see this thread that sophiebits answer your question.
from react.
@leadq maybe you can see this thread that sophiebits answer your question.
I've checked the thread. But, no one explained the extra rerender at the end of that thread
from react.
@leadq As far as I know, React canโt guess the output of render() wonโt change, even if you update state has the same value, it has to render() again and compare the results with the previous render(). This is the conclusion. React optimize this strategy called "eagerState" to make sure it will not re-render.
So how is the "eagerState" work?
In React, state is stored in the fiber tree, and react use double cache mechanism, there are at least two fiber trees in existence. When we mark a component A as needing an update, the "update exists" information is stored in two fiber nodes corresponding to component A in its respective fiber trees. When the first update occurs and is completed after a click, the "update exists" information is erased from one of the fibers, but it remains in the other related fiber. So, the next time component A is updated, it will still render because the "update exists" information remains in one of the fibers. However, during subsequent updates, both fibers related to component A do not have updates, allowing component A to hit eagerState and avoid rendering.
If you don't want this behavior. Just simply prevent by yourself. ๐
const handleClick = () => {
if (count === prevCount) return
setCount(1)
}
If you want more detail, you need to study the react source code by yourself.๐
Hope this can help. ;)
from react.
@leadq As far as I know, React canโt guess the output of render() wonโt change, even if you update state has the same value, it has to render() again and compare the results with the previous render(). This is the conclusion. React optimize this strategy called "eagerState" to make sure it will not re-render.
So how is the "eagerState" work?
In React, state is stored in the fiber tree, and react use double cache mechanism, there are at least two fiber trees in existence. When we mark a component A as needing an update, the "update exists" information is stored in two fiber nodes corresponding to component A in its respective fiber trees. When the first update occurs and is completed after a click, the "update exists" information is erased from one of the fibers, but it remains in the other related fiber. So, the next time component A is updated, it will still render because the "update exists" information remains in one of the fibers. However, during subsequent updates, both fibers related to component A do not have updates, allowing component A to hit eagerState and avoid rendering.
If you don't want this behavior. Just simply prevent by yourself. ๐
const handleClick = () => { if (count === prevCount) return setCount(1) }If you want more detail, you need to study the react source code by yourself.๐
Hope this can help. ;)
From my perspective, this library (which, by the way, is a great asset to have in our lives) offers some APIs for us to use. As an end user, I view this library as a black boxโI expect it to function reliably and consistently as described, without needing to understand its internal workings, much like any API consumer would. According to React's documentation, setState performs certain optimizations, and if the next value remains the same after being checked by a method like Object.is, it does not re-render the component. However, the official docs also mention that there might be cases where it could still cause a re-render. This seems to be a buggy behavior, but the docs mention this only briefly, allowing us to categorize this issue as "some cases". However, there's no clarification on what these "some cases" are. If it requires looking into the source code to understand, this is indeed a significant challenge for us.
Certainly, having an in-depth knowledge of the source code and understanding how it works would be ideal. I do find myself diving into the library out of curiosity from time to time. However, my point is that using the phrase "some cases" in the official documentation feels rather precarious. It's very vague, and when I encounter a bug, I can't possibly know whether it falls into this "some cases" category. Therefore, there should be examples and clear limitations of these cases in the official documentation. I've opened this issue because maybe something is missed, and it would be beneficial if the maintainers could shed some light on this.
from react.
@leadq
I've reviewed the source code regarding this issue and discovered the following:
- The actual implementation of useState's setState is dispatchSetState.
react/packages/react-reconciler/src/ReactFiberHooks.js
Lines 1840 to 1846 in d779eba
- It's found that if the lanes bound to the fiber in dispatchSetState are not NoLanes (0), re-rendering occurs even if the eagerState remains the same.
- EagerState is checked, and if it's identical to the current value, it returns without re-rendering.
react/packages/react-reconciler/src/ReactFiberHooks.js
Lines 3341 to 3348 in d779eba
- If the lanes of the fiber bound to dispatchSetState are not NoLane, eagerState is not checked, leading to re-rendering even when the same value is set.
react/packages/react-reconciler/src/ReactFiberHooks.js
Lines 3318 to 3329 in d779eba
Why is it re-rendering even when the same value is set?
Regarding this issue, I've observed two crucial behaviors in React:
- When setState (dispatchSetState) is called, the bound fiber might refer to the current one (yet to be reflected on the screen) or the previous fiber (currently reflected on the screen). They alternate with each rendering.
- During re-rendering, the current fiber may have lanes at 0, but the previous fiber (contained in the current fiber's alternate) may have lanes at 2.
Reasons for these behaviors:
The fiber referred to by dispatchSetState at the time of the call might be the current fiber or the previous one
React places the previous fiber in the alternate of the current fiber with each rendering. Thus, the fiber bound to dispatchSetState alternates between the current and the previous one with each rendering. (This is my observation based on changes during rendering.)
During re-rendering, the current fiber may have lanes at 0, but the previous fiber may have lanes at 2
The lanes of a fiber become NoLanes when bailoutHooks()'s removeLanes() is executed, which occurs when the current value matches the previous value.
Thus, when there's an update, markWorkInProgressReceivedUpdate is executed,
react/packages/react-reconciler/src/ReactFiberHooks.js
Lines 1459 to 1462 in d779eba
leading to didReceiveUpdate becoming true
react/packages/react-reconciler/src/ReactFiberBeginWork.js
Lines 3451 to 3453 in d779eba
When didReceiveUpdate is false, bailoutHooks()'s removeLanes() is executed
react/packages/react-reconciler/src/ReactFiberBeginWork.js
Lines 1180 to 1183 in d779eba
Conclusion
When the fiber bound to dispatchSetState refers to the previous fiber, and the lanes of the previous fiber are at 2 (If there was an update to the value in the previous rendering), re-rendering occurs even when the same value is set for setState.
This speculation is not verified against all operations, so there might be inaccuracies.
from react.
Related Issues (20)
- Bug: HOT 1
- [React 19] Async transitions race condition handling
- Bug: React 18.3 warning says to import `act` from `react`, only `unstable_act` exists HOT 3
- Bug: "React Developer Tools" Chrome Extension unable to profile page rendering
- Bug: Source not displayed in DevTools HOT 2
- Bug:
- [React 19] useTransition()'s pending state does not go back to false (revision 94eed63c49-20240425) HOT 3
- Changelog for 18.3 is missing HOT 1
- [React 19] Removal of `ReactDOM.findDOMNode` HOT 2
- [React 19] react-test-renderer deprecation HOT 2
- E-Commerce HOT 1
- [React 19] HOT 1
- [React 19] Eslint React JSDoc support HOT 1
- [React 19] Support scoped custom element registries (i.e, react with Custom Elements being rendered in a shadow root)
- Bug [React-DOM]: Missing "bun" export field in package.json HOT 2
- Bug: Spurious warnings in react dev runtime about using "key" property in spread object
- [React 19] React warns about "fetchpriority" props HOT 9
- Bug: Uncaught TypeError: Cannot read properties of null (reading 'useMemo') [React 18.3.1] HOT 6
- Bug: Warning: React does not recognize the `fetchPriority` prop on a DOM element [React 18.3.1] HOT 2
- [React 19] react-reconciler README is missing scheduler and form hooks
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. ๐๐๐
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google โค๏ธ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from react.