Skip to content

Commit

Permalink
Fix: LegacyHidden should not toggle effects (#21928)
Browse files Browse the repository at this point in the history
LegacyHidden is a transitional API that we added to replace the old
`<div hidden={true} />` API that we used to use for pre-rendering. The
plan is to replace this with the Offscreen component, once it's ready.

The idea is that LegacyHidden has identical behavior to Offscreen except
that it doesn't change the behavior of effects. (Which is basically how
`<div hidden={true} />` worked — it prerendered the hidden content in
the background, but nothing else.) That way, while we're rolling this
out, we could toggle the feature behind a feature flag either for
performance testing or as a kill switch.

It looks like we accidentally enabled the effects flag for both
Offscreen _and_ LegacyHidden. I suppose it's a good thing that nobody
has complained yet, since we eventually do want to ship this
behavior everywhere?

But I do think we should remove it from LegacyHidden, and roll it out by
gating the component type in the downstream repo. That way if there's an
issue related to the use of LegacyHidden, we can disable that without
disabling the behavior for Suspense boundaries.

In retrospect, I might have implemented this as an unstable prop on
Offscreen instead of a completely separate type — though at the time,
Offscreen didn't exist. I originally added LegacyHidden to unblock the
Lanes refactor, so I could move the deprioritization logic out of the
HostComponent implementation.

Not a big deal since we're going to remove this soon. The implementation
is almost the same regardless: before disconnecting or reconnecting
the effects, check the fiber tag. The rest of the logic is the same.
  • Loading branch information
acdlite committed Jul 21, 2021
1 parent 25f09e3 commit ae5d261
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 40 deletions.
36 changes: 17 additions & 19 deletions packages/react-reconciler/src/ReactFiberCommitWork.new.js
Original file line number Diff line number Diff line change
Expand Up @@ -2131,8 +2131,7 @@ function commitMutationEffectsOnFiber(finishedWork: Fiber, root: FiberRoot) {
}
break;
}
case OffscreenComponent:
case LegacyHiddenComponent: {
case OffscreenComponent: {
const newState: OffscreenState | null = finishedWork.memoizedState;
const isHidden = newState !== null;
const current = finishedWork.alternate;
Expand All @@ -2145,27 +2144,26 @@ function commitMutationEffectsOnFiber(finishedWork: Fiber, root: FiberRoot) {
hideOrUnhideAllChildren(offscreenBoundary, isHidden);
}

if (isHidden) {
if (!wasHidden) {
if (
enableSuspenseLayoutEffectSemantics &&
(offscreenBoundary.mode & ConcurrentMode) !== NoMode
) {
nextEffect = offscreenBoundary;
let offscreenChild = offscreenBoundary.child;
while (offscreenChild !== null) {
nextEffect = offscreenChild;
disappearLayoutEffects_begin(offscreenChild);
offscreenChild = offscreenChild.sibling;
if (enableSuspenseLayoutEffectSemantics) {
if (isHidden) {
if (!wasHidden) {
if ((offscreenBoundary.mode & ConcurrentMode) !== NoMode) {
nextEffect = offscreenBoundary;
let offscreenChild = offscreenBoundary.child;
while (offscreenChild !== null) {
nextEffect = offscreenChild;
disappearLayoutEffects_begin(offscreenChild);
offscreenChild = offscreenChild.sibling;
}
}
}
} else {
if (wasHidden) {
// TODO: Move re-appear call here for symmetry?
}
}
} else {
if (wasHidden) {
// TODO: Move re-appear call here for symmetry?
}
break;
}
break;
}
}
}
Expand Down
36 changes: 17 additions & 19 deletions packages/react-reconciler/src/ReactFiberCommitWork.old.js
Original file line number Diff line number Diff line change
Expand Up @@ -2131,8 +2131,7 @@ function commitMutationEffectsOnFiber(finishedWork: Fiber, root: FiberRoot) {
}
break;
}
case OffscreenComponent:
case LegacyHiddenComponent: {
case OffscreenComponent: {
const newState: OffscreenState | null = finishedWork.memoizedState;
const isHidden = newState !== null;
const current = finishedWork.alternate;
Expand All @@ -2145,27 +2144,26 @@ function commitMutationEffectsOnFiber(finishedWork: Fiber, root: FiberRoot) {
hideOrUnhideAllChildren(offscreenBoundary, isHidden);
}

if (isHidden) {
if (!wasHidden) {
if (
enableSuspenseLayoutEffectSemantics &&
(offscreenBoundary.mode & ConcurrentMode) !== NoMode
) {
nextEffect = offscreenBoundary;
let offscreenChild = offscreenBoundary.child;
while (offscreenChild !== null) {
nextEffect = offscreenChild;
disappearLayoutEffects_begin(offscreenChild);
offscreenChild = offscreenChild.sibling;
if (enableSuspenseLayoutEffectSemantics) {
if (isHidden) {
if (!wasHidden) {
if ((offscreenBoundary.mode & ConcurrentMode) !== NoMode) {
nextEffect = offscreenBoundary;
let offscreenChild = offscreenBoundary.child;
while (offscreenChild !== null) {
nextEffect = offscreenChild;
disappearLayoutEffects_begin(offscreenChild);
offscreenChild = offscreenChild.sibling;
}
}
}
} else {
if (wasHidden) {
// TODO: Move re-appear call here for symmetry?
}
}
} else {
if (wasHidden) {
// TODO: Move re-appear call here for symmetry?
}
break;
}
break;
}
}
}
Expand Down
4 changes: 3 additions & 1 deletion packages/react-reconciler/src/ReactFiberCompleteWork.new.js
Original file line number Diff line number Diff line change
Expand Up @@ -1361,7 +1361,9 @@ function completeWork(
const prevIsHidden = prevState !== null;
if (
prevIsHidden !== nextIsHidden &&
newProps.mode !== 'unstable-defer-without-hiding'
newProps.mode !== 'unstable-defer-without-hiding' &&
// LegacyHidden doesn't do any hiding — it only pre-renders.
workInProgress.tag !== LegacyHiddenComponent
) {
workInProgress.flags |= Visibility;
}
Expand Down
4 changes: 3 additions & 1 deletion packages/react-reconciler/src/ReactFiberCompleteWork.old.js
Original file line number Diff line number Diff line change
Expand Up @@ -1361,7 +1361,9 @@ function completeWork(
const prevIsHidden = prevState !== null;
if (
prevIsHidden !== nextIsHidden &&
newProps.mode !== 'unstable-defer-without-hiding'
newProps.mode !== 'unstable-defer-without-hiding' &&
// LegacyHidden doesn't do any hiding — it only pre-renders.
workInProgress.tag !== LegacyHiddenComponent
) {
workInProgress.flags |= Visibility;
}
Expand Down
48 changes: 48 additions & 0 deletions packages/react-reconciler/src/__tests__/ReactOffscreen-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -337,4 +337,52 @@ describe('ReactOffscreen', () => {
expect(root).toMatchRenderedOutput(<span prop="Child" />);
}
});

// @gate experimental || www
it('does not toggle effects for LegacyHidden component', async () => {
// LegacyHidden is meant to be the same as offscreen except it doesn't
// do anything to effects. Only used by www, as a temporary migration step.
function Child({text}) {
useLayoutEffect(() => {
Scheduler.unstable_yieldValue('Mount layout');
return () => {
Scheduler.unstable_yieldValue('Unmount layout');
};
}, []);
return <Text text="Child" />;
}

const root = ReactNoop.createRoot();
await act(async () => {
root.render(
<LegacyHidden mode="visible">
<Child />
</LegacyHidden>,
);
});
expect(Scheduler).toHaveYielded(['Child', 'Mount layout']);

await act(async () => {
root.render(
<LegacyHidden mode="hidden">
<Child />
</LegacyHidden>,
);
});
expect(Scheduler).toHaveYielded(['Child']);

await act(async () => {
root.render(
<LegacyHidden mode="visible">
<Child />
</LegacyHidden>,
);
});
expect(Scheduler).toHaveYielded(['Child']);

await act(async () => {
root.render(null);
});
expect(Scheduler).toHaveYielded(['Unmount layout']);
});
});

0 comments on commit ae5d261

Please sign in to comment.