Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -725,14 +725,14 @@ describe('ProfilingCache', () => {
const commitData = store.profilerStore.getDataForRoot(rootID).commitData;
expect(commitData).toHaveLength(2);

const isLegacySuspense = React.version.startsWith('17');
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

17 isn't what we refer to in the Fiber renderer as "legacy Suspense". Legacy Suspense had a Fragment for the content Fiber. 17 is using LegacyHiddenComponent and 18+ is using OffscreenComponent.

if (isLegacySuspense) {
if (React.version.startsWith('17')) {
// React 17 will mount all children until it suspends in a LegacyHidden
// The ID gap is from the Fiber for <Async> that's in the disconnected tree.
expect(commitData[0].fiberActualDurations).toMatchInlineSnapshot(`
Map {
1 => 15,
2 => 15,
3 => 5,
4 => 3,
5 => 2,
}
`);
Expand All @@ -741,7 +741,6 @@ describe('ProfilingCache', () => {
1 => 0,
2 => 10,
3 => 3,
4 => 3,
5 => 2,
}
`);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ describe('commit tree', () => {
let Scheduler;
let store: Store;
let utils;
const isLegacySuspense =
React.version.startsWith('16') || React.version.startsWith('17');

beforeEach(() => {
utils = require('./utils');
Expand Down Expand Up @@ -186,24 +184,13 @@ describe('commit tree', () => {
utils.act(() => store.profilerStore.startProfiling());
utils.act(() => legacyRender(<App renderChildren={true} />));
await Promise.resolve();
if (isLegacySuspense) {
expect(store).toMatchInlineSnapshot(`
[root]
▾ <App>
▾ <Suspense>
<Lazy>
[suspense-root] rects={null}
<Suspense name="App" rects={null}>
`);
} else {
expect(store).toMatchInlineSnapshot(`
[root]
▾ <App>
<Suspense>
[suspense-root] rects={null}
<Suspense name="App" rects={null}>
`);
}
expect(store).toMatchInlineSnapshot(`
[root]
▾ <App>
<Suspense>
[suspense-root] rects={null}
<Suspense name="App" rects={null}>
`);
utils.act(() => legacyRender(<App renderChildren={true} />));
expect(store).toMatchInlineSnapshot(`
[root]
Expand Down Expand Up @@ -231,13 +218,7 @@ describe('commit tree', () => {
);
}

expect(commitTrees[0].nodes.size).toBe(
isLegacySuspense
? // <Root> + <App> + <Suspense> + <Lazy>
4
: // <Root> + <App> + <Suspense>
3,
);
expect(commitTrees[0].nodes.size).toBe(3);
expect(commitTrees[1].nodes.size).toBe(4); // <Root> + <App> + <Suspense> + <LazyInnerComponent>
expect(commitTrees[2].nodes.size).toBe(2); // <Root> + <App>
});
Expand Down Expand Up @@ -291,24 +272,13 @@ describe('commit tree', () => {
it('should support Lazy components that are unmounted before resolving (legacy render)', async () => {
utils.act(() => store.profilerStore.startProfiling());
utils.act(() => legacyRender(<App renderChildren={true} />));
if (isLegacySuspense) {
expect(store).toMatchInlineSnapshot(`
[root]
▾ <App>
▾ <Suspense>
<Lazy>
[suspense-root] rects={null}
<Suspense name="App" rects={null}>
`);
} else {
expect(store).toMatchInlineSnapshot(`
[root]
▾ <App>
<Suspense>
[suspense-root] rects={null}
<Suspense name="App" rects={null}>
`);
}
expect(store).toMatchInlineSnapshot(`
[root]
▾ <App>
<Suspense>
[suspense-root] rects={null}
<Suspense name="App" rects={null}>
`);
utils.act(() => legacyRender(<App renderChildren={false} />));
expect(store).toMatchInlineSnapshot(`
[root]
Expand All @@ -327,13 +297,7 @@ describe('commit tree', () => {
);
}

expect(commitTrees[0].nodes.size).toBe(
isLegacySuspense
? // <Root> + <App> + <Suspense> + <Lazy>
4
: // <Root> + <App> + <Suspense>
3,
);
expect(commitTrees[0].nodes.size).toBe(3);
expect(commitTrees[1].nodes.size).toBe(2); // <Root> + <App>
});

Expand Down
4 changes: 2 additions & 2 deletions packages/react-devtools-shared/src/__tests__/store-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2828,7 +2828,7 @@ describe('Store', () => {
`);
});

// @reactVersion >= 18.0
// @reactVersion >= 17.0
it('can reconcile Suspense in fallback positions', async () => {
let resolveFallback;
const fallbackPromise = new Promise(resolve => {
Expand Down Expand Up @@ -2907,7 +2907,7 @@ describe('Store', () => {
`);
});

// @reactVersion >= 18.0
// @reactVersion >= 17.0
it('can reconcile resuspended Suspense with Suspense in fallback positions', async () => {
let resolveHeadFallback;
let resolveHeadContent;
Expand Down
40 changes: 24 additions & 16 deletions packages/react-devtools-shared/src/backend/fiber/renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -460,10 +460,10 @@ export function getInternalReactConstants(version: string): {
IncompleteFunctionComponent: 28,
IndeterminateComponent: 2, // removed in 19.0.0
LazyComponent: 16,
LegacyHiddenComponent: 23,
LegacyHiddenComponent: 23, // Does not exist in 18+ OSS but exists in fb builds
MemoComponent: 14,
Mode: 8,
OffscreenComponent: 22, // Experimental
OffscreenComponent: 22, // Experimental in 17. Stable in 18+
Profiler: 12,
ScopeComponent: 21, // Experimental
SimpleMemoComponent: 15,
Expand Down Expand Up @@ -3057,13 +3057,23 @@ export function attach(
}
}

function isHiddenOffscreen(fiber: Fiber): boolean {
switch (fiber.tag) {
case LegacyHiddenComponent:
// fallthrough since all published implementations currently implement the same state as Offscreen.
case OffscreenComponent:
return fiber.memoizedState !== null;
default:
return false;
}
}

function unmountRemainingChildren() {
if (
reconcilingParent !== null &&
(reconcilingParent.kind === FIBER_INSTANCE ||
reconcilingParent.kind === FILTERED_FIBER_INSTANCE) &&
reconcilingParent.data.tag === OffscreenComponent &&
reconcilingParent.data.memoizedState !== null &&
isHiddenOffscreen(reconcilingParent.data) &&
!isInDisconnectedSubtree
) {
// This is a hidden offscreen, we need to execute this in the context of a disconnected subtree.
Expand Down Expand Up @@ -3170,8 +3180,7 @@ export function attach(
if (
(parent.kind === FIBER_INSTANCE ||
parent.kind === FILTERED_FIBER_INSTANCE) &&
parent.data.tag === OffscreenComponent &&
parent.data.memoizedState !== null
isHiddenOffscreen(parent.data)
) {
// We're inside a hidden offscreen Fiber. We're in a disconnected tree.
return;
Expand Down Expand Up @@ -3819,7 +3828,9 @@ export function attach(
(reconcilingParent !== null &&
reconcilingParent.kind === VIRTUAL_INSTANCE) ||
fiber.tag === SuspenseComponent ||
fiber.tag === OffscreenComponent // Use to keep resuspended instances alive inside a SuspenseComponent.
// Use to keep resuspended instances alive inside a SuspenseComponent.
fiber.tag === OffscreenComponent ||
fiber.tag === LegacyHiddenComponent
) {
// If the parent is a Virtual Instance and we filtered this Fiber we include a
// hidden node. We also include this if it's a Suspense boundary so we can track those
Expand Down Expand Up @@ -3939,7 +3950,7 @@ export function attach(
trackDebugInfoFromHostComponent(nearestInstance, fiber);
}

if (fiber.tag === OffscreenComponent && fiber.memoizedState !== null) {
if (isHiddenOffscreen(fiber)) {
// If an Offscreen component is hidden, mount its children as disconnected.
const stashedDisconnected = isInDisconnectedSubtree;
isInDisconnectedSubtree = true;
Expand Down Expand Up @@ -4261,7 +4272,7 @@ export function attach(
while (child !== null) {
if (child.kind === FILTERED_FIBER_INSTANCE) {
const fiber = child.data;
if (fiber.tag === OffscreenComponent && fiber.memoizedState !== null) {
if (isHiddenOffscreen(fiber)) {
// The children of this Offscreen are hidden so they don't get added.
} else {
addUnfilteredChildrenIDs(child, nextChildren);
Expand Down Expand Up @@ -4888,9 +4899,8 @@ export function attach(
const nextDidTimeOut =
isLegacySuspense && nextFiber.memoizedState !== null;

const isOffscreen = nextFiber.tag === OffscreenComponent;
const prevWasHidden = isOffscreen && prevFiber.memoizedState !== null;
const nextIsHidden = isOffscreen && nextFiber.memoizedState !== null;
const prevWasHidden = isHiddenOffscreen(prevFiber);
const nextIsHidden = isHiddenOffscreen(nextFiber);

if (isLegacySuspense) {
if (
Expand Down Expand Up @@ -5245,8 +5255,7 @@ export function attach(
if (
(child.kind === FIBER_INSTANCE ||
child.kind === FILTERED_FIBER_INSTANCE) &&
child.data.tag === OffscreenComponent &&
child.data.memoizedState !== null
isHiddenOffscreen(child.data)
) {
// This instance's children are already disconnected.
} else {
Expand Down Expand Up @@ -5275,8 +5284,7 @@ export function attach(
if (
(child.kind === FIBER_INSTANCE ||
child.kind === FILTERED_FIBER_INSTANCE) &&
child.data.tag === OffscreenComponent &&
child.data.memoizedState !== null
isHiddenOffscreen(child.data)
) {
// This instance's children should remain disconnected.
} else {
Expand Down
Loading