diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.new.js b/packages/react-reconciler/src/ReactFiberBeginWork.new.js index 76838eda06..3e4ae4d96f 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.new.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.new.js @@ -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. diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.new.js b/packages/react-reconciler/src/ReactFiberCompleteWork.new.js index 8e78d36e6a..8256e3f783 100644 --- a/packages/react-reconciler/src/ReactFiberCompleteWork.new.js +++ b/packages/react-reconciler/src/ReactFiberCompleteWork.new.js @@ -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; } } diff --git a/packages/react-reconciler/src/ReactFiberOffscreenComponent.js b/packages/react-reconciler/src/ReactFiberOffscreenComponent.js index 58f2360501..9f3aabcdde 100644 --- a/packages/react-reconciler/src/ReactFiberOffscreenComponent.js +++ b/packages/react-reconciler/src/ReactFiberOffscreenComponent.js @@ -17,7 +17,7 @@ export type OffscreenProps = {| // // Default mode is visible. Kind of a weird default for a component // called "Offscreen." Possible alt: ? - mode?: 'hidden' | 'visible' | null | void, + mode?: 'hidden' | 'unstable-defer-without-hiding' | 'visible' | null | void, children?: ReactNodeList, |}; diff --git a/packages/react-reconciler/src/__tests__/ReactOffscreen-test.js b/packages/react-reconciler/src/__tests__/ReactOffscreen-test.js new file mode 100644 index 0000000000..c85f333067 --- /dev/null +++ b/packages/react-reconciler/src/__tests__/ReactOffscreen-test.js @@ -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 ; + } + + // @gate experimental + // @gate new + it('unstable-defer-without-hiding should never toggle the visibility of its children', async () => { + function App({mode}) { + return ( + <> + + + + + + ); + } + + // Test the initial mount + const root = ReactNoop.createRoot(); + await ReactNoop.act(async () => { + root.render(); + expect(Scheduler).toFlushUntilNextPaint(['Normal']); + expect(root).toMatchRenderedOutput(); + }); + expect(Scheduler).toHaveYielded(['Deferred']); + expect(root).toMatchRenderedOutput( + <> + + + , + ); + + // Now try after an update + await ReactNoop.act(async () => { + root.render(); + }); + expect(Scheduler).toHaveYielded(['Normal', 'Deferred']); + expect(root).toMatchRenderedOutput( + <> + + + , + ); + + await ReactNoop.act(async () => { + root.render(); + expect(Scheduler).toFlushUntilNextPaint(['Normal']); + expect(root).toMatchRenderedOutput( + <> + + + , + ); + }); + expect(Scheduler).toHaveYielded(['Deferred']); + expect(root).toMatchRenderedOutput( + <> + + + , + ); + }); +});