Skip to content
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

Fix incorrect unmounted state update warning #18617

Merged
merged 3 commits into from
Apr 18, 2020

Conversation

bvaughn
Copy link
Contributor

@bvaughn bvaughn commented Apr 15, 2020

There was a recent report within Facebook of an invalid "can't perform a React state update on an unmounted component" warning that was eventually reduced to the fact that the Fiber being warned about was not tracked in the pendingPassiveHookEffectsUnmount array, but it's alternate was.

Unfortunately, because we detach fibers (which nulls the .alternate field) when we commit a deletion, any state updates scheduled between that point and the eventual passive effects flush won't have a way to check if there is a pending passive unmount effect scheduled for the alternate unless we also explicitly track the alternates.

This fix feels heavy handed and gross to me. Is there a better solution that I'm overlooking? Rather than tracking both sets of Fibers, this PR uses a new DEV-only effect that indicates if the Fiber has a pending passive unmount effect scheduled.

Relates to PR #18096

@facebook-github-bot facebook-github-bot added CLA Signed React Core Team Opened by a member of the React Core Team labels Apr 15, 2020
@codesandbox-ci
Copy link

codesandbox-ci bot commented Apr 15, 2020

This pull request is automatically built and testable in CodeSandbox.

To see build info of the built libraries, click here or the icon next to each commit SHA.

Latest deployment of this branch, based on commit 4537c13:

Sandbox Source
dazzling-butterfly-p7x4y Configuration

@sizebot
Copy link

sizebot commented Apr 15, 2020

Details of bundled changes.

Comparing: 58c895e...4537c13

react-dom

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
react-dom.production.min.js 0.0% 0.0% 119.68 KB 119.68 KB 37.51 KB 37.51 KB NODE_PROD
react-dom-test-utils.development.js 0.0% 0.0% 75.25 KB 75.26 KB 20.14 KB 20.15 KB UMD_DEV
react-dom-test-utils.production.min.js 0.0% 0.0% 13.23 KB 13.23 KB 4.89 KB 4.9 KB UMD_PROD
react-dom-unstable-fizz.browser.production.min.js 0.0% 🔺+0.1% 1.19 KB 1.19 KB 698 B 699 B UMD_PROD
ReactDOMForked-dev.js +0.1% 0.0% 1.03 MB 1.04 MB 236.08 KB 236.17 KB FB_WWW_DEV
react-dom-unstable-fizz.node.development.js 0.0% +0.1% 5.28 KB 5.28 KB 1.73 KB 1.73 KB NODE_DEV
react-dom.development.js 0.0% 0.0% 906.25 KB 906.3 KB 199.43 KB 199.44 KB UMD_DEV
react-dom-unstable-fizz.node.production.min.js 0.0% 🔺+0.5% 1.16 KB 1.16 KB 658 B 661 B NODE_PROD
ReactDOMTesting-dev.js +0.1% 0.0% 955.58 KB 956.15 KB 213.17 KB 213.26 KB FB_WWW_DEV
react-dom.development.js 0.0% 0.0% 862.65 KB 862.71 KB 196.91 KB 196.91 KB NODE_DEV
ReactDOM-dev.js +0.1% 0.0% 1.03 MB 1.04 MB 235.91 KB 236.01 KB FB_WWW_DEV
react-dom-test-utils.development.js 0.0% 0.0% 70.09 KB 70.1 KB 19.64 KB 19.64 KB NODE_DEV
react-dom-unstable-fizz.browser.development.js 0.0% +0.1% 4.53 KB 4.53 KB 1.57 KB 1.57 KB NODE_DEV
react-dom-test-utils.production.min.js 0.0% 0.0% 13.1 KB 13.1 KB 4.8 KB 4.8 KB NODE_PROD
react-dom-unstable-fizz.browser.production.min.js 0.0% 🔺+0.2% 1 KB 1 KB 610 B 611 B NODE_PROD
ReactTestUtils-dev.js 0.0% 0.0% 65.12 KB 65.12 KB 17.54 KB 17.55 KB FB_WWW_DEV
react-dom-unstable-native-dependencies.production.min.js 0.0% 0.0% 9.73 KB 9.73 KB 3.25 KB 3.25 KB NODE_PROD

react-art

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
react-art.development.js 0.0% 0.0% 634.46 KB 634.51 KB 133.77 KB 133.77 KB UMD_DEV
react-art.production.min.js 0.0% 0.0% 107.22 KB 107.22 KB 32.53 KB 32.53 KB UMD_PROD
react-art.development.js 0.0% 0.0% 538.74 KB 538.79 KB 116.19 KB 116.2 KB NODE_DEV
react-art.production.min.js 0.0% 0.0% 72.21 KB 72.21 KB 21.7 KB 21.7 KB NODE_PROD
ReactART-dev.js +0.1% +0.1% 612.13 KB 612.7 KB 128.72 KB 128.82 KB FB_WWW_DEV

react-test-renderer

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
react-test-renderer.development.js 0.0% 0.0% 570.98 KB 571.03 KB 118.91 KB 118.91 KB UMD_DEV
react-test-renderer.production.min.js 0.0% -0.0% 74.25 KB 74.25 KB 22.61 KB 22.61 KB UMD_PROD
react-test-renderer.development.js 0.0% 0.0% 544.42 KB 544.47 KB 117.56 KB 117.56 KB NODE_DEV
react-test-renderer.production.min.js 0.0% 0.0% 74.05 KB 74.05 KB 22.32 KB 22.32 KB NODE_PROD
ReactTestRenderer-dev.js +0.1% +0.1% 576.46 KB 577.02 KB 122.02 KB 122.12 KB FB_WWW_DEV

ReactDOM: size: 0.0%, gzip: 0.0%

Size changes (stable)

Generated by 🚫 dangerJS against 4537c13

@sizebot
Copy link

sizebot commented Apr 15, 2020

Details of bundled changes.

Comparing: 58c895e...4537c13

react-test-renderer

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
ReactTestRenderer-dev.js +0.1% +0.1% 576.47 KB 577.04 KB 122.03 KB 122.12 KB FB_WWW_DEV
react-test-renderer-shallow.development.js 0.0% 0.0% 38.84 KB 38.84 KB 9.46 KB 9.46 KB UMD_DEV
react-test-renderer.development.js 0.0% 0.0% 571 KB 571.05 KB 118.92 KB 118.92 KB UMD_DEV
react-test-renderer.development.js 0.0% 0.0% 544.44 KB 544.49 KB 117.57 KB 117.57 KB NODE_DEV
react-test-renderer.production.min.js 0.0% 0.0% 74.07 KB 74.07 KB 22.33 KB 22.33 KB NODE_PROD

react-dom

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
ReactTestUtils-dev.js 0.0% 0.0% 65.11 KB 65.12 KB 17.55 KB 17.55 KB FB_WWW_DEV
react-dom.profiling.min.js 0.0% -0.0% 128.5 KB 128.5 KB 40.16 KB 40.16 KB NODE_PROFILING
ReactDOM-dev.js +0.1% 0.0% 1.01 MB 1.01 MB 231.02 KB 231.12 KB FB_WWW_DEV
ReactDOM-profiling.js 0.0% 0.0% 445.63 KB 445.63 KB 78.21 KB 78.21 KB FB_WWW_PROFILING
react-dom-unstable-fizz.browser.production.min.js 0.0% 🔺+0.2% 1.02 KB 1.02 KB 618 B 619 B NODE_PROD
react-dom-test-utils.development.js 0.0% 0.0% 75.27 KB 75.27 KB 20.15 KB 20.15 KB UMD_DEV
ReactDOMTesting-dev.js +0.1% 0.0% 929.04 KB 929.6 KB 207.62 KB 207.72 KB FB_WWW_DEV
react-dom-test-utils.development.js 0.0% 0.0% 70.1 KB 70.11 KB 19.65 KB 19.65 KB NODE_DEV
react-dom-server.node.development.js 0.0% 0.0% 154.12 KB 154.12 KB 40.77 KB 40.77 KB NODE_DEV
react-dom-test-utils.production.min.js 0.0% 0.0% 13.12 KB 13.12 KB 4.81 KB 4.81 KB NODE_PROD
react-dom.development.js 0.0% 0.0% 939.93 KB 939.99 KB 205.88 KB 205.88 KB UMD_DEV
react-dom-unstable-native-dependencies.development.js 0.0% 0.0% 56.11 KB 56.11 KB 13.85 KB 13.85 KB UMD_DEV
react-dom.production.min.js 0.0% 0.0% 124.31 KB 124.31 KB 39.73 KB 39.73 KB UMD_PROD
ReactDOMForked-dev.js +0.1% 0.0% 1.01 MB 1.01 MB 231.18 KB 231.28 KB FB_WWW_DEV
ReactDOMServer-dev.js 0.0% -0.0% 161.56 KB 161.56 KB 41.04 KB 41.04 KB FB_WWW_DEV
react-dom.profiling.min.js 0.0% 0.0% 128.17 KB 128.17 KB 40.93 KB 40.93 KB UMD_PROFILING
react-dom.development.js 0.0% 0.0% 894.9 KB 894.95 KB 203.32 KB 203.33 KB NODE_DEV
ReactDOMForked-profiling.js 0.0% 0.0% 446.2 KB 446.2 KB 78.3 KB 78.3 KB FB_WWW_PROFILING
react-dom-unstable-fizz.node.development.js 0.0% +0.1% 5.29 KB 5.29 KB 1.73 KB 1.74 KB NODE_DEV
react-dom-unstable-native-dependencies.production.min.js 0.0% 0.0% 9.75 KB 9.75 KB 3.25 KB 3.26 KB NODE_PROD
react-dom-unstable-fizz.node.production.min.js 0.0% 🔺+0.3% 1.17 KB 1.17 KB 667 B 669 B NODE_PROD

react-art

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
react-art.development.js 0.0% 0.0% 658.59 KB 658.64 KB 138.56 KB 138.57 KB UMD_DEV
react-art.development.js 0.0% 0.0% 561.87 KB 561.92 KB 121.02 KB 121.02 KB NODE_DEV
ReactART-dev.js +0.1% +0.1% 602.11 KB 602.68 KB 126.68 KB 126.77 KB FB_WWW_DEV

Size changes (experimental)

Generated by 🚫 dangerJS against 4537c13

Copy link
Collaborator

@acdlite acdlite left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have an idea

@bvaughn
Copy link
Contributor Author

bvaughn commented Apr 17, 2020

Cool 👍 What is it?

@acdlite
Copy link
Collaborator

acdlite commented Apr 17, 2020

Instead of pushing the fiber to pendingPassiveHookEffectsUnmount and then checking with indexOf, you can set a bit on its effectTag.

You can also set that bit on the effectTag of its current fiber. As a general rule you shouldn't mutate current fibers, but effect tags are fine because it already committed.

We detach fibers (which nulls the  field) when we commit a deletion, so any state updates scheduled between that point and when we eventually flush passive effect destroys won't have a way to check if there is a pending passive unmount effect scheduled for its alternate unless we also explicitly track the alternates.
@bvaughn bvaughn force-pushed the fix-unmounted-state-update-warning branch from 84fb700 to a72eab8 Compare April 17, 2020 19:19
dquote>
dquote> Replace the separate current and alternate arrays with a new effect tag, PassiveUnmountPending, that we use to check for the case where a state update is scheduled for an unmoutned component that has a pending passive effect cleanup scheduled.
@bvaughn
Copy link
Contributor Author

bvaughn commented Apr 17, 2020

That sounds like a nicer approach, @acdlite. Back to you.

@bvaughn bvaughn force-pushed the fix-unmounted-state-update-warning branch from a72eab8 to 5223a07 Compare April 17, 2020 19:20
@@ -2310,6 +2311,15 @@ export function enqueuePendingPassiveHookEffectUnmount(
): void {
if (runAllPassiveEffectDestroysBeforeCreates) {
pendingPassiveHookEffectsUnmount.push(effect, fiber);
if (__DEV__) {
if (deferPassiveEffectCleanupDuringUnmount) {
fiber.effectTag |= PassiveUnmountPending;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it weird to only set this bit in DEV mode? It's only needed there.

Copy link
Collaborator

@gaearon gaearon Apr 17, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add Dev suffix to the flag?

@bvaughn bvaughn requested a review from acdlite April 17, 2020 19:27
Copy link
Collaborator

@acdlite acdlite left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

if (__DEV__) {
if (deferPassiveEffectCleanupDuringUnmount) {
fiber.effectTag |= PassiveUnmountPendingDev;
const alternate = fiber.alternate;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Can you pass current as an argument through schedulePassiveEffects? We try to limit the number of places we access the .alternate pointer, since one day we're hoping to remove i t.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could do that for schedulePassiveEffects() because commitLifeCycles() already has both fibers.

But we also call enqueuePendingPassiveHookEffectUnmount() from commitUnmount() which is called from commitNestedUnmounts() which traces all the through to commitMutationEffects(), so I'm still going to have to access alternate there (and add a bunch of additional params to the functions in between).

Is this still the right change?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like what we're calling "current" is inconsistent between commitWork and commitDeletion calls.

For e.g. commitWork we pass nextEffect.alternate as current:

const current = nextEffect.alternate;
commitWork(current, nextEffect);

But for commitDeletion we pass nextEffect as current:

commitDeletion(root, nextEffect, renderPriorityLevel);

Copy link
Contributor Author

@bvaughn bvaughn Apr 18, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We also call commitNestedUnmounts and commitUnmount from unmountHostComponents while traversing the tree, so we'd need to add a bunch of additional alternate access here to pass through the added param.

Since this was a "nit" and given the above considerations, I think I'm not going to make any additional change to the alternate stuff for now. Seems like it would be a net loss. We can always follow up with another PR if someone disagrees strongly.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right I guess “current” isn’t accurate, it’s just the “other fiber”

Yeah that makes sense. If we got rid of alternates, each fiber would have a shared instance object and we could put the flag on that.

@bvaughn bvaughn merged commit 263bc5d into facebook:master Apr 18, 2020
@bvaughn bvaughn deleted the fix-unmounted-state-update-warning branch April 18, 2020 19:18
@sebmarkbage
Copy link
Collaborator

sebmarkbage commented Apr 21, 2020

Turns out this is actually going to be what tips us over and would have to require us to add a whole other field since we'd run out of double effect tags like we talked about earlier. We probably need to revisit to see if we can do something less invasive. (I'll also do a pass and see if we can remove a couple of other ones.)

@bvaughn
Copy link
Contributor Author

bvaughn commented Apr 21, 2020

I'm not sure I fully understand the "whole other field" comment. I think I'm missing some context. Is there more written down somewhere or is it mostly just convos between you and Andrew?

Since this is DEV only, seems like we could use a less optimal, DEV-only strategy for this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed React Core Team Opened by a member of the React Core Team
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants