LegacyHidden: mode that defers without hiding (#18958)

Need this to unblock www. Not sure yet how we'll support this properly
long term.

While adding this, I noticed that the normal "hidden" mode of
LegacyHidden doesn't work properly because it doesn't toggle the
visibility of newly inserted nodes. This is fine for now since we only
use it via a userspace abstraction that wraps the children in an
additional node. But implementing this correctly is required for us
to start using it like a fragment, without the wrapper node.
This commit is contained in:
Andrew Clark
2020-05-19 15:58:02 -07:00
committed by GitHub
parent 5aa967b69b
commit 95ea8ed47c
4 changed files with 89 additions and 3 deletions

View File

@@ -566,7 +566,10 @@ function updateOffscreenComponent(
const prevState: OffscreenState | null =
current !== null ? current.memoizedState : null;
if (nextProps.mode === 'hidden') {
if (
nextProps.mode === 'hidden' ||
nextProps.mode === 'unstable-defer-without-hiding'
) {
if ((workInProgress.mode & ConcurrentMode) === NoMode) {
// In legacy sync mode, don't defer the subtree. Render it now.
// TODO: Figure out what we should do in Blocking mode.

View File

@@ -1308,7 +1308,10 @@ function completeWork(
const prevIsHidden = prevState !== null;
const nextIsHidden = nextState !== null;
if (prevIsHidden !== nextIsHidden) {
if (
prevIsHidden !== nextIsHidden &&
newProps.mode !== 'unstable-defer-without-hiding'
) {
workInProgress.effectTag |= Update;
}
}

View File

@@ -17,7 +17,7 @@ export type OffscreenProps = {|
//
// Default mode is visible. Kind of a weird default for a component
// called "Offscreen." Possible alt: <Visibility />?
mode?: 'hidden' | 'visible' | null | void,
mode?: 'hidden' | 'unstable-defer-without-hiding' | 'visible' | null | void,
children?: ReactNodeList,
|};

View File

@@ -0,0 +1,80 @@
let React;
let ReactNoop;
let Scheduler;
let LegacyHidden;
describe('ReactOffscreen', () => {
beforeEach(() => {
jest.resetModules();
React = require('react');
ReactNoop = require('react-noop-renderer');
Scheduler = require('scheduler');
LegacyHidden = React.unstable_LegacyHidden;
});
function Text(props) {
Scheduler.unstable_yieldValue(props.text);
return <span prop={props.text} />;
}
// @gate experimental
// @gate new
it('unstable-defer-without-hiding should never toggle the visibility of its children', async () => {
function App({mode}) {
return (
<>
<Text text="Normal" />
<LegacyHidden mode={mode}>
<Text text="Deferred" />
</LegacyHidden>
</>
);
}
// Test the initial mount
const root = ReactNoop.createRoot();
await ReactNoop.act(async () => {
root.render(<App mode="unstable-defer-without-hiding" />);
expect(Scheduler).toFlushUntilNextPaint(['Normal']);
expect(root).toMatchRenderedOutput(<span prop="Normal" />);
});
expect(Scheduler).toHaveYielded(['Deferred']);
expect(root).toMatchRenderedOutput(
<>
<span prop="Normal" />
<span prop="Deferred" />
</>,
);
// Now try after an update
await ReactNoop.act(async () => {
root.render(<App mode="visible" />);
});
expect(Scheduler).toHaveYielded(['Normal', 'Deferred']);
expect(root).toMatchRenderedOutput(
<>
<span prop="Normal" />
<span prop="Deferred" />
</>,
);
await ReactNoop.act(async () => {
root.render(<App mode="unstable-defer-without-hiding" />);
expect(Scheduler).toFlushUntilNextPaint(['Normal']);
expect(root).toMatchRenderedOutput(
<>
<span prop="Normal" />
<span prop="Deferred" />
</>,
);
});
expect(Scheduler).toHaveYielded(['Deferred']);
expect(root).toMatchRenderedOutput(
<>
<span prop="Normal" />
<span prop="Deferred" />
</>,
);
});
});