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

Destroy insertion effects when deleting previously hidden subtrees #26843

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

Andarist
Copy link
Contributor

Summary

This PR fixes the lack of symmetry between creating and destroying insertion effects under certain circumstances. It could happen that the insertion effect was not destroyed if its owner component got hidden first as a result of re-suspending and if another subtree ended up being rendered.

How did you test this change?

  1. added test case
  2. tested it manually in the app

fixes #26670

cc @gaearon

@react-sizebot
Copy link

react-sizebot commented May 24, 2023

Comparing: 4b877b6...905106c

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.min.js = 164.23 kB 164.22 kB = 51.77 kB 51.77 kB
oss-experimental/react-dom/cjs/react-dom.production.min.js = 171.59 kB 171.59 kB +0.01% 53.98 kB 53.99 kB
facebook-www/ReactDOM-prod.classic.js = 570.55 kB 570.54 kB = 100.66 kB 100.66 kB
facebook-www/ReactDOM-prod.modern.js = 554.29 kB 554.28 kB = 97.84 kB 97.84 kB

Significant size changes

Includes any change greater than 0.2%:

(No significant changes)

Generated by 🚫 dangerJS against 905106c

destroy,
);
} else if (
!offscreenSubtreeWasHidden &&
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Effectively I just moved the !offscreenSubtreeWasHidden check here which caused the whole block to be dedented.

  1. this code is responsible for destroying insertion and layout effects (passive effects are destroyed in commitPassiveUnmountOnFiber)
  2. layout effects are guaranteed to be already destroyed when offscreenSubtreeWasHidden because they are destroyed by disappearLayoutEffects immediately~ when the tree gets hidden. We can't destroy them here unconditionally because that would sometimes destroy them twice
  3. and yet we need to destroy insertion effects here since they are meant to stay "connected" when the tree gets hidden (that's what @gaearon suggested to me as the fix for this issue). That's why this loop through updateQueue is not completely avoidable here right now.

'Text:Function destroy layout',
'ClassText:Class componentWillUnmount',
'Text:Function destroy passive',
]);
});
});

describe('insertion effects within a tree that re-suspends in an update', () => {
// @gate enableLegacyCache
it('should be destroyed in the deleted tree', async () => {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

this is the only new test here in this PR

@@ -148,6 +148,12 @@ describe('ReactSuspenseEffectsSemantics', () => {

function Text({children = null, text}) {
Scheduler.log(`Text:${text} render`);
React.useInsertionEffect(() => {
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 made sense to me to add useInsertionEffect to all of those helper components that are used throughout this test suite.

It increases the coverage and might surface some subtle bugs in the future. I think that this matches the spirit of this file because tests that are explicitly concerned with layout effects behavior are also tracking passive effects (since they rely on those helper components). So I didn't see a reason why insertion effects should be skipped from here.

If you don't like the change - let me know and I can easily remove that and only leave the targeted test case for the fixed issue.

'AsyncText:Two render',
'Text:Fallback destroy insertion',
'Text:Fallback destroy layout',
'Text:One destroy insertion',
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This one position is what is missing without the fix.

I'm not completely sure how I feel about insertion effects staying connected in hidden subtrees. I think that goes against my intuition as I always thought about them as a layout effect variant. It made sense to me because their introduction is directly related to the needs of CSS-in-JS libraries.

We have used useInsertionEffect in Emotion's <Global/> (a component responsible for injecting global styles) and it seems that if those are the desired semantics for useInsertionEffect then we should reconsider that. We can't fully rely on the destroy callback being called at the right time here:
https://github.com/emotion-js/emotion/blob/1135f8e9d97ea711eb483368313afdfe7b176845/packages/react/src/global.js#L112-L114

I prepared a demo to showcase the problem with this patch applied to it (couldn't use codesandbox's per-commit builds since CSB is 431-ing me when I try to save a sandbox 😢):
https://github.com/Andarist/use-insertion-effect-stays-connected-when-hidden-demo/tree/e11c278fd69c947764d4d5c14399326bff6cdca2

What happens here:

  1. Page1 is being loaded (white background since we render fallback)
  2. Page1 loads and the background becomes red through the usage of Emotion's <Global/>
  3. we click the link and navigate to Page2, since it's a lazy component we display the fallback but the background stays red while the fallback is being rendered
  4. background finally switches to white when Page2 loads

I understand that this might not be how you have wanted us to use this but I'm not sure if this effect here is desirable for us and our users. If insertion effects are meant to be connected in hidden trees then I'll likely have to consider refactoring the <Global/> component.

@Andarist Andarist force-pushed the fix/insertion-effects-unmount branch from 6f57d62 to 905106c Compare May 24, 2023 13:03
@gaearon gaearon requested review from sammy-SC and acdlite May 24, 2023 15:32
@Andarist
Copy link
Contributor Author

@sammy-SC @acdlite friendly 🏓

@Andarist
Copy link
Contributor Author

@sammy-SC @acdlite @sebmarkbage @rickhanlonii friendly 🏓

Copy link

github-actions bot commented Apr 9, 2024

This pull request has been automatically marked as stale. If this pull request is still relevant, please leave any comment (for example, "bump"), and we'll keep it open. We are sorry that we haven't been able to prioritize reviewing it yet. Your contribution is very much appreciated.

@github-actions github-actions bot added the Resolution: Stale Automatically closed due to inactivity label Apr 9, 2024
@Andarist
Copy link
Contributor Author

Andarist commented Apr 9, 2024

bump

@github-actions github-actions bot removed the Resolution: Stale Automatically closed due to inactivity label Apr 10, 2024
Copy link

github-actions bot commented Jul 9, 2024

This pull request has been automatically marked as stale. If this pull request is still relevant, please leave any comment (for example, "bump"), and we'll keep it open. We are sorry that we haven't been able to prioritize reviewing it yet. Your contribution is very much appreciated.

@github-actions github-actions bot added the Resolution: Stale Automatically closed due to inactivity label Jul 9, 2024
@Andarist
Copy link
Contributor Author

Andarist commented Jul 9, 2024

bump

@github-actions github-actions bot removed the Resolution: Stale Automatically closed due to inactivity label Jul 9, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
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
3 participants