diff --git a/packages/react-dom/src/__tests__/ReactFresh-test.internal.js b/packages/react-dom/src/__tests__/ReactFresh-test.internal.js
index ec5c733494..b92c2d6c44 100644
--- a/packages/react-dom/src/__tests__/ReactFresh-test.internal.js
+++ b/packages/react-dom/src/__tests__/ReactFresh-test.internal.js
@@ -897,7 +897,7 @@ describe('ReactFresh', () => {
it('can preserve state for lazy after resolution', async () => {
if (__DEV__) {
- let OuterV1 = render(() => {
+ let AppV1 = render(() => {
function Hello() {
const [val, setVal] = React.useState(0);
return (
@@ -923,6 +923,7 @@ describe('ReactFresh', () => {
);
}
+ __register__(App, 'App');
return App;
});
@@ -943,7 +944,7 @@ describe('ReactFresh', () => {
expect(el.textContent).toBe('1');
// Perform a hot update.
- patch(() => {
+ let AppV2 = patch(() => {
function Hello() {
const [val, setVal] = React.useState(0);
return (
@@ -953,6 +954,25 @@ describe('ReactFresh', () => {
);
}
__register__(Hello, 'Hello');
+
+ const Outer = React.lazy(
+ () =>
+ new Promise(resolve => {
+ setTimeout(() => resolve({default: Hello}), 100);
+ }),
+ );
+ __register__(Outer, 'Outer');
+
+ function App() {
+ return (
+ Loading
}>
+
+
+ );
+ }
+ __register__(App, 'App');
+
+ return App;
});
// Assert the state was preserved but color changed.
@@ -968,8 +988,12 @@ describe('ReactFresh', () => {
expect(el.textContent).toBe('2');
expect(el.style.color).toBe('red');
- // Perform a top-down render with a stale type.
- render(() => OuterV1);
+ // Perform top-down renders with both fresh and stale types.
+ // Neither should change the state or color.
+ // They should always resolve to the latest version.
+ render(() => AppV1);
+ render(() => AppV2);
+ render(() => AppV1);
expect(container.firstChild).toBe(el);
expect(el.textContent).toBe('2');
expect(el.style.color).toBe('red');
@@ -987,7 +1011,17 @@ describe('ReactFresh', () => {
__register__(Hello, 'Hello');
// Note: no lazy wrapper this time.
- return Hello;
+
+ function App() {
+ return (
+ Loading}>
+
+
+ );
+ }
+ __register__(App, 'App');
+
+ return App;
});
expect(container.firstChild).not.toBe(el);
@@ -2053,6 +2087,18 @@ describe('ReactFresh', () => {
}
});
+ it('can remount on signature change within a lazy simple memo wrapper', () => {
+ if (__DEV__) {
+ testRemountingWithWrapper(Hello =>
+ React.lazy(() => ({
+ then(cb) {
+ cb({default: React.memo(Hello)});
+ },
+ })),
+ );
+ }
+ });
+
it('can remount on signature change within forwardRef', () => {
if (__DEV__) {
testRemountingWithWrapper(Hello => React.forwardRef(Hello));
diff --git a/packages/react-reconciler/src/ReactFiberHotReloading.js b/packages/react-reconciler/src/ReactFiberHotReloading.js
index c6a7d2e0db..886d63d4a0 100644
--- a/packages/react-reconciler/src/ReactFiberHotReloading.js
+++ b/packages/react-reconciler/src/ReactFiberHotReloading.js
@@ -23,7 +23,11 @@ import {
MemoComponent,
SimpleMemoComponent,
} from 'shared/ReactWorkTags';
-import {REACT_FORWARD_REF_TYPE, REACT_MEMO_TYPE} from 'shared/ReactSymbols';
+import {
+ REACT_FORWARD_REF_TYPE,
+ REACT_MEMO_TYPE,
+ REACT_LAZY_TYPE,
+} from 'shared/ReactSymbols';
type Family = {|
currentType: any,
@@ -108,39 +112,47 @@ export function isCompatibleFamilyForHotReloading(
const prevType = fiber.elementType;
const nextType = element.type;
+
// If we got here, we know types aren't === equal.
let needsCompareFamilies = false;
+
+ const $$typeofNextType =
+ typeof nextType === 'object' && nextType !== null
+ ? nextType.$$typeof
+ : null;
+
switch (fiber.tag) {
case FunctionComponent: {
if (typeof nextType === 'function') {
needsCompareFamilies = true;
+ } else if ($$typeofNextType === REACT_LAZY_TYPE) {
+ // We don't know the inner type yet.
+ // We're going to assume that the lazy inner type is stable,
+ // and so it is sufficient to avoid reconciling it away.
+ // We're not going to unwrap or actually use the new lazy type.
+ needsCompareFamilies = true;
}
break;
}
case ForwardRef: {
- if (
- typeof nextType === 'object' &&
- nextType !== null &&
- nextType.$$typeof === REACT_FORWARD_REF_TYPE
- ) {
+ if ($$typeofNextType === REACT_FORWARD_REF_TYPE) {
+ needsCompareFamilies = true;
+ } else if ($$typeofNextType === REACT_LAZY_TYPE) {
needsCompareFamilies = true;
}
break;
}
case MemoComponent:
case SimpleMemoComponent: {
- if (
- typeof nextType === 'object' &&
- nextType !== null &&
- nextType.$$typeof === REACT_MEMO_TYPE
- ) {
+ if ($$typeofNextType === REACT_MEMO_TYPE) {
// TODO: if it was but can no longer be simple,
// we shouldn't set this.
needsCompareFamilies = true;
+ } else if ($$typeofNextType === REACT_LAZY_TYPE) {
+ needsCompareFamilies = true;
}
break;
}
- // TODO: maybe support lazy?
default:
return false;
}