Skip to content

Commit 22abdff

Browse files
committed
Selective Hydration: Don't suspend if showing fallback
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 b4cdd3e commit 22abdff

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
@@ -6131,4 +6131,55 @@ describe('ReactDOMFizzServer', () => {
61316131
// However, it does error the shell.
61326132
expect(fatalErrors).toEqual(['testing postpone']);
61336133
});
6134+
6135+
it(
6136+
'a transition that flows into a dehydrated boundary should not suspend ' +
6137+
'if the boundary is showing a fallback',
6138+
async () => {
6139+
let setSearch;
6140+
function App() {
6141+
const [search, _setSearch] = React.useState('initial query');
6142+
setSearch = _setSearch;
6143+
return (
6144+
<div>
6145+
<div>{search}</div>
6146+
<div>
6147+
<Suspense fallback="Loading...">
6148+
<AsyncText text="Async" />
6149+
</Suspense>
6150+
</div>
6151+
</div>
6152+
);
6153+
}
6154+
6155+
// Render the initial HTML, which is showing a fallback.
6156+
await act(() => {
6157+
const {pipe} = renderToPipeableStream(<App />);
6158+
pipe(writable);
6159+
});
6160+
6161+
// Start hydrating.
6162+
await clientAct(() => {
6163+
ReactDOMClient.hydrateRoot(container, <App />);
6164+
});
6165+
expect(getVisibleChildren(container)).toEqual(
6166+
<div>
6167+
<div>initial query</div>
6168+
<div>Loading...</div>
6169+
</div>,
6170+
);
6171+
6172+
// Before the HTML has streamed in, update the query. The part outside
6173+
// the fallback should be allowed to finish.
6174+
await clientAct(() => {
6175+
React.startTransition(() => setSearch('updated query'));
6176+
});
6177+
expect(getVisibleChildren(container)).toEqual(
6178+
<div>
6179+
<div>updated query</div>
6180+
<div>Loading...</div>
6181+
</div>,
6182+
);
6183+
},
6184+
);
61346185
});

packages/react-reconciler/src/ReactFiberBeginWork.js

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

0 commit comments

Comments
 (0)