Skip to content

Commit de47999

Browse files
committed
Skip emitting entire hidden Activity boundary including comments from SSR
Then we handle this fully in the Activity wrapper. That's what determines whether the offscreen content is SSR:ed or not. This also optimizes by avoiding an emit commit pass that used to hydrate the hidden Activity and then client renders the hidden content.
1 parent 57dc1f2 commit de47999

3 files changed

Lines changed: 25 additions & 37 deletions

File tree

packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3746,11 +3746,7 @@ describe('ReactDOMServerPartialHydration', () => {
37463746
<span>
37473747
Visible
37483748
</span>
3749-
<!--&-->
3750-
<!--/&-->
37513749
<!--$-->
3752-
<!--&-->
3753-
<!--/&-->
37543750
<!--/$-->
37553751
</div>
37563752
`);
@@ -3767,10 +3763,6 @@ describe('ReactDOMServerPartialHydration', () => {
37673763
await waitForPaint([]);
37683764
}
37693765

3770-
// Commit just the Activity boundary
3771-
// TODO: Optimize this
3772-
await waitForPaint([]);
3773-
37743766
// Subsequently, the hidden child is prerendered on the client
37753767
// along with hydrating the Suspense boundary outside the Activity.
37763768
await waitForPaint(['HiddenChild']);
@@ -3779,11 +3771,7 @@ describe('ReactDOMServerPartialHydration', () => {
37793771
<span>
37803772
Visible
37813773
</span>
3782-
<!--&-->
3783-
<!--/&-->
37843774
<!--$-->
3785-
<!--&-->
3786-
<!--/&-->
37873775
<!--/$-->
37883776
<span
37893777
style="display: none;"
@@ -3801,11 +3789,7 @@ describe('ReactDOMServerPartialHydration', () => {
38013789
<span>
38023790
Visible
38033791
</span>
3804-
<!--&-->
3805-
<!--/&-->
38063792
<!--$-->
3807-
<!--&-->
3808-
<!--/&-->
38093793
<!--/$-->
38103794
<span
38113795
style="display: none;"

packages/react-reconciler/src/ReactFiberBeginWork.js

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -730,12 +730,7 @@ function updateOffscreenComponent(
730730
reuseHiddenContextOnStack(workInProgress);
731731
pushOffscreenSuspenseHandler(workInProgress);
732732
} else if (
733-
!includesSomeLane(renderLanes, (OffscreenLane: Lane)) ||
734-
// SSR doesn't render hidden content (except legacy hidden) so it shouldn't hydrate,
735-
// even at offscreen lane. Defer to a client rendered offscreen lane.
736-
(getIsHydrating() &&
737-
(!enableLegacyHidden ||
738-
nextProps.mode !== 'unstable-defer-without-hiding'))
733+
!includesSomeLane(renderLanes, (OffscreenLane: Lane))
739734
) {
740735
// We're hidden, and we're not rendering at Offscreen. We will bail out
741736
// and resume this tree later.
@@ -1113,17 +1108,26 @@ function updateActivityComponent(
11131108

11141109
// Special path for hydration
11151110
// If we're currently hydrating, try to hydrate this boundary.
1111+
// Hidden Activity boundaries are not emitted on the server.
11161112
if (getIsHydrating()) {
1117-
// We must push the suspense handler context *before* attempting to
1118-
// hydrate, to avoid a mismatch in case it errors.
1119-
pushDehydratedActivitySuspenseHandler(workInProgress);
1120-
const dehydrated: ActivityInstance =
1121-
claimNextHydratableActivityInstance(workInProgress);
1122-
return mountDehydratedActivityComponent(
1123-
workInProgress,
1124-
dehydrated,
1125-
renderLanes,
1126-
);
1113+
if (nextProps.mode === 'hidden') {
1114+
// SSR doesn't render hidden Activity so it shouldn't hydrate,
1115+
// even at offscreen lane. Defer to a client rendered offscreen lane.
1116+
mountActivityChildren(workInProgress, nextProps, renderLanes);
1117+
workInProgress.lanes = laneToLanes(OffscreenLane);
1118+
return null;
1119+
} else {
1120+
// We must push the suspense handler context *before* attempting to
1121+
// hydrate, to avoid a mismatch in case it errors.
1122+
pushDehydratedActivitySuspenseHandler(workInProgress);
1123+
const dehydrated: ActivityInstance =
1124+
claimNextHydratableActivityInstance(workInProgress);
1125+
return mountDehydratedActivityComponent(
1126+
workInProgress,
1127+
dehydrated,
1128+
renderLanes,
1129+
);
1130+
}
11271131
}
11281132

11291133
return mountActivityChildren(workInProgress, nextProps, renderLanes);

packages/react-server/src/ReactFizzServer.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2227,24 +2227,24 @@ function renderActivity(
22272227
}
22282228
} else {
22292229
// Render
2230-
// An Activity boundary is delimited so that we can hydrate it separately.
2231-
pushStartActivityBoundary(segment.chunks, request.renderState);
2232-
segment.lastPushedText = false;
22332230
const mode = props.mode;
22342231
if (mode === 'hidden') {
22352232
// A hidden Activity boundary is not server rendered. Prerendering happens
22362233
// on the client.
22372234
} else {
2235+
// An Activity boundary is delimited so that we can hydrate it separately.
2236+
pushStartActivityBoundary(segment.chunks, request.renderState);
2237+
segment.lastPushedText = false;
22382238
// A visible Activity boundary has its children rendered inside the boundary.
22392239
const prevKeyPath = task.keyPath;
22402240
task.keyPath = keyPath;
22412241
// We use the non-destructive form because if something suspends, we still
22422242
// need to pop back up and finish the end comment.
22432243
renderNode(request, task, props.children, -1);
22442244
task.keyPath = prevKeyPath;
2245+
pushEndActivityBoundary(segment.chunks, request.renderState);
2246+
segment.lastPushedText = false;
22452247
}
2246-
pushEndActivityBoundary(segment.chunks, request.renderState);
2247-
segment.lastPushedText = false;
22482248
}
22492249
}
22502250

0 commit comments

Comments
 (0)