diff --git a/packages/react-reconciler/src/ReactChildFiber.new.js b/packages/react-reconciler/src/ReactChildFiber.new.js
index 601f3b21e4395..1e8d9a7fa18cd 100644
--- a/packages/react-reconciler/src/ReactChildFiber.new.js
+++ b/packages/react-reconciler/src/ReactChildFiber.new.js
@@ -246,6 +246,12 @@ function warnOnFunctionType(returnFiber: Fiber) {
}
}
+function resolveLazy(lazyType) {
+ const payload = lazyType._payload;
+ const init = lazyType._init;
+ return init(payload);
+}
+
// This wrapper function exists because I expect to clone the code in each path
// to be able to optimize each path individually by branching early. This needs
// a compiler or we can do it manually. Helpers that don't need this branching
@@ -383,9 +389,24 @@ function ChildReconciler(shouldTrackSideEffects) {
element: ReactElement,
lanes: Lanes,
): Fiber {
+ const elementType = element.type;
+ if (elementType === REACT_FRAGMENT_TYPE) {
+ return updateFragment(
+ returnFiber,
+ current,
+ element.props.children,
+ lanes,
+ element.key,
+ );
+ }
if (current !== null) {
if (
- current.elementType === element.type ||
+ current.elementType === elementType ||
+ (enableLazyElements &&
+ typeof elementType === 'object' &&
+ elementType !== null &&
+ elementType.$$typeof === REACT_LAZY_TYPE &&
+ resolveLazy(elementType) === current.type) ||
// Keep this check inline so it only runs on the false path:
(__DEV__ ? isCompatibleFamilyForHotReloading(current, element) : false)
) {
@@ -551,15 +572,6 @@ function ChildReconciler(shouldTrackSideEffects) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE: {
if (newChild.key === key) {
- if (newChild.type === REACT_FRAGMENT_TYPE) {
- return updateFragment(
- returnFiber,
- oldFiber,
- newChild.props.children,
- lanes,
- key,
- );
- }
return updateElement(returnFiber, oldFiber, newChild, lanes);
} else {
return null;
@@ -622,15 +634,6 @@ function ChildReconciler(shouldTrackSideEffects) {
existingChildren.get(
newChild.key === null ? newIdx : newChild.key,
) || null;
- if (newChild.type === REACT_FRAGMENT_TYPE) {
- return updateFragment(
- returnFiber,
- matchedFiber,
- newChild.props.children,
- lanes,
- newChild.key,
- );
- }
return updateElement(returnFiber, matchedFiber, newChild, lanes);
}
case REACT_PORTAL_TYPE: {
@@ -1101,39 +1104,40 @@ function ChildReconciler(shouldTrackSideEffects) {
// TODO: If key === null and child.key === null, then this only applies to
// the first item in the list.
if (child.key === key) {
- switch (child.tag) {
- case Fragment: {
- if (element.type === REACT_FRAGMENT_TYPE) {
- deleteRemainingChildren(returnFiber, child.sibling);
- const existing = useFiber(child, element.props.children);
- existing.return = returnFiber;
- if (__DEV__) {
- existing._debugSource = element._source;
- existing._debugOwner = element._owner;
- }
- return existing;
+ let elementType = element.type;
+ if (elementType === REACT_FRAGMENT_TYPE) {
+ if (child.tag === Fragment) {
+ deleteRemainingChildren(returnFiber, child.sibling);
+ const existing = useFiber(child, element.props.children);
+ existing.return = returnFiber;
+ if (__DEV__) {
+ existing._debugSource = element._source;
+ existing._debugOwner = element._owner;
}
- break;
+ return existing;
}
- default: {
- if (
- child.elementType === element.type ||
- // Keep this check inline so it only runs on the false path:
- (__DEV__
- ? isCompatibleFamilyForHotReloading(child, element)
- : false)
- ) {
- deleteRemainingChildren(returnFiber, child.sibling);
- const existing = useFiber(child, element.props);
- existing.ref = coerceRef(returnFiber, child, element);
- existing.return = returnFiber;
- if (__DEV__) {
- existing._debugSource = element._source;
- existing._debugOwner = element._owner;
- }
- return existing;
+ } else {
+ if (
+ child.elementType === elementType ||
+ (enableLazyElements &&
+ typeof elementType === 'object' &&
+ elementType !== null &&
+ elementType.$$typeof === REACT_LAZY_TYPE &&
+ resolveLazy(elementType) === child.type) ||
+ // Keep this check inline so it only runs on the false path:
+ (__DEV__
+ ? isCompatibleFamilyForHotReloading(child, element)
+ : false)
+ ) {
+ deleteRemainingChildren(returnFiber, child.sibling);
+ const existing = useFiber(child, element.props);
+ existing.ref = coerceRef(returnFiber, child, element);
+ existing.return = returnFiber;
+ if (__DEV__) {
+ existing._debugSource = element._source;
+ existing._debugOwner = element._owner;
}
- break;
+ return existing;
}
}
// Didn't match.
diff --git a/packages/react-reconciler/src/ReactChildFiber.old.js b/packages/react-reconciler/src/ReactChildFiber.old.js
index adb9c0418df0e..cde52aef0f6ab 100644
--- a/packages/react-reconciler/src/ReactChildFiber.old.js
+++ b/packages/react-reconciler/src/ReactChildFiber.old.js
@@ -246,6 +246,12 @@ function warnOnFunctionType(returnFiber: Fiber) {
}
}
+function resolveLazy(lazyType) {
+ const payload = lazyType._payload;
+ const init = lazyType._init;
+ return init(payload);
+}
+
// This wrapper function exists because I expect to clone the code in each path
// to be able to optimize each path individually by branching early. This needs
// a compiler or we can do it manually. Helpers that don't need this branching
@@ -383,9 +389,24 @@ function ChildReconciler(shouldTrackSideEffects) {
element: ReactElement,
lanes: Lanes,
): Fiber {
+ const elementType = element.type;
+ if (elementType === REACT_FRAGMENT_TYPE) {
+ return updateFragment(
+ returnFiber,
+ current,
+ element.props.children,
+ lanes,
+ element.key,
+ );
+ }
if (current !== null) {
if (
- current.elementType === element.type ||
+ current.elementType === elementType ||
+ (enableLazyElements &&
+ typeof elementType === 'object' &&
+ elementType !== null &&
+ elementType.$$typeof === REACT_LAZY_TYPE &&
+ resolveLazy(elementType) === current.type) ||
// Keep this check inline so it only runs on the false path:
(__DEV__ ? isCompatibleFamilyForHotReloading(current, element) : false)
) {
@@ -551,15 +572,6 @@ function ChildReconciler(shouldTrackSideEffects) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE: {
if (newChild.key === key) {
- if (newChild.type === REACT_FRAGMENT_TYPE) {
- return updateFragment(
- returnFiber,
- oldFiber,
- newChild.props.children,
- lanes,
- key,
- );
- }
return updateElement(returnFiber, oldFiber, newChild, lanes);
} else {
return null;
@@ -622,15 +634,6 @@ function ChildReconciler(shouldTrackSideEffects) {
existingChildren.get(
newChild.key === null ? newIdx : newChild.key,
) || null;
- if (newChild.type === REACT_FRAGMENT_TYPE) {
- return updateFragment(
- returnFiber,
- matchedFiber,
- newChild.props.children,
- lanes,
- newChild.key,
- );
- }
return updateElement(returnFiber, matchedFiber, newChild, lanes);
}
case REACT_PORTAL_TYPE: {
@@ -1101,39 +1104,40 @@ function ChildReconciler(shouldTrackSideEffects) {
// TODO: If key === null and child.key === null, then this only applies to
// the first item in the list.
if (child.key === key) {
- switch (child.tag) {
- case Fragment: {
- if (element.type === REACT_FRAGMENT_TYPE) {
- deleteRemainingChildren(returnFiber, child.sibling);
- const existing = useFiber(child, element.props.children);
- existing.return = returnFiber;
- if (__DEV__) {
- existing._debugSource = element._source;
- existing._debugOwner = element._owner;
- }
- return existing;
+ const elementType = element.type;
+ if (elementType === REACT_FRAGMENT_TYPE) {
+ if (child.tag === Fragment) {
+ deleteRemainingChildren(returnFiber, child.sibling);
+ const existing = useFiber(child, element.props.children);
+ existing.return = returnFiber;
+ if (__DEV__) {
+ existing._debugSource = element._source;
+ existing._debugOwner = element._owner;
}
- break;
+ return existing;
}
- default: {
- if (
- child.elementType === element.type ||
- // Keep this check inline so it only runs on the false path:
- (__DEV__
- ? isCompatibleFamilyForHotReloading(child, element)
- : false)
- ) {
- deleteRemainingChildren(returnFiber, child.sibling);
- const existing = useFiber(child, element.props);
- existing.ref = coerceRef(returnFiber, child, element);
- existing.return = returnFiber;
- if (__DEV__) {
- existing._debugSource = element._source;
- existing._debugOwner = element._owner;
- }
- return existing;
+ } else {
+ if (
+ child.elementType === elementType ||
+ (enableLazyElements &&
+ typeof elementType === 'object' &&
+ elementType !== null &&
+ elementType.$$typeof === REACT_LAZY_TYPE &&
+ resolveLazy(elementType) === child.type) ||
+ // Keep this check inline so it only runs on the false path:
+ (__DEV__
+ ? isCompatibleFamilyForHotReloading(child, element)
+ : false)
+ ) {
+ deleteRemainingChildren(returnFiber, child.sibling);
+ const existing = useFiber(child, element.props);
+ existing.ref = coerceRef(returnFiber, child, element);
+ existing.return = returnFiber;
+ if (__DEV__) {
+ existing._debugSource = element._source;
+ existing._debugOwner = element._owner;
}
- break;
+ return existing;
}
}
// Didn't match.
diff --git a/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js b/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js
index 55c15e7412eab..80ab9b132de27 100644
--- a/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js
+++ b/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js
@@ -1268,6 +1268,93 @@ describe('ReactLazy', () => {
expect(componentStackMessage).toContain('in Lazy');
});
+ // @gate enableLazyElements
+ it('mount and reorder lazy types', 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 ;
+ }
+ }
+
+ function ChildA({lowerCase}) {
+ return ;
+ }
+
+ function ChildB({lowerCase}) {
+ return ;
+ }
+
+ const LazyChildA = lazy(() => {
+ Scheduler.unstable_yieldValue('Init A');
+ return fakeImport(ChildA);
+ });
+ const LazyChildB = lazy(() => {
+ Scheduler.unstable_yieldValue('Init B');
+ return fakeImport(ChildB);
+ });
+ const LazyChildA2 = lazy(() => {
+ Scheduler.unstable_yieldValue('Init A2');
+ return fakeImport(ChildA);
+ });
+ const LazyChildB2 = lazy(() => {
+ Scheduler.unstable_yieldValue('Init B2');
+ return fakeImport(ChildB);
+ });
+
+ function Parent({swap}) {
+ return (
+ }>
+ {swap
+ ? [
+ ,
+ ,
+ ]
+ : [, ]}
+
+ );
+ }
+
+ const root = ReactTestRenderer.create(, {
+ unstable_isConcurrent: true,
+ });
+
+ expect(Scheduler).toFlushAndYield(['Init A', 'Init B', 'Loading...']);
+ expect(root).not.toMatchRenderedOutput('AB');
+
+ await LazyChildA;
+ 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');
+ });
+
// @gate enableLazyElements
it('mount and reorder lazy elements', async () => {
class Child extends React.Component {