Skip to content

Commit a80f1ab

Browse files
acdliteAndyPengc12
authored andcommitted
Selective Hydration: Don't suspend if showing fallback (facebook#27230)
A transition that flows into a dehydrated boundary should not suspend if the boundary is showing a fallback. This is related to another issue where Fizz streams in the initial HTML after a client navigation has already happened. That issue is not fixed by this commit, but it does make it less likely. Need to think more about the larger issue.
1 parent 41db22f commit a80f1ab

File tree

2 files changed

+60
-1
lines changed

2 files changed

+60
-1
lines changed

packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6176,4 +6176,55 @@ describe('ReactDOMFizzServer', () => {
61766176
// However, it does error the shell.
61776177
expect(fatalErrors).toEqual(['testing postpone']);
61786178
});
6179+
6180+
it(
6181+
'a transition that flows into a dehydrated boundary should not suspend ' +
6182+
'if the boundary is showing a fallback',
6183+
async () => {
6184+
let setSearch;
6185+
function App() {
6186+
const [search, _setSearch] = React.useState('initial query');
6187+
setSearch = _setSearch;
6188+
return (
6189+
<div>
6190+
<div>{search}</div>
6191+
<div>
6192+
<Suspense fallback="Loading...">
6193+
<AsyncText text="Async" />
6194+
</Suspense>
6195+
</div>
6196+
</div>
6197+
);
6198+
}
6199+
6200+
// Render the initial HTML, which is showing a fallback.
6201+
await act(() => {
6202+
const {pipe} = renderToPipeableStream(<App />);
6203+
pipe(writable);
6204+
});
6205+
6206+
// Start hydrating.
6207+
await clientAct(() => {
6208+
ReactDOMClient.hydrateRoot(container, <App />);
6209+
});
6210+
expect(getVisibleChildren(container)).toEqual(
6211+
<div>
6212+
<div>initial query</div>
6213+
<div>Loading...</div>
6214+
</div>,
6215+
);
6216+
6217+
// Before the HTML has streamed in, update the query. The part outside
6218+
// the fallback should be allowed to finish.
6219+
await clientAct(() => {
6220+
React.startTransition(() => setSearch('updated query'));
6221+
});
6222+
expect(getVisibleChildren(container)).toEqual(
6223+
<div>
6224+
<div>updated query</div>
6225+
<div>Loading...</div>
6226+
</div>,
6227+
);
6228+
},
6229+
);
61796230
});

packages/react-reconciler/src/ReactFiberBeginWork.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2959,7 +2959,15 @@ function updateDehydratedSuspenseComponent(
29592959
// TODO: We should ideally have a sync hydration lane that we can apply to do
29602960
// a pass where we hydrate this subtree in place using the previous Context and then
29612961
// reapply the update afterwards.
2962-
renderDidSuspendDelayIfPossible();
2962+
if (isSuspenseInstancePending(suspenseInstance)) {
2963+
// This is a dehydrated suspense instance. We don't need to suspend
2964+
// because we're already showing a fallback.
2965+
// TODO: The Fizz runtime might still stream in completed HTML, out-of-
2966+
// band. Should we fix this? There's a version of this bug that happens
2967+
// during client rendering, too. Needs more consideration.
2968+
} else {
2969+
renderDidSuspendDelayIfPossible();
2970+
}
29632971
return retrySuspenseComponentWithoutHydrating(
29642972
current,
29652973
workInProgress,

0 commit comments

Comments
 (0)