diff --git a/packages/react-reconciler/src/ReactChildFiber.new.js b/packages/react-reconciler/src/ReactChildFiber.new.js
index 5b0e858d6af96..0406a362db5b2 100644
--- a/packages/react-reconciler/src/ReactChildFiber.new.js
+++ b/packages/react-reconciler/src/ReactChildFiber.new.js
@@ -33,7 +33,11 @@ import {
Block,
} from './ReactWorkTags';
import invariant from 'shared/invariant';
-import {warnAboutStringRefs, enableBlocksAPI} from 'shared/ReactFeatureFlags';
+import {
+ warnAboutStringRefs,
+ enableBlocksAPI,
+ enableLazyElements,
+} from 'shared/ReactFeatureFlags';
import {
createWorkInProgress,
@@ -532,6 +536,13 @@ function ChildReconciler(shouldTrackSideEffects) {
created.return = returnFiber;
return created;
}
+ case REACT_LAZY_TYPE: {
+ if (enableLazyElements) {
+ const payload = newChild._payload;
+ const init = newChild._init;
+ return createChild(returnFiber, init(payload), lanes);
+ }
+ }
}
if (isArray(newChild) || getIteratorFn(newChild)) {
@@ -602,6 +613,13 @@ function ChildReconciler(shouldTrackSideEffects) {
return null;
}
}
+ case REACT_LAZY_TYPE: {
+ if (enableLazyElements) {
+ const payload = newChild._payload;
+ const init = newChild._init;
+ return updateSlot(returnFiber, oldFiber, init(payload), lanes);
+ }
+ }
}
if (isArray(newChild) || getIteratorFn(newChild)) {
@@ -663,6 +681,18 @@ function ChildReconciler(shouldTrackSideEffects) {
) || null;
return updatePortal(returnFiber, matchedFiber, newChild, lanes);
}
+ case REACT_LAZY_TYPE:
+ if (enableLazyElements) {
+ const payload = newChild._payload;
+ const init = newChild._init;
+ return updateFromMap(
+ existingChildren,
+ returnFiber,
+ newIdx,
+ init(payload),
+ lanes,
+ );
+ }
}
if (isArray(newChild) || getIteratorFn(newChild)) {
@@ -720,6 +750,15 @@ function ChildReconciler(shouldTrackSideEffects) {
key,
);
break;
+ case REACT_LAZY_TYPE:
+ if (enableLazyElements) {
+ const payload = child._payload;
+ const init = (child._init: any);
+ warnOnInvalidKey(init(payload), knownKeys, returnFiber);
+ break;
+ }
+ // We intentionally fallthrough here if enableLazyElements is not on.
+ // eslint-disable-next-lined no-fallthrough
default:
break;
}
@@ -1276,6 +1315,18 @@ function ChildReconciler(shouldTrackSideEffects) {
lanes,
),
);
+ case REACT_LAZY_TYPE:
+ if (enableLazyElements) {
+ const payload = newChild._payload;
+ const init = newChild._init;
+ // TODO: This function is supposed to be non-recursive.
+ return reconcileChildFibers(
+ returnFiber,
+ currentFirstChild,
+ init(payload),
+ lanes,
+ );
+ }
}
}
diff --git a/packages/react-reconciler/src/ReactChildFiber.old.js b/packages/react-reconciler/src/ReactChildFiber.old.js
index 3f5da1699a93d..1eeaa2d022ee7 100644
--- a/packages/react-reconciler/src/ReactChildFiber.old.js
+++ b/packages/react-reconciler/src/ReactChildFiber.old.js
@@ -33,7 +33,11 @@ import {
Block,
} from './ReactWorkTags';
import invariant from 'shared/invariant';
-import {warnAboutStringRefs, enableBlocksAPI} from 'shared/ReactFeatureFlags';
+import {
+ warnAboutStringRefs,
+ enableBlocksAPI,
+ enableLazyElements,
+} from 'shared/ReactFeatureFlags';
import {
createWorkInProgress,
@@ -542,6 +546,13 @@ function ChildReconciler(shouldTrackSideEffects) {
created.return = returnFiber;
return created;
}
+ case REACT_LAZY_TYPE: {
+ if (enableLazyElements) {
+ const payload = newChild._payload;
+ const init = newChild._init;
+ return createChild(returnFiber, init(payload), expirationTime);
+ }
+ }
}
if (isArray(newChild) || getIteratorFn(newChild)) {
@@ -627,6 +638,18 @@ function ChildReconciler(shouldTrackSideEffects) {
return null;
}
}
+ case REACT_LAZY_TYPE: {
+ if (enableLazyElements) {
+ const payload = newChild._payload;
+ const init = newChild._init;
+ return updateSlot(
+ returnFiber,
+ oldFiber,
+ init(payload),
+ expirationTime,
+ );
+ }
+ }
}
if (isArray(newChild) || getIteratorFn(newChild)) {
@@ -709,6 +732,18 @@ function ChildReconciler(shouldTrackSideEffects) {
expirationTime,
);
}
+ case REACT_LAZY_TYPE:
+ if (enableLazyElements) {
+ const payload = newChild._payload;
+ const init = newChild._init;
+ return updateFromMap(
+ existingChildren,
+ returnFiber,
+ newIdx,
+ init(payload),
+ expirationTime,
+ );
+ }
}
if (isArray(newChild) || getIteratorFn(newChild)) {
@@ -772,6 +807,15 @@ function ChildReconciler(shouldTrackSideEffects) {
key,
);
break;
+ case REACT_LAZY_TYPE:
+ if (enableLazyElements) {
+ const payload = child._payload;
+ const init = (child._init: any);
+ warnOnInvalidKey(init(payload), knownKeys, returnFiber);
+ break;
+ }
+ // We intentionally fallthrough here if enableLazyElements is not on.
+ // eslint-disable-next-lined no-fallthrough
default:
break;
}
@@ -1349,6 +1393,18 @@ function ChildReconciler(shouldTrackSideEffects) {
expirationTime,
),
);
+ case REACT_LAZY_TYPE:
+ if (enableLazyElements) {
+ const payload = newChild._payload;
+ const init = newChild._init;
+ // TODO: This function is supposed to be non-recursive.
+ return reconcileChildFibers(
+ returnFiber,
+ currentFirstChild,
+ init(payload),
+ expirationTime,
+ );
+ }
}
}
diff --git a/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js b/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js
index 2f79cdf653ef5..202396660b3fd 100644
--- a/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js
+++ b/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js
@@ -1273,4 +1273,80 @@ describe('ReactLazy', () => {
expect(componentStackMessage).toContain('in Lazy');
});
+
+ // @gate enableLazyElements && enableNewReconciler
+ it('mount and reorder lazy elements', async () => {
+ class Child extends React.Component {
+ componentDidMount() {
+ Scheduler.unstable_yieldValue('Did mount: ' + this.props.label);
+ }
+ componentDidUpdate() {
+ Scheduler.unstable_yieldValue('Did update: ' + this.props.label);
+ }
+ render() {
+ return ;
+ }
+ }
+
+ const lazyChildA = lazy(() => {
+ Scheduler.unstable_yieldValue('Init A');
+ return fakeImport();
+ });
+ const lazyChildB = lazy(() => {
+ Scheduler.unstable_yieldValue('Init B');
+ return fakeImport();
+ });
+ const lazyChildA2 = lazy(() => {
+ Scheduler.unstable_yieldValue('Init A2');
+ return fakeImport();
+ });
+ const lazyChildB2 = lazy(() => {
+ Scheduler.unstable_yieldValue('Init B2');
+ return fakeImport();
+ });
+
+ function Parent({swap}) {
+ return (
+ }>
+ {swap ? [lazyChildB2, lazyChildA2] : [lazyChildA, lazyChildB]}
+
+ );
+ }
+
+ const root = ReactTestRenderer.create(, {
+ unstable_isConcurrent: true,
+ });
+
+ expect(Scheduler).toFlushAndYield(['Init A', 'Loading...']);
+ expect(root).not.toMatchRenderedOutput('AB');
+
+ await lazyChildA;
+ // We need to flush to trigger the B to load.
+ expect(Scheduler).toFlushAndYield(['Init B']);
+ await lazyChildB;
+
+ expect(Scheduler).toFlushAndYield([
+ 'A',
+ 'B',
+ 'Did mount: A',
+ 'Did mount: B',
+ ]);
+ expect(root).toMatchRenderedOutput('AB');
+
+ // Swap the position of A and B
+ root.update();
+ expect(Scheduler).toFlushAndYield(['Init B2', 'Loading...']);
+ await lazyChildB2;
+ // We need to flush to trigger the second one to load.
+ expect(Scheduler).toFlushAndYield(['Init A2', 'Loading...']);
+ await lazyChildA2;
+
+ expect(Scheduler).toFlushAndYield([
+ 'b',
+ 'a',
+ 'Did update: b',
+ 'Did update: a',
+ ]);
+ expect(root).toMatchRenderedOutput('ba');
+ });
});
diff --git a/packages/shared/ReactFeatureFlags.js b/packages/shared/ReactFeatureFlags.js
index 300bb5c859e0c..31042e493c2bb 100644
--- a/packages/shared/ReactFeatureFlags.js
+++ b/packages/shared/ReactFeatureFlags.js
@@ -41,6 +41,7 @@ export const enableSelectiveHydration = __EXPERIMENTAL__;
// Flight experiments
export const enableBlocksAPI = __EXPERIMENTAL__;
+export const enableLazyElements = __EXPERIMENTAL__;
// Only used in www builds.
export const enableSchedulerDebugging = false;
diff --git a/packages/shared/forks/ReactFeatureFlags.native-fb.js b/packages/shared/forks/ReactFeatureFlags.native-fb.js
index 375e5797627e2..7e72f53dc7e0b 100644
--- a/packages/shared/forks/ReactFeatureFlags.native-fb.js
+++ b/packages/shared/forks/ReactFeatureFlags.native-fb.js
@@ -18,6 +18,7 @@ export const enableSchedulerTracing = __PROFILE__;
export const enableSuspenseServerRenderer = false;
export const enableSelectiveHydration = false;
export const enableBlocksAPI = false;
+export const enableLazyElements = false;
export const enableSchedulerDebugging = false;
export const debugRenderPhaseSideEffectsForStrictMode = true;
export const disableJavaScriptURLs = false;
diff --git a/packages/shared/forks/ReactFeatureFlags.native-oss.js b/packages/shared/forks/ReactFeatureFlags.native-oss.js
index c0e300fa17707..2c598645be664 100644
--- a/packages/shared/forks/ReactFeatureFlags.native-oss.js
+++ b/packages/shared/forks/ReactFeatureFlags.native-oss.js
@@ -20,6 +20,7 @@ export const enableSchedulerTracing = __PROFILE__;
export const enableSuspenseServerRenderer = false;
export const enableSelectiveHydration = false;
export const enableBlocksAPI = false;
+export const enableLazyElements = false;
export const disableJavaScriptURLs = false;
export const disableInputAttributeSyncing = false;
export const enableSchedulerDebugging = false;
diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.js
index 5dfa2ebf3e043..c4f35fe9e9f97 100644
--- a/packages/shared/forks/ReactFeatureFlags.test-renderer.js
+++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.js
@@ -20,6 +20,7 @@ export const enableSchedulerTracing = __PROFILE__;
export const enableSuspenseServerRenderer = false;
export const enableSelectiveHydration = false;
export const enableBlocksAPI = false;
+export const enableLazyElements = false;
export const disableJavaScriptURLs = false;
export const disableInputAttributeSyncing = false;
export const enableSchedulerDebugging = false;
diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
index 16a1e8fabedb5..8e299769894ae 100644
--- a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
+++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
@@ -20,6 +20,7 @@ export const enableSchedulerTracing = __PROFILE__;
export const enableSuspenseServerRenderer = false;
export const enableSelectiveHydration = false;
export const enableBlocksAPI = false;
+export const enableLazyElements = false;
export const enableSchedulerDebugging = false;
export const disableJavaScriptURLs = false;
export const disableInputAttributeSyncing = false;
diff --git a/packages/shared/forks/ReactFeatureFlags.testing.js b/packages/shared/forks/ReactFeatureFlags.testing.js
index b034a559fc5e5..5080f0e2647a0 100644
--- a/packages/shared/forks/ReactFeatureFlags.testing.js
+++ b/packages/shared/forks/ReactFeatureFlags.testing.js
@@ -20,6 +20,7 @@ export const enableSchedulerTracing = __PROFILE__;
export const enableSuspenseServerRenderer = false;
export const enableSelectiveHydration = false;
export const enableBlocksAPI = false;
+export const enableLazyElements = false;
export const disableJavaScriptURLs = false;
export const disableInputAttributeSyncing = false;
export const enableSchedulerDebugging = false;
diff --git a/packages/shared/forks/ReactFeatureFlags.testing.www.js b/packages/shared/forks/ReactFeatureFlags.testing.www.js
index ae2ad698c3865..bd5442df091db 100644
--- a/packages/shared/forks/ReactFeatureFlags.testing.www.js
+++ b/packages/shared/forks/ReactFeatureFlags.testing.www.js
@@ -20,6 +20,7 @@ export const enableSchedulerTracing = false;
export const enableSuspenseServerRenderer = true;
export const enableSelectiveHydration = true;
export const enableBlocksAPI = true;
+export const enableLazyElements = false;
export const disableJavaScriptURLs = true;
export const disableInputAttributeSyncing = false;
export const enableSchedulerDebugging = false;
diff --git a/packages/shared/forks/ReactFeatureFlags.www.js b/packages/shared/forks/ReactFeatureFlags.www.js
index b9ee3f04086a8..21b9d663da653 100644
--- a/packages/shared/forks/ReactFeatureFlags.www.js
+++ b/packages/shared/forks/ReactFeatureFlags.www.js
@@ -50,6 +50,7 @@ export const enableSuspenseServerRenderer = true;
export const enableSelectiveHydration = true;
export const enableBlocksAPI = true;
+export const enableLazyElements = true;
export const disableJavaScriptURLs = true;