Skip to content

Call cleanup of insertion effects when hidden#30954

Merged
rickhanlonii merged 7 commits into
react:mainfrom
kassens:pr30954
Sep 13, 2024
Merged

Call cleanup of insertion effects when hidden#30954
rickhanlonii merged 7 commits into
react:mainfrom
kassens:pr30954

Conversation

@kassens

@kassens kassens commented Sep 12, 2024

Copy link
Copy Markdown
Contributor

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

@vercel

vercel Bot commented Sep 12, 2024

Copy link
Copy Markdown

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

react-sizebot commented Sep 12, 2024

Copy link
Copy Markdown

Comparing: 94e4acaa1477e65cac02ba86058cde0afe4c8f1f...a99a92c7387bf6151666d678dd4491a7bc3e7eac

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 react#26670
@sebmarkbage

sebmarkbage commented Sep 13, 2024

Copy link
Copy Markdown
Contributor

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.)

@sebmarkbage sebmarkbage left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

^

@sebmarkbage

Copy link
Copy Markdown
Contributor

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
Copy Markdown
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

kassens commented Sep 13, 2024

Copy link
Copy Markdown
Contributor Author

(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
Copy Markdown
Contributor

Oh I see

@rickhanlonii

Copy link
Copy Markdown
Contributor

@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.

@josephsavona josephsavona left a comment

Copy link
Copy Markdown
Contributor

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

Comment thread packages/react-reconciler/src/__tests__/Activity-test.js Outdated
Comment thread packages/react-reconciler/src/ReactFiberCommitWork.js
@rickhanlonii rickhanlonii merged commit d3d4d3a into react:main Sep 13, 2024
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
eps1lon pushed a commit to vercel/next.js that referenced this pull request Sep 18, 2024
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