Skip to content

Conversation

sebmarkbage
Copy link
Collaborator

Found this bug while working on Activity. There's a weird edge case when a dehydrated Suspense boundary is a direct child of another Suspense boundary which is hydrated but then it resuspends without forcing the inner one to hydrate/delete.

It used to just leave that in place because hiding/unhiding didn't deal with dehydrated fragments.

Not sure this is really worth fixing.

@github-actions github-actions bot added the React Core Team Opened by a member of the React Core Team label Apr 15, 2025

expect(ref.current).toBe(null);
expect(span.style.display).toBe('none');
expect(textNode.nodeValue).toBe('');
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

What used to happen is that these weren't hidden. They remained visible.

}

function App({text, resuspend}) {
const memoized = React.useMemo(() => <Component text={text} />, [text]);
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Memoization is important because otherwise the change in props due to the update can force a hydration.

if (node.nodeType === ELEMENT_NODE) {
const instance = ((node: any): HTMLElement & {_stashedDisplay?: string});
if (isHidden) {
instance._stashedDisplay = instance.style.display;
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We don't have the original props here yet since we haven't hydrated so we don't know what value to restore it to. So we stash the old value on an expando when we hide it. This is unfortunate but also it's just an edge case.

@react-sizebot
Copy link

Comparing: 539bbdb...ae9c0fd

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 = 1.83 kB 1.83 kB
oss-stable/react-dom/cjs/react-dom-client.production.js +0.27% 516.18 kB 517.59 kB +0.24% 91.88 kB 92.10 kB
oss-experimental/react-dom/cjs/react-dom.production.js = 6.69 kB 6.69 kB = 1.83 kB 1.83 kB
oss-experimental/react-dom/cjs/react-dom-client.production.js +0.32% 621.49 kB 623.45 kB +0.24% 110.02 kB 110.28 kB
facebook-www/ReactDOM-prod.classic.js +0.31% 654.79 kB 656.80 kB +0.21% 115.57 kB 115.81 kB
facebook-www/ReactDOM-prod.modern.js +0.31% 645.07 kB 647.08 kB +0.21% 114.04 kB 114.28 kB

Significant size changes

Includes any change greater than 0.2%:

Expand to show
Name +/- Base Current +/- gzip Base gzip Current gzip
facebook-react-native/react-dom/cjs/ReactDOMClient-profiling.js +0.32% 567.27 kB 569.08 kB +0.25% 99.68 kB 99.92 kB
oss-experimental/react-dom/cjs/react-dom-client.production.js +0.32% 621.49 kB 623.45 kB +0.24% 110.02 kB 110.28 kB
facebook-react-native/react-dom/cjs/ReactDOMProfiling-profiling.js +0.31% 573.21 kB 575.02 kB +0.25% 100.83 kB 101.08 kB
facebook-www/ReactDOM-prod.modern.js +0.31% 645.07 kB 647.08 kB +0.21% 114.04 kB 114.28 kB
oss-stable-semver/react-dom/cjs/react-dom-profiling.profiling.js +0.31% 548.65 kB 550.37 kB +0.26% 96.72 kB 96.96 kB
oss-stable/react-dom/cjs/react-dom-profiling.profiling.js +0.31% 548.78 kB 550.49 kB +0.26% 96.74 kB 96.99 kB
oss-experimental/react-dom/cjs/react-dom-unstable_testing.production.js +0.31% 635.90 kB 637.86 kB +0.24% 113.58 kB 113.85 kB
facebook-www/ReactDOM-prod.classic.js +0.31% 654.79 kB 656.80 kB +0.21% 115.57 kB 115.81 kB
facebook-www/ReactDOMTesting-prod.modern.js +0.31% 659.47 kB 661.48 kB +0.21% 117.64 kB 117.89 kB
facebook-www/ReactDOMTesting-prod.classic.js +0.30% 669.19 kB 671.20 kB +0.20% 119.25 kB 119.49 kB
facebook-react-native/react-dom/cjs/ReactDOMClient-dev.js +0.30% 989.88 kB 992.80 kB +0.18% 166.45 kB 166.76 kB
facebook-react-native/react-dom/cjs/ReactDOMProfiling-dev.js +0.29% 1,006.20 kB 1,009.13 kB +0.18% 169.27 kB 169.58 kB
oss-stable-semver/react-dom/cjs/react-dom-client.development.js +0.28% 956.54 kB 959.22 kB +0.17% 161.16 kB 161.45 kB
oss-stable/react-dom/cjs/react-dom-client.development.js +0.28% 956.66 kB 959.35 kB +0.17% 161.19 kB 161.47 kB
oss-stable-semver/react-dom/cjs/react-dom-profiling.development.js +0.28% 972.98 kB 975.66 kB +0.17% 164.01 kB 164.29 kB
oss-stable/react-dom/cjs/react-dom-profiling.development.js +0.28% 973.10 kB 975.79 kB +0.17% 164.04 kB 164.32 kB
oss-stable-semver/react-dom/cjs/react-dom-client.production.js +0.27% 516.05 kB 517.47 kB +0.25% 91.85 kB 92.08 kB
oss-stable/react-dom/cjs/react-dom-client.production.js +0.27% 516.18 kB 517.59 kB +0.24% 91.88 kB 92.10 kB
facebook-react-native/react-dom/cjs/ReactDOMClient-prod.js +0.26% 540.23 kB 541.65 kB +0.23% 95.76 kB 95.98 kB
facebook-react-native/react-dom/cjs/ReactDOMProfiling-prod.js +0.26% 545.74 kB 547.16 kB +0.23% 96.84 kB 97.06 kB
react-native/implementations/ReactNativeRenderer-dev.fb.js +0.24% 668.07 kB 669.66 kB +0.09% 109.01 kB 109.12 kB
oss-experimental/react-dom/cjs/react-dom-client.development.js +0.20% 1,129.76 kB 1,132.08 kB +0.13% 188.57 kB 188.82 kB
oss-experimental/react-dom/cjs/react-dom-profiling.profiling.js +0.20% 686.20 kB 687.60 kB +0.19% 119.80 kB 120.03 kB
oss-experimental/react-dom/cjs/react-dom-profiling.development.js +0.20% 1,146.16 kB 1,148.47 kB +0.12% 191.41 kB 191.65 kB
oss-experimental/react-dom/cjs/react-dom-unstable_testing.development.js +0.20% 1,146.31 kB 1,148.62 kB +0.12% 192.26 kB 192.50 kB

Generated by 🚫 dangerJS against ae9c0fd

@sebmarkbage
Copy link
Collaborator Author

A possible alternative would be maybe to try to clear the boundary as if we're unable to hydrate it but we're not actually unable to hydrate content that was just temporarily hidden even though we might be unable to before it unsuspends.

@sebmarkbage
Copy link
Collaborator Author

I think this is probably the right semantics if you also account for having to do a view transition on the dehydrated content.

Copy link
Member

@rickhanlonii rickhanlonii left a comment

Choose a reason for hiding this comment

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

sgtm but up to you if it's worth fixing

@sebmarkbage sebmarkbage merged commit ebf7318 into facebook:main Apr 22, 2025
242 checks passed
github-actions bot pushed a commit that referenced this pull request Apr 22, 2025
…uspend (#32900)

Found this bug while working on Activity. There's a weird edge case when
a dehydrated Suspense boundary is a direct child of another Suspense
boundary which is hydrated but then it resuspends without forcing the
inner one to hydrate/delete.

It used to just leave that in place because hiding/unhiding didn't deal
with dehydrated fragments.

Not sure this is really worth fixing.

DiffTrain build for [ebf7318](ebf7318)
github-actions bot pushed a commit that referenced this pull request Apr 22, 2025
…uspend (#32900)

Found this bug while working on Activity. There's a weird edge case when
a dehydrated Suspense boundary is a direct child of another Suspense
boundary which is hydrated but then it resuspends without forcing the
inner one to hydrate/delete.

It used to just leave that in place because hiding/unhiding didn't deal
with dehydrated fragments.

Not sure this is really worth fixing.

DiffTrain build for [ebf7318](ebf7318)
sebmarkbage added a commit that referenced this pull request Apr 22, 2025
Stacked on #32851 and #32900.

This implements the equivalent Configs for ActivityInstance as we have
for SuspenseInstance. These can be implemented as comments but they
don't have to be and can be implemented differently in the renderer.

This seems like a lot duplication but it's actually ends mostly just
calling the same methods underneath and the wrappers compiles out.

This doesn't leave the Activity dehydrated yet. It just hydrates into it
immediately.
github-actions bot pushed a commit that referenced this pull request Apr 22, 2025
Stacked on #32851 and #32900.

This implements the equivalent Configs for ActivityInstance as we have
for SuspenseInstance. These can be implemented as comments but they
don't have to be and can be implemented differently in the renderer.

This seems like a lot duplication but it's actually ends mostly just
calling the same methods underneath and the wrappers compiles out.

This doesn't leave the Activity dehydrated yet. It just hydrates into it
immediately.

DiffTrain build for [17f88c8](17f88c8)
github-actions bot pushed a commit that referenced this pull request Apr 22, 2025
Stacked on #32851 and #32900.

This implements the equivalent Configs for ActivityInstance as we have
for SuspenseInstance. These can be implemented as comments but they
don't have to be and can be implemented differently in the renderer.

This seems like a lot duplication but it's actually ends mostly just
calling the same methods underneath and the wrappers compiles out.

This doesn't leave the Activity dehydrated yet. It just hydrates into it
immediately.

DiffTrain build for [17f88c8](17f88c8)
github-actions bot pushed a commit to code/lib-react that referenced this pull request Apr 23, 2025
Stacked on facebook#32851 and facebook#32900.

This implements the equivalent Configs for ActivityInstance as we have
for SuspenseInstance. These can be implemented as comments but they
don't have to be and can be implemented differently in the renderer.

This seems like a lot duplication but it's actually ends mostly just
calling the same methods underneath and the wrappers compiles out.

This doesn't leave the Activity dehydrated yet. It just hydrates into it
immediately.

DiffTrain build for [17f88c8](facebook@17f88c8)
github-actions bot pushed a commit to code/lib-react that referenced this pull request Apr 23, 2025
Stacked on facebook#32851 and facebook#32900.

This implements the equivalent Configs for ActivityInstance as we have
for SuspenseInstance. These can be implemented as comments but they
don't have to be and can be implemented differently in the renderer.

This seems like a lot duplication but it's actually ends mostly just
calling the same methods underneath and the wrappers compiles out.

This doesn't leave the Activity dehydrated yet. It just hydrates into it
immediately.

DiffTrain build for [17f88c8](facebook@17f88c8)
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.

4 participants