mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
[DevTools] Handle fallback unmount in Suspense update path (#34199)
This commit is contained in:
@@ -2696,4 +2696,83 @@ describe('Store', () => {
|
||||
<ClientComponent key="D">
|
||||
`);
|
||||
});
|
||||
|
||||
// @reactVersion >= 18.0
|
||||
it('can reconcile Suspense in fallback positions', async () => {
|
||||
let resolveFallback;
|
||||
const fallbackPromise = new Promise(resolve => {
|
||||
resolveFallback = resolve;
|
||||
});
|
||||
let resolveContent;
|
||||
const contentPromise = new Promise(resolve => {
|
||||
resolveContent = resolve;
|
||||
});
|
||||
|
||||
function Component({children, promise}) {
|
||||
if (promise) {
|
||||
React.use(promise);
|
||||
}
|
||||
return <div>{children}</div>;
|
||||
}
|
||||
|
||||
await actAsync(() =>
|
||||
render(
|
||||
<React.Suspense
|
||||
name="content"
|
||||
fallback={
|
||||
<React.Suspense
|
||||
name="fallback"
|
||||
fallback={
|
||||
<Component key="fallback-fallback">
|
||||
Loading fallback...
|
||||
</Component>
|
||||
}>
|
||||
<Component key="fallback-content" promise={fallbackPromise}>
|
||||
Loading...
|
||||
</Component>
|
||||
</React.Suspense>
|
||||
}>
|
||||
<Component key="content" promise={contentPromise}>
|
||||
done
|
||||
</Component>
|
||||
</React.Suspense>,
|
||||
),
|
||||
);
|
||||
|
||||
expect(store).toMatchInlineSnapshot(`
|
||||
[root]
|
||||
▾ <Suspense name="content">
|
||||
▾ <Suspense name="fallback">
|
||||
<Component key="fallback-fallback">
|
||||
[shell]
|
||||
<Suspense name="content" rects={null}>
|
||||
<Suspense name="fallback" rects={null}>
|
||||
`);
|
||||
|
||||
await actAsync(() => {
|
||||
resolveFallback();
|
||||
});
|
||||
|
||||
expect(store).toMatchInlineSnapshot(`
|
||||
[root]
|
||||
▾ <Suspense name="content">
|
||||
▾ <Suspense name="fallback">
|
||||
<Component key="fallback-content">
|
||||
[shell]
|
||||
<Suspense name="content" rects={null}>
|
||||
<Suspense name="fallback" rects={[{x:1,y:2,width:10,height:1}]}>
|
||||
`);
|
||||
|
||||
await actAsync(() => {
|
||||
resolveContent();
|
||||
});
|
||||
|
||||
expect(store).toMatchInlineSnapshot(`
|
||||
[root]
|
||||
▾ <Suspense name="content">
|
||||
<Component key="content">
|
||||
[shell]
|
||||
<Suspense name="content" rects={[{x:1,y:2,width:4,height:1}]}>
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
+15
-10
@@ -4736,26 +4736,30 @@ export function attach(
|
||||
);
|
||||
|
||||
shouldMeasureSuspenseNode = false;
|
||||
if (nextFallbackFiber !== null) {
|
||||
if (prevFallbackFiber !== null || nextFallbackFiber !== null) {
|
||||
const fallbackStashedSuspenseParent = reconcilingParentSuspenseNode;
|
||||
const fallbackStashedSuspensePrevious =
|
||||
previouslyReconciledSiblingSuspenseNode;
|
||||
const fallbackStashedSuspenseRemaining =
|
||||
remainingReconcilingChildrenSuspenseNodes;
|
||||
// Next, we'll pop back out of the SuspenseNode that we added above and now we'll
|
||||
// reconcile the fallback, reconciling anything by inserting into the parent SuspenseNode.
|
||||
// reconcile the fallback, reconciling anything in the context of the parent SuspenseNode.
|
||||
// Since the fallback conceptually blocks the parent.
|
||||
reconcilingParentSuspenseNode = stashedSuspenseParent;
|
||||
previouslyReconciledSiblingSuspenseNode = stashedSuspensePrevious;
|
||||
remainingReconcilingChildrenSuspenseNodes = stashedSuspenseRemaining;
|
||||
try {
|
||||
updateFlags |= updateVirtualChildrenRecursively(
|
||||
nextFallbackFiber,
|
||||
null,
|
||||
prevFallbackFiber,
|
||||
traceNearestHostComponentUpdate,
|
||||
0,
|
||||
);
|
||||
if (nextFallbackFiber === null) {
|
||||
unmountRemainingChildren();
|
||||
} else {
|
||||
updateFlags |= updateVirtualChildrenRecursively(
|
||||
nextFallbackFiber,
|
||||
null,
|
||||
prevFallbackFiber,
|
||||
traceNearestHostComponentUpdate,
|
||||
0,
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
reconcilingParentSuspenseNode = fallbackStashedSuspenseParent;
|
||||
previouslyReconciledSiblingSuspenseNode =
|
||||
@@ -4763,7 +4767,8 @@ export function attach(
|
||||
remainingReconcilingChildrenSuspenseNodes =
|
||||
fallbackStashedSuspenseRemaining;
|
||||
}
|
||||
} else if (nextFiber.memoizedState === null) {
|
||||
}
|
||||
if (nextFiber.memoizedState === null) {
|
||||
// Measure this Suspense node in case it changed. We don't update the rect while
|
||||
// we're inside a disconnected subtree nor if we are the Suspense boundary that
|
||||
// is suspended. This lets us keep the rectangle of the displayed content while
|
||||
|
||||
Reference in New Issue
Block a user