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;