-
Notifications
You must be signed in to change notification settings - Fork 46.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Bug: Derived value does not update in StrictMode #18003
Comments
FWIW it works fine with the production build of react-dom, change the import in codesandbox to test it: import ReactDOM from "react-dom/cjs/react-dom.production.min"; |
When What went wrong here?
In this particular case, mutating a ref during render is the side effect. The way this plays out in practice is like so:
How can you fix it?The "quick fix" here would be to get rid of the render-phase side effect by moving your ref update to an effect: if (prevValue.current !== value && internalValue !== "") {
setInternalValue("");
}
useEffect(() => {
// It's safe to update your prev-value ref here
prevValue.current = value;
}); However, this Code Sandbox also looks like one of the derived state anti-patterns described in a blog post a while back. Maybe worth re-considering the higher level approach being used here and possibly going with a fully-controlled component instead? Why does this matter?It might not be obvious why this matters. After all- if React didn't double-render the component, it wouldn't break. That's why using the production bundle "fixes" things. The truth is that side effects like this might cause things to break even outside of strict mode. The upcoming "concurrent" rendering mode is one such case, but it's not the only one. A component might render twice between commits even in "legacy" (synchronous) rendering mode in the event of an error. (In that case, React would bubble the error up to the nearest error boundary, then re-render before committing.) |
Thank you @bvaughn for the quick reply and detailed explanation. |
Refs cannot be mutated and used to update state in the same time in rendering phase. As this is side-effect, it can produce various bugs in concurrent mode. In StrictMode Elements doesn't do transition from null to valid stripe instance. As in StrictMode React renders twice, `final` ref becomes `true`, but `ctx` state isn't changed. References: https://reactjs.org/docs/strict-mode.html#detecting-unexpected-side-effects facebook/react#18003 facebook/react#18545
Refs cannot be mutated and used to update state in the same time in rendering phase. As this is side-effect, it can produce various bugs in concurrent mode. In StrictMode Elements doesn't do transition from null to valid stripe instance. As in StrictMode React renders twice, `final` ref becomes `true`, but `ctx` state isn't changed. References: https://reactjs.org/docs/strict-mode.html#detecting-unexpected-side-effects facebook/react#18003 facebook/react#18545
Refs cannot be mutated and used to update state in the same time in rendering phase. As this is side-effect, it can produce various bugs in concurrent mode. In StrictMode Elements doesn't do transition from null to valid stripe instance. As in StrictMode React renders twice, `final` ref becomes `true`, but `ctx` state isn't changed. References: https://reactjs.org/docs/strict-mode.html#detecting-unexpected-side-effects facebook/react#18003 facebook/react#18545
Refs cannot be mutated and used to update state in the same time in rendering phase. As this is side-effect, it can produce various bugs in concurrent mode. In StrictMode Elements doesn't do transition from null to valid stripe instance. As in StrictMode React renders twice, `final` ref becomes `true`, but `ctx` state isn't changed. References: https://reactjs.org/docs/strict-mode.html#detecting-unexpected-side-effects facebook/react#18003 facebook/react#18545
@bvaughn when react renders second time intentionally due to strict mode, does it keep the value of |
Ref isn't changed between regular and strict renders. Shouldn't matter. Your example code has a side effect ( |
@bvaughn in my question if you rendered it without strict mode, you would see If yes, then I found out that by replacing Check the answer and comments. |
Reading or writing from external values during render is a side effect and is not supported in React components for reasons explained here: |
@bvaughn That's a general guideline, I see. But if you could answer more precisely, whether you agree with the explanation (which I summarized in previous comment) user ray came up with, would be nice. Because like I said we had the impression during second strict render the |
React renders twice in strict mode, without resetting anything: react/packages/react-reconciler/src/ReactFiberBeginWork.new.js Lines 971 to 1000 in 1e247ff
If it's an update, react/packages/react-reconciler/src/ReactFiberHooks.new.js Lines 1632 to 1635 in 1e247ff
If it's a mount, react/packages/react-reconciler/src/ReactFiberHooks.new.js Lines 1560 to 1630 in 1e247ff
But components should not have side effects during render, so this distinction shouldn't matter. |
React renders twice in strict mode, without resetting anything
Hm before you said
This means that, in DEV mode, React will render a component once- throw away any state updates- and then render again
Isn't throw away and resetting kind of similar? I thought it could throw away a ref too.
Ps I think you are suggesting in the example of this thread there was update, in my question it was mount.
…On Wed, Oct 13, 2021, 2:26 AM Brian Vaughn ***@***.***> wrote:
React renders twice in strict mode, without resetting anything:
https://github.com/facebook/react/blob/1e247ff892985488c6bcb87e924d7881f5c37261/packages/react-reconciler/src/ReactFiberBeginWork.new.js#L971-L1000
If it's an *update*, useRef will do *exactly the same thing*:
https://github.com/facebook/react/blob/1e247ff892985488c6bcb87e924d7881f5c37261/packages/react-reconciler/src/ReactFiberHooks.new.js#L1632-L1635
If it's a *mount*, useRef will be initialized twice:
https://github.com/facebook/react/blob/1e247ff892985488c6bcb87e924d7881f5c37261/packages/react-reconciler/src/ReactFiberHooks.new.js#L1560-L1630
But components should not have side effects during render, so this
distinction shouldn't matter.
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#18003 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ASNHTLRLX5L6O7Y6UGSGMQLUGSY2HANCNFSM4KR56ONA>
.
Triage notifications on the go with GitHub Mobile for iOS
<https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675>
or Android
<https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub>.
|
I think this is going to be my last response. I don't think this conversation is super helpful, since you seem to be ignoring the fact that the example code you posted has side-effects and so is known/expected to be broken. (This is the purpose of StrictMode double rendering, as the link I shared above explains.)
Refs are mutable values. React initializes them once and then returns the same object/wrapper every time your component renders (including strict mode). |
@bvaughn Well thanks for the effort anyway. I don't want to sound rude but I also have a feeling you didn't try to understand some of my comments above. |
I'm sorry you feel that that way, but I answered your question in my comment above (#18003 (comment)) when I said that if your component is mounting, React will reset the ref between regular and strict mode render. That's why you see a different behavior than your code that modifies a local variable (which is not managed by React and so is only true for the initial render). That being said, the reason I said this is not a very useful discussion is because the code pattern you're demonstrating is known to be broken and our documentation explicitly recommends against it (for reasons like the one we've been discussing). |
React version:
16.12.0
Steps To Reproduce
Link to code example:
https://codesandbox.io/s/elated-mendeleev-1m5tm
The current behavior
Derived value does not update in strict mode
The expected behavior
Derived value should update in strict mode
The text was updated successfully, but these errors were encountered: