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(
+ <>
+
+
+ >,
+ );
+ });
+});