@samitouri / QOS-React-1 / commits / ab31a9ed28

Selective Hydration: Don't suspend if showing fallback (#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.

Andrew Clark committed Aug 24, 2023 at 20:04 UTC ab31a9ed28d340172440e4b12e27d2af689249b3
2 files changed +60 -1
packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
+51
@@ -6176,4 +6176,55 @@ describe('ReactDOMFizzServer', () => {
6176 // However, it does error the shell.
6177 expect(fatalErrors).toEqual(['testing postpone']);
6178 });
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 + );
6230 });
packages/react-reconciler/src/ReactFiberBeginWork.js
+9 -1
@@ -2958,7 +2958,15 @@ function updateDehydratedSuspenseComponent(
2958 // TODO: We should ideally have a sync hydration lane that we can apply to do
2959 // a pass where we hydrate this subtree in place using the previous Context and then
2960 // 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 + }
2970 return retrySuspenseComponentWithoutHydrating(
2971 current,
2972 workInProgress,