Skip to content

Commit 448d49a

Browse files
committed
Bugfix: LegacyHidden shouldn't defer effects
In facebook#24967, I changed the behavior of Offscreen so that passive effects are not fired when the tree is hidden. I accidentally applied this behavior to the old LegacyHidden API, too, which is a deprecated internal-only type that www has been using while they wait for Offscreen to be ready. This fixes LegacyHidden so that the effects do not get deferred, like before. The new behavior still remains in the Offscreen API, which is experimental and not currently in use in www.
1 parent 922dda3 commit 448d49a

File tree

3 files changed

+130
-4
lines changed

3 files changed

+130
-4
lines changed

packages/react-reconciler/src/ReactFiberCommitWork.new.js

+37-2
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ import {
5151
enableTransitionTracing,
5252
enableUseEventHook,
5353
enableStrictEffects,
54+
enableLegacyHidden,
5455
} from 'shared/ReactFeatureFlags';
5556
import {
5657
FunctionComponent,
@@ -3346,7 +3347,23 @@ function commitPassiveMountOnFiber(
33463347
}
33473348
break;
33483349
}
3349-
case LegacyHiddenComponent:
3350+
case LegacyHiddenComponent: {
3351+
if (enableLegacyHidden) {
3352+
recursivelyTraversePassiveMountEffects(
3353+
finishedRoot,
3354+
finishedWork,
3355+
committedLanes,
3356+
committedTransitions,
3357+
);
3358+
3359+
if (flags & Passive) {
3360+
const current = finishedWork.alternate;
3361+
const instance: OffscreenInstance = finishedWork.stateNode;
3362+
commitOffscreenPassiveMountEffects(current, finishedWork, instance);
3363+
}
3364+
}
3365+
break;
3366+
}
33503367
case OffscreenComponent: {
33513368
// TODO: Pass `current` as argument to this function
33523369
const instance: OffscreenInstance = finishedWork.stateNode;
@@ -3527,7 +3544,25 @@ export function reconnectPassiveEffects(
35273544
// case HostRoot: {
35283545
// ...
35293546
// }
3530-
case LegacyHiddenComponent:
3547+
case LegacyHiddenComponent: {
3548+
if (enableLegacyHidden) {
3549+
recursivelyTraverseReconnectPassiveEffects(
3550+
finishedRoot,
3551+
finishedWork,
3552+
committedLanes,
3553+
committedTransitions,
3554+
includeWorkInProgressEffects,
3555+
);
3556+
3557+
const instance: OffscreenInstance = finishedWork.stateNode;
3558+
if (includeWorkInProgressEffects && flags & Passive) {
3559+
// TODO: Pass `current` as argument to this function
3560+
const current: Fiber | null = finishedWork.alternate;
3561+
commitOffscreenPassiveMountEffects(current, finishedWork, instance);
3562+
}
3563+
}
3564+
break;
3565+
}
35313566
case OffscreenComponent: {
35323567
const instance: OffscreenInstance = finishedWork.stateNode;
35333568
const nextState: OffscreenState | null = finishedWork.memoizedState;

packages/react-reconciler/src/ReactFiberCommitWork.old.js

+37-2
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ import {
5151
enableTransitionTracing,
5252
enableUseEventHook,
5353
enableStrictEffects,
54+
enableLegacyHidden,
5455
} from 'shared/ReactFeatureFlags';
5556
import {
5657
FunctionComponent,
@@ -3346,7 +3347,23 @@ function commitPassiveMountOnFiber(
33463347
}
33473348
break;
33483349
}
3349-
case LegacyHiddenComponent:
3350+
case LegacyHiddenComponent: {
3351+
if (enableLegacyHidden) {
3352+
recursivelyTraversePassiveMountEffects(
3353+
finishedRoot,
3354+
finishedWork,
3355+
committedLanes,
3356+
committedTransitions,
3357+
);
3358+
3359+
if (flags & Passive) {
3360+
const current = finishedWork.alternate;
3361+
const instance: OffscreenInstance = finishedWork.stateNode;
3362+
commitOffscreenPassiveMountEffects(current, finishedWork, instance);
3363+
}
3364+
}
3365+
break;
3366+
}
33503367
case OffscreenComponent: {
33513368
// TODO: Pass `current` as argument to this function
33523369
const instance: OffscreenInstance = finishedWork.stateNode;
@@ -3527,7 +3544,25 @@ export function reconnectPassiveEffects(
35273544
// case HostRoot: {
35283545
// ...
35293546
// }
3530-
case LegacyHiddenComponent:
3547+
case LegacyHiddenComponent: {
3548+
if (enableLegacyHidden) {
3549+
recursivelyTraverseReconnectPassiveEffects(
3550+
finishedRoot,
3551+
finishedWork,
3552+
committedLanes,
3553+
committedTransitions,
3554+
includeWorkInProgressEffects,
3555+
);
3556+
3557+
const instance: OffscreenInstance = finishedWork.stateNode;
3558+
if (includeWorkInProgressEffects && flags & Passive) {
3559+
// TODO: Pass `current` as argument to this function
3560+
const current: Fiber | null = finishedWork.alternate;
3561+
commitOffscreenPassiveMountEffects(current, finishedWork, instance);
3562+
}
3563+
}
3564+
break;
3565+
}
35313566
case OffscreenComponent: {
35323567
const instance: OffscreenInstance = finishedWork.stateNode;
35333568
const nextState: OffscreenState | null = finishedWork.memoizedState;

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

+56
Original file line numberDiff line numberDiff line change
@@ -942,6 +942,62 @@ describe('ReactOffscreen', () => {
942942
]);
943943
});
944944

945+
// @gate enableLegacyHidden
946+
it('do not defer passive effects when prerendering a new LegacyHidden tree', async () => {
947+
function Child({label}) {
948+
useEffect(() => {
949+
Scheduler.unstable_yieldValue('Mount ' + label);
950+
return () => {
951+
Scheduler.unstable_yieldValue('Unmount ' + label);
952+
};
953+
}, [label]);
954+
return <Text text={label} />;
955+
}
956+
957+
function App({showMore}) {
958+
return (
959+
<>
960+
<Child label="Shell" />
961+
<LegacyHidden
962+
mode={showMore ? 'visible' : 'unstable-defer-without-hiding'}>
963+
<Child label="More" />
964+
</LegacyHidden>
965+
</>
966+
);
967+
}
968+
969+
const root = ReactNoop.createRoot();
970+
971+
// Mount the app without showing the extra content
972+
await act(async () => {
973+
root.render(<App showMore={false} />);
974+
});
975+
expect(Scheduler).toHaveYielded([
976+
// First mount the outer visible shell
977+
'Shell',
978+
'Mount Shell',
979+
980+
// Then prerender the hidden extra context. Unlike Offscreen, the passive
981+
// effects in the hidden tree *should* fire
982+
'More',
983+
'Mount More',
984+
]);
985+
986+
// The hidden content has been prerendered
987+
expect(root).toMatchRenderedOutput(
988+
<>
989+
<span prop="Shell" />
990+
<span prop="More" />
991+
</>,
992+
);
993+
994+
// Reveal the prerendered tree
995+
await act(async () => {
996+
root.render(<App showMore={true} />);
997+
});
998+
expect(Scheduler).toHaveYielded(['Shell', 'More']);
999+
});
1000+
9451001
// @gate enableOffscreen
9461002
it('passive effects are connected and disconnected when the visibility changes', async () => {
9471003
function Child({step}) {

0 commit comments

Comments
 (0)