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

Call cleanup of insertion effects when hidden #30954

Merged
merged 7 commits into from
Sep 13, 2024

Conversation

kassens
Copy link
Member

@kassens kassens commented Sep 12, 2024

Insertion effects do not unmount when a subtree is removed while offscreen.

Current behavior for an insertion effect is if the component goes

  • visible -> removed: calls insertion effect cleanup
  • visible -> offscreen -> removed: insertion effect cleanup is never called

This makes it so we always call insertion effect cleanup when removing the component.

Likely also fixes #26670

Copy link

vercel bot commented Sep 12, 2024

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
react-compiler-playground ✅ Ready (Inspect) Visit Preview 💬 Add feedback Sep 13, 2024 6:42pm

@facebook-github-bot facebook-github-bot added CLA Signed React Core Team Opened by a member of the React Core Team labels Sep 12, 2024
@react-sizebot
Copy link

react-sizebot commented Sep 12, 2024

Comparing: 94e4aca...a99a92c

Critical size changes

Includes critical production bundles, as well as any change greater than 2%:

Name +/- Base Current +/- gzip Base gzip Current gzip
oss-stable/react-dom/cjs/react-dom.production.js = 6.68 kB 6.68 kB +0.05% 1.82 kB 1.82 kB
oss-stable/react-dom/cjs/react-dom-client.production.js = 508.72 kB 508.72 kB = 90.98 kB 90.98 kB
oss-experimental/react-dom/cjs/react-dom.production.js = 6.69 kB 6.69 kB +0.11% 1.83 kB 1.83 kB
oss-experimental/react-dom/cjs/react-dom-client.production.js = 513.65 kB 513.65 kB = 91.70 kB 91.70 kB
facebook-www/ReactDOM-prod.classic.js +0.24% 602.81 kB 604.26 kB +0.13% 106.63 kB 106.77 kB
facebook-www/ReactDOM-prod.modern.js +0.25% 579.08 kB 580.53 kB +0.15% 102.76 kB 102.91 kB

Significant size changes

Includes any change greater than 0.2%:

Expand to show
Name +/- Base Current +/- gzip Base gzip Current gzip
react-native/implementations/ReactNativeRenderer-profiling.fb.js +0.46% 412.28 kB 414.17 kB +0.25% 71.66 kB 71.84 kB
react-native/implementations/ReactFabric-profiling.fb.js +0.44% 406.47 kB 408.25 kB +0.19% 70.52 kB 70.66 kB
facebook-www/ReactART-prod.modern.js +0.38% 352.84 kB 354.17 kB +0.27% 59.84 kB 60.00 kB
facebook-www/ReactART-dev.modern.js +0.36% 633.46 kB 635.76 kB +0.18% 101.58 kB 101.77 kB
facebook-www/ReactART-prod.classic.js +0.36% 369.87 kB 371.19 kB +0.25% 62.60 kB 62.76 kB
facebook-www/ReactART-dev.classic.js +0.35% 655.94 kB 658.24 kB +0.17% 105.25 kB 105.43 kB
facebook-www/ReactDOM-profiling.modern.js +0.35% 608.08 kB 610.20 kB +0.17% 107.06 kB 107.25 kB
react-native/implementations/ReactNativeRenderer-prod.fb.js +0.34% 385.18 kB 386.48 kB +0.23% 67.38 kB 67.54 kB
facebook-www/ReactDOM-profiling.classic.js +0.34% 632.46 kB 634.58 kB +0.15% 111.07 kB 111.24 kB
react-native/implementations/ReactNativeRenderer-dev.fb.js +0.33% 668.02 kB 670.21 kB +0.17% 109.58 kB 109.77 kB
facebook-www/ReactReconciler-prod.modern.js +0.32% 443.87 kB 445.31 kB +0.15% 72.17 kB 72.28 kB
react-native/implementations/ReactFabric-prod.fb.js +0.32% 379.40 kB 380.61 kB +0.19% 66.15 kB 66.28 kB
facebook-www/ReactReconciler-dev.modern.js +0.32% 718.54 kB 720.81 kB +0.15% 114.43 kB 114.60 kB
facebook-www/ReactReconciler-prod.classic.js +0.31% 462.66 kB 464.10 kB +0.15% 74.99 kB 75.10 kB
facebook-www/ReactReconciler-dev.classic.js +0.31% 742.42 kB 744.70 kB +0.15% 118.49 kB 118.67 kB
react-native/implementations/ReactFabric-dev.fb.js +0.30% 660.60 kB 662.60 kB +0.18% 108.10 kB 108.29 kB
facebook-www/ReactDOM-prod.modern.js +0.25% 579.08 kB 580.53 kB +0.15% 102.76 kB 102.91 kB
facebook-www/ReactDOMTesting-prod.modern.js +0.24% 593.81 kB 595.26 kB +0.14% 106.46 kB 106.61 kB
facebook-www/ReactDOM-prod.classic.js +0.24% 602.81 kB 604.26 kB +0.13% 106.63 kB 106.77 kB
facebook-www/ReactDOMTesting-prod.classic.js +0.23% 617.55 kB 619.00 kB +0.14% 110.32 kB 110.48 kB
facebook-www/ReactDOM-dev.modern.js +0.22% 1,052.01 kB 1,054.28 kB +0.11% 176.46 kB 176.65 kB
facebook-www/ReactDOMTesting-dev.modern.js +0.21% 1,068.91 kB 1,071.19 kB +0.11% 180.34 kB 180.53 kB
facebook-www/ReactDOM-dev.classic.js +0.21% 1,087.89 kB 1,090.17 kB +0.10% 182.43 kB 182.60 kB
facebook-www/ReactDOMTesting-dev.classic.js +0.21% 1,104.80 kB 1,107.07 kB +0.09% 186.39 kB 186.57 kB

Generated by 🚫 dangerJS against 32fb3eb

Insertion effects do not unmount when a subtree goes offscreen, this means we still have to traverse the subtree in that case.

Likely also fixes facebook#26670
@sebmarkbage
Copy link
Collaborator

sebmarkbage commented Sep 13, 2024

This is intentional. We should not cleanup insertion effects when hidden. Additionally they should actually run early inside a hidden tree if it commits in the hidden state. That's because otherwise we cannot perform layout calculation warm ups of the hidden tree.

(Insertion effects is really just code for "insert css-in-js style tags". Really it should be deleted eventually once all those libraries have moved to Float.)

Copy link
Collaborator

@sebmarkbage sebmarkbage left a comment

Choose a reason for hiding this comment

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

^

@sebmarkbage
Copy link
Collaborator

Another example is portals. useInsertionEffect can be used to insert a hidden portal early and have it perform its layout calculations and possibly even prerendering. Then useLayoutEffect should reveal it. So for a typical mount it gets inserted as hidden then immediately revealed. Then the useLayoutEffect unmounts it can be hidden but still in the DOM. Allowing the layout state and DOM state to be preserved just like a non-portal would.

@josephsavona
Copy link
Contributor

@sebmarkbage Ah I think there's a misunderstanding (@kassens it might be worth rephrasing the description a bit). What we've observed is:

  1. component has an insertion effect, which runs when the component mounts
  2. component goes hidden (eg due to suspending) and the effect does not unmount (correct)
  3. component unmounts, but the effect cleanup never runs (bug).

As you noted it's expected that it shouldn't unmount the effect in (2). But we would expect it to unmount in (3).

@kassens
Copy link
Member Author

kassens commented Sep 13, 2024

(sorry for the imprecise description, I'll update it)

This does not change when they're mounted, just ensures they're unmounted if a hidden subtree is unmounted.

Current behavior for an insertion effect is if the component goes

  • visible -> removed: calls insertion effect cleanup
  • visible -> hidden -> removed: insertion effect cleanup is never called

This change is supposed to make it consistently call insertion effect cleanup upon removing the component (not when just offscreen), even if going through hidden first.

The other option for consistency would be to say insertion effects never clean up, but that's a bigger behavior change.

@kassens kassens requested a review from sebmarkbage September 13, 2024 16:49
@sebmarkbage
Copy link
Collaborator

Oh I see

@rickhanlonii
Copy link
Member

@sebmarkbage I added more tests so it's more clear which unmounts are actually added, and adds more coverage for the existing behavior. Does this make sense? It should only unmount the insertion effects when the tree is deleted, but still not when it it's only hidden.

@kassens I also added the error if you call setState in useInsertionEffect unmount for this case.

Copy link
Contributor

@josephsavona josephsavona left a comment

Choose a reason for hiding this comment

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

Looks good, just one nit

packages/react-reconciler/src/__tests__/Activity-test.js Outdated Show resolved Hide resolved
@rickhanlonii rickhanlonii merged commit d3d4d3a into facebook:main Sep 13, 2024
184 checks passed
github-actions bot pushed a commit that referenced this pull request Sep 13, 2024
Insertion effects do not unmount when a subtree is removed while
offscreen.

Current behavior for an insertion effect is if the component goes

- *visible -> removed:* calls insertion effect cleanup
- *visible -> offscreen -> removed:* insertion effect cleanup is never
called

This makes it so we always call insertion effect cleanup when removing
the component.

Likely also fixes #26670

---------

Co-authored-by: Rick Hanlon <rickhanlonii@fb.com>

DiffTrain build for commit d3d4d3a.
github-actions bot pushed a commit that referenced this pull request Sep 13, 2024
Insertion effects do not unmount when a subtree is removed while
offscreen.

Current behavior for an insertion effect is if the component goes

- *visible -> removed:* calls insertion effect cleanup
- *visible -> offscreen -> removed:* insertion effect cleanup is never
called

This makes it so we always call insertion effect cleanup when removing
the component.

Likely also fixes #26670

---------

Co-authored-by: Rick Hanlon <rickhanlonii@fb.com>

DiffTrain build for [d3d4d3a](d3d4d3a)
@kassens kassens deleted the pr30954 branch September 16, 2024 22:24
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.

Bug: useInsertionEffect() cleanup function does not fire if a component is wrapped in React.lazy
6 participants