Skip to content

Commit ac7820a

Browse files
authored
Create fresh Offscreen instance when replaying (facebook#34127)
1 parent 3c67bbe commit ac7820a

File tree

3 files changed

+78
-36
lines changed

3 files changed

+78
-36
lines changed

packages/react-reconciler/src/ReactFiber.js

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import type {ActivityInstance, SuspenseInstance} from './ReactFiberConfig';
2424
import type {
2525
LegacyHiddenProps,
2626
OffscreenProps,
27-
OffscreenInstance,
2827
} from './ReactFiberOffscreenComponent';
2928
import type {ViewTransitionState} from './ReactFiberViewTransitionComponent';
3029
import type {TracingMarkerInstance} from './ReactFiberTracingMarkerComponent';
@@ -76,7 +75,6 @@ import {
7675
ViewTransitionComponent,
7776
ActivityComponent,
7877
} from './ReactWorkTags';
79-
import {OffscreenVisible} from './ReactFiberOffscreenComponent';
8078
import {getComponentNameFromOwner} from 'react-reconciler/src/getComponentNameFromFiber';
8179
import {isDevToolsPresent} from './ReactFiberDevToolsHook';
8280
import {
@@ -831,13 +829,6 @@ export function createFiberFromOffscreen(
831829
): Fiber {
832830
const fiber = createFiber(OffscreenComponent, pendingProps, key, mode);
833831
fiber.lanes = lanes;
834-
const primaryChildInstance: OffscreenInstance = {
835-
_visibility: OffscreenVisible,
836-
_pendingMarkers: null,
837-
_retryCache: null,
838-
_transitions: null,
839-
};
840-
fiber.stateNode = primaryChildInstance;
841832
return fiber;
842833
}
843834
export function createFiberFromActivity(
@@ -885,15 +876,6 @@ export function createFiberFromLegacyHidden(
885876
const fiber = createFiber(LegacyHiddenComponent, pendingProps, key, mode);
886877
fiber.elementType = REACT_LEGACY_HIDDEN_TYPE;
887878
fiber.lanes = lanes;
888-
// Adding a stateNode for legacy hidden because it's currently using
889-
// the offscreen implementation, which depends on a state node
890-
const instance: OffscreenInstance = {
891-
_visibility: OffscreenVisible,
892-
_pendingMarkers: null,
893-
_transitions: null,
894-
_retryCache: null,
895-
};
896-
fiber.stateNode = instance;
897879
return fiber;
898880
}
899881

packages/react-reconciler/src/ReactFiberBeginWork.js

Lines changed: 54 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,7 @@ import {
280280
createCapturedValueFromError,
281281
createCapturedValueAtFiber,
282282
} from './ReactCapturedValue';
283+
import {OffscreenVisible} from './ReactFiberOffscreenComponent';
283284
import {
284285
createClassErrorUpdate,
285286
initializeClassErrorUpdate,
@@ -620,6 +621,18 @@ function updateOffscreenComponent(
620621
const prevState: OffscreenState | null =
621622
current !== null ? current.memoizedState : null;
622623

624+
if (current === null && workInProgress.stateNode === null) {
625+
// We previously reset the work-in-progress.
626+
// We need to create a new Offscreen instance.
627+
const primaryChildInstance: OffscreenInstance = {
628+
_visibility: OffscreenVisible,
629+
_pendingMarkers: null,
630+
_retryCache: null,
631+
_transitions: null,
632+
};
633+
workInProgress.stateNode = primaryChildInstance;
634+
}
635+
623636
if (
624637
nextProps.mode === 'hidden' ||
625638
(enableLegacyHidden && nextProps.mode === 'unstable-defer-without-hiding')
@@ -788,6 +801,26 @@ function updateOffscreenComponent(
788801
return workInProgress.child;
789802
}
790803

804+
function bailoutOffscreenComponent(
805+
current: Fiber | null,
806+
workInProgress: Fiber,
807+
): Fiber | null {
808+
if (
809+
(current === null || current.tag !== OffscreenComponent) &&
810+
workInProgress.stateNode === null
811+
) {
812+
const primaryChildInstance: OffscreenInstance = {
813+
_visibility: OffscreenVisible,
814+
_pendingMarkers: null,
815+
_retryCache: null,
816+
_transitions: null,
817+
};
818+
workInProgress.stateNode = primaryChildInstance;
819+
}
820+
821+
return workInProgress.sibling;
822+
}
823+
791824
function deferHiddenOffscreenComponent(
792825
current: Fiber | null,
793826
workInProgress: Fiber,
@@ -1095,9 +1128,13 @@ function updateActivityComponent(
10951128
if (nextProps.mode === 'hidden') {
10961129
// SSR doesn't render hidden Activity so it shouldn't hydrate,
10971130
// even at offscreen lane. Defer to a client rendered offscreen lane.
1098-
mountActivityChildren(workInProgress, nextProps, renderLanes);
1131+
const primaryChildFragment = mountActivityChildren(
1132+
workInProgress,
1133+
nextProps,
1134+
renderLanes,
1135+
);
10991136
workInProgress.lanes = laneToLanes(OffscreenLane);
1100-
return null;
1137+
return bailoutOffscreenComponent(null, primaryChildFragment);
11011138
} else {
11021139
// We must push the suspense handler context *before* attempting to
11031140
// hydrate, to avoid a mismatch in case it errors.
@@ -2373,7 +2410,7 @@ function updateSuspenseComponent(
23732410
if (showFallback) {
23742411
pushFallbackTreeSuspenseHandler(workInProgress);
23752412

2376-
const fallbackFragment = mountSuspenseFallbackChildren(
2413+
mountSuspenseFallbackChildren(
23772414
workInProgress,
23782415
nextPrimaryChildren,
23792416
nextFallbackChildren,
@@ -2408,7 +2445,7 @@ function updateSuspenseComponent(
24082445
}
24092446
}
24102447

2411-
return fallbackFragment;
2448+
return bailoutOffscreenComponent(null, primaryChildFragment);
24122449
} else if (
24132450
enableCPUSuspense &&
24142451
typeof nextProps.unstable_expectedLoadTime === 'number'
@@ -2417,7 +2454,7 @@ function updateSuspenseComponent(
24172454
// unblock the surrounding content. Then immediately retry after the
24182455
// initial commit.
24192456
pushFallbackTreeSuspenseHandler(workInProgress);
2420-
const fallbackFragment = mountSuspenseFallbackChildren(
2457+
mountSuspenseFallbackChildren(
24212458
workInProgress,
24222459
nextPrimaryChildren,
24232460
nextFallbackChildren,
@@ -2444,7 +2481,7 @@ function updateSuspenseComponent(
24442481
// RetryLane even if it's the one currently rendering since we're leaving
24452482
// it behind on this node.
24462483
workInProgress.lanes = SomeRetryLane;
2447-
return fallbackFragment;
2484+
return bailoutOffscreenComponent(null, primaryChildFragment);
24482485
} else {
24492486
pushPrimaryTreeSuspenseHandler(workInProgress);
24502487
return mountSuspensePrimaryChildren(
@@ -2479,7 +2516,7 @@ function updateSuspenseComponent(
24792516

24802517
const nextFallbackChildren = nextProps.fallback;
24812518
const nextPrimaryChildren = nextProps.children;
2482-
const fallbackChildFragment = updateSuspenseFallbackChildren(
2519+
updateSuspenseFallbackChildren(
24832520
current,
24842521
workInProgress,
24852522
nextPrimaryChildren,
@@ -2532,7 +2569,7 @@ function updateSuspenseComponent(
25322569
renderLanes,
25332570
);
25342571
workInProgress.memoizedState = SUSPENDED_MARKER;
2535-
return fallbackChildFragment;
2572+
return bailoutOffscreenComponent(current.child, primaryChildFragment);
25362573
} else {
25372574
if (
25382575
prevState !== null &&
@@ -2788,7 +2825,7 @@ function updateSuspenseFallbackChildren(
27882825
primaryChildFragment.sibling = fallbackChildFragment;
27892826
workInProgress.child = primaryChildFragment;
27902827

2791-
return fallbackChildFragment;
2828+
return bailoutOffscreenComponent(null, primaryChildFragment);
27922829
}
27932830

27942831
function retrySuspenseComponentWithoutHydrating(
@@ -3094,14 +3131,13 @@ function updateDehydratedSuspenseComponent(
30943131

30953132
const nextPrimaryChildren = nextProps.children;
30963133
const nextFallbackChildren = nextProps.fallback;
3097-
const fallbackChildFragment =
3098-
mountSuspenseFallbackAfterRetryWithoutHydrating(
3099-
current,
3100-
workInProgress,
3101-
nextPrimaryChildren,
3102-
nextFallbackChildren,
3103-
renderLanes,
3104-
);
3134+
mountSuspenseFallbackAfterRetryWithoutHydrating(
3135+
current,
3136+
workInProgress,
3137+
nextPrimaryChildren,
3138+
nextFallbackChildren,
3139+
renderLanes,
3140+
);
31053141
const primaryChildFragment: Fiber = (workInProgress.child: any);
31063142
primaryChildFragment.memoizedState =
31073143
mountSuspenseOffscreenState(renderLanes);
@@ -3111,7 +3147,7 @@ function updateDehydratedSuspenseComponent(
31113147
renderLanes,
31123148
);
31133149
workInProgress.memoizedState = SUSPENDED_MARKER;
3114-
return fallbackChildFragment;
3150+
return bailoutOffscreenComponent(null, primaryChildFragment);
31153151
}
31163152
}
31173153
}

packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4141,4 +4141,28 @@ describe('ReactSuspenseWithNoopRenderer', () => {
41414141
</>,
41424142
);
41434143
});
4144+
4145+
it('can rerender after resolving a promise', async () => {
4146+
const promise = Promise.resolve(null);
4147+
const root = ReactNoop.createRoot();
4148+
4149+
await act(() => {
4150+
startTransition(() => {
4151+
root.render(<Suspense>{promise}</Suspense>);
4152+
});
4153+
});
4154+
4155+
assertLog([]);
4156+
expect(root).toMatchRenderedOutput(null);
4157+
4158+
await act(() => {
4159+
startTransition(() => {
4160+
root.render(
4161+
<Suspense>
4162+
<div />
4163+
</Suspense>,
4164+
);
4165+
});
4166+
});
4167+
});
41444168
});

0 commit comments

Comments
 (0)