diff --git a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js b/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
index 568a931c7d9e3..295e99b26a691 100644
--- a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
+++ b/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
@@ -869,16 +869,16 @@ describe('ReactDOMFizzServer', () => {
});
// We still can't render it on the client.
- expect(Scheduler).toFlushAndYield([
- 'The server could not finish this Suspense boundary, likely due to an ' +
- 'error during server rendering. Switched to client rendering.',
- ]);
+ expect(Scheduler).toFlushAndYield([]);
expect(getVisibleChildren(container)).toEqual(
Loading...
);
// We now resolve it on the client.
resolveText('Hello');
- Scheduler.unstable_flushAll();
+ expect(Scheduler).toFlushAndYield([
+ 'The server could not finish this Suspense boundary, likely due to an ' +
+ 'error during server rendering. Switched to client rendering.',
+ ]);
// The client rendered HTML is now in place.
expect(getVisibleChildren(container)).toEqual(
@@ -2220,6 +2220,286 @@ describe('ReactDOMFizzServer', () => {
},
);
+ // @gate experimental
+ it('does not recreate the fallback if server errors and hydration suspends', async () => {
+ let isClient = false;
+
+ function Child() {
+ if (isClient) {
+ readText('Yay!');
+ } else {
+ throw Error('Oops.');
+ }
+ Scheduler.unstable_yieldValue('Yay!');
+ return 'Yay!';
+ }
+
+ const fallbackRef = React.createRef();
+ function App() {
+ return (
+
diff --git a/packages/react-dom/src/__tests__/ReactDOMHydrationDiff-test.js b/packages/react-dom/src/__tests__/ReactDOMHydrationDiff-test.js
index f79ca0c18b631..a6a1a99890388 100644
--- a/packages/react-dom/src/__tests__/ReactDOMHydrationDiff-test.js
+++ b/packages/react-dom/src/__tests__/ReactDOMHydrationDiff-test.js
@@ -1046,7 +1046,7 @@ describe('ReactDOMServerHydration', () => {
});
// @gate __DEV__
- it('warns when client renders an extra node inside Suspense fallback', () => {
+ it('does not warn when client renders an extra node inside Suspense fallback', () => {
function Mismatch({isClient}) {
return (
@@ -1063,27 +1063,18 @@ describe('ReactDOMServerHydration', () => {
);
}
- // TODO: Why does this not show a fallback mismatch?
- // And why is this message different from the other ones?
if (
gate(flags => flags.enableClientRenderFallbackOnHydrationMismatch)
) {
- expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
- Array [
- "Caught [The server could not finish this Suspense boundary, likely due to an error during server rendering. Switched to client rendering.]",
- ]
- `);
+ // There is no error because we don't actually hydrate fallbacks.
+ expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`Array []`);
} else {
- expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
- Array [
- "Caught [The server could not finish this Suspense boundary, likely due to an error during server rendering. Switched to client rendering.]",
- ]
- `);
+ expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`Array []`);
}
});
// @gate __DEV__
- it('warns when server renders an extra node inside Suspense fallback', () => {
+ it('does not warn when server renders an extra node inside Suspense fallback', () => {
function Mismatch({isClient}) {
return (
@@ -1100,22 +1091,13 @@ describe('ReactDOMServerHydration', () => {
);
}
- // TODO: Why does this not show a fallback mismatch?
- // And why is this message different from the other ones?
if (
gate(flags => flags.enableClientRenderFallbackOnHydrationMismatch)
) {
- expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
- Array [
- "Caught [The server could not finish this Suspense boundary, likely due to an error during server rendering. Switched to client rendering.]",
- ]
- `);
+ // There is no error because we don't actually hydrate fallbacks.
+ expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`Array []`);
} else {
- expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
- Array [
- "Caught [The server could not finish this Suspense boundary, likely due to an error during server rendering. Switched to client rendering.]",
- ]
- `);
+ expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`Array []`);
}
});
});
diff --git a/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js b/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js
index f27d002db48fc..19846522ee44d 100644
--- a/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js
+++ b/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js
@@ -2137,10 +2137,7 @@ describe('ReactDOMServerPartialHydration', () => {
});
suspend = true;
- expect(Scheduler).toFlushAndYield([
- 'The server could not finish this Suspense boundary, likely due to ' +
- 'an error during server rendering. Switched to client rendering.',
- ]);
+ expect(Scheduler).toFlushAndYield([]);
// We haven't hydrated the second child but the placeholder is still in the list.
expect(container.textContent).toBe('ALoading B');
diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.new.js b/packages/react-reconciler/src/ReactFiberBeginWork.new.js
index 6fee8d948ebe2..32da430eef071 100644
--- a/packages/react-reconciler/src/ReactFiberBeginWork.new.js
+++ b/packages/react-reconciler/src/ReactFiberBeginWork.new.js
@@ -2241,6 +2241,7 @@ function updateSuspenseComponent(current, workInProgress, renderLanes) {
} else {
// Suspended but we should no longer be in dehydrated mode.
// Therefore we now have to render the fallback.
+ renderDidSuspendDelayIfPossible();
const nextPrimaryChildren = nextProps.children;
const nextFallbackChildren = nextProps.fallback;
const fallbackChildFragment = mountSuspenseFallbackAfterRetryWithoutHydrating(
diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.old.js b/packages/react-reconciler/src/ReactFiberBeginWork.old.js
index fc4912e7ec393..45aa1cd43ba91 100644
--- a/packages/react-reconciler/src/ReactFiberBeginWork.old.js
+++ b/packages/react-reconciler/src/ReactFiberBeginWork.old.js
@@ -2241,6 +2241,7 @@ function updateSuspenseComponent(current, workInProgress, renderLanes) {
} else {
// Suspended but we should no longer be in dehydrated mode.
// Therefore we now have to render the fallback.
+ renderDidSuspendDelayIfPossible();
const nextPrimaryChildren = nextProps.children;
const nextFallbackChildren = nextProps.fallback;
const fallbackChildFragment = mountSuspenseFallbackAfterRetryWithoutHydrating(
diff --git a/packages/react-reconciler/src/ReactFiberLane.new.js b/packages/react-reconciler/src/ReactFiberLane.new.js
index 597f996331999..3bf65a9093add 100644
--- a/packages/react-reconciler/src/ReactFiberLane.new.js
+++ b/packages/react-reconciler/src/ReactFiberLane.new.js
@@ -458,8 +458,9 @@ export function includesNonIdleWork(lanes: Lanes) {
export function includesOnlyRetries(lanes: Lanes) {
return (lanes & RetryLanes) === lanes;
}
-export function includesOnlyTransitions(lanes: Lanes) {
- return (lanes & TransitionLanes) === lanes;
+export function includesOnlyNonUrgentLanes(lanes: Lanes) {
+ const UrgentLanes = SyncLane | InputContinuousLane | DefaultLane;
+ return (lanes & UrgentLanes) === NoLanes;
}
export function includesBlockingLane(root: FiberRoot, lanes: Lanes) {
diff --git a/packages/react-reconciler/src/ReactFiberLane.old.js b/packages/react-reconciler/src/ReactFiberLane.old.js
index 9f366c9ade886..ec884de182ff6 100644
--- a/packages/react-reconciler/src/ReactFiberLane.old.js
+++ b/packages/react-reconciler/src/ReactFiberLane.old.js
@@ -458,8 +458,9 @@ export function includesNonIdleWork(lanes: Lanes) {
export function includesOnlyRetries(lanes: Lanes) {
return (lanes & RetryLanes) === lanes;
}
-export function includesOnlyTransitions(lanes: Lanes) {
- return (lanes & TransitionLanes) === lanes;
+export function includesOnlyNonUrgentLanes(lanes: Lanes) {
+ const UrgentLanes = SyncLane | InputContinuousLane | DefaultLane;
+ return (lanes & UrgentLanes) === NoLanes;
}
export function includesBlockingLane(root: FiberRoot, lanes: Lanes) {
diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.new.js b/packages/react-reconciler/src/ReactFiberWorkLoop.new.js
index 558440effa77a..3e50d6e391608 100644
--- a/packages/react-reconciler/src/ReactFiberWorkLoop.new.js
+++ b/packages/react-reconciler/src/ReactFiberWorkLoop.new.js
@@ -132,7 +132,7 @@ import {
pickArbitraryLane,
includesNonIdleWork,
includesOnlyRetries,
- includesOnlyTransitions,
+ includesOnlyNonUrgentLanes,
includesBlockingLane,
includesExpiredLane,
getNextLanes,
@@ -1110,7 +1110,7 @@ function finishConcurrentRender(root, exitStatus, lanes) {
case RootSuspendedWithDelay: {
markRootSuspended(root, lanes);
- if (includesOnlyTransitions(lanes)) {
+ if (includesOnlyNonUrgentLanes(lanes)) {
// This is a transition, so we should exit without committing a
// placeholder and without scheduling a timeout. Delay indefinitely
// until we receive more data.
diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.old.js b/packages/react-reconciler/src/ReactFiberWorkLoop.old.js
index c1c090d82b0b5..2745bb87c09eb 100644
--- a/packages/react-reconciler/src/ReactFiberWorkLoop.old.js
+++ b/packages/react-reconciler/src/ReactFiberWorkLoop.old.js
@@ -132,7 +132,7 @@ import {
pickArbitraryLane,
includesNonIdleWork,
includesOnlyRetries,
- includesOnlyTransitions,
+ includesOnlyNonUrgentLanes,
includesBlockingLane,
includesExpiredLane,
getNextLanes,
@@ -1110,7 +1110,7 @@ function finishConcurrentRender(root, exitStatus, lanes) {
case RootSuspendedWithDelay: {
markRootSuspended(root, lanes);
- if (includesOnlyTransitions(lanes)) {
+ if (includesOnlyNonUrgentLanes(lanes)) {
// This is a transition, so we should exit without committing a
// placeholder and without scheduling a timeout. Delay indefinitely
// until we receive more data.