[Fresh] Support re-rendering lazy() without losing state (#15686)
* Support re-rendering lazy() without losing state * Clearer naming
Dan Abramov committed
May 20, 2019 at 15:47 UTC
0bd9b5d006bec02b7f4149a618e5433f1cc6c6bd
2 files changed
+75
-17
packages/react-dom/src/__tests__/ReactFresh-test.internal.js
+51
-5
@@ -897,7 +897,7 @@ describe('ReactFresh', () => {
897
898
it('can preserve state for lazy after resolution', async () => {
899
if (__DEV__) {
900
- let OuterV1 = render(() => {
900
+ let AppV1 = render(() => {
901
function Hello() {
902
const [val, setVal] = React.useState(0);
903
return (
@@ -923,6 +923,7 @@ describe('ReactFresh', () => {
923
</React.Suspense>
924
);
925
}
926
+ __register__(App, 'App');
927
928
return App;
929
});
@@ -943,7 +944,7 @@ describe('ReactFresh', () => {
944
expect(el.textContent).toBe('1');
945
946
// Perform a hot update.
946
- patch(() => {
947
+ let AppV2 = patch(() => {
948
function Hello() {
949
const [val, setVal] = React.useState(0);
950
return (
@@ -953,6 +954,25 @@ describe('ReactFresh', () => {
954
);
955
}
956
__register__(Hello, 'Hello');
957
+
958
+ const Outer = React.lazy(
959
+ () =>
960
+ new Promise(resolve => {
961
+ setTimeout(() => resolve({default: Hello}), 100);
962
+ }),
963
+ );
964
+ __register__(Outer, 'Outer');
965
+
966
+ function App() {
967
+ return (
968
+ <React.Suspense fallback={<p>Loading</p>}>
969
+ <Outer />
970
+ </React.Suspense>
971
+ );
972
+ }
973
+ __register__(App, 'App');
974
+
975
+ return App;
976
});
977
978
// Assert the state was preserved but color changed.
@@ -968,8 +988,12 @@ describe('ReactFresh', () => {
988
expect(el.textContent).toBe('2');
989
expect(el.style.color).toBe('red');
990
971
- // Perform a top-down render with a stale type.
972
- render(() => OuterV1);
991
+ // Perform top-down renders with both fresh and stale types.
992
+ // Neither should change the state or color.
993
+ // They should always resolve to the latest version.
994
+ render(() => AppV1);
995
+ render(() => AppV2);
996
+ render(() => AppV1);
997
expect(container.firstChild).toBe(el);
998
expect(el.textContent).toBe('2');
999
expect(el.style.color).toBe('red');
@@ -987,7 +1011,17 @@ describe('ReactFresh', () => {
1011
__register__(Hello, 'Hello');
1012
1013
// Note: no lazy wrapper this time.
990
- return Hello;
1014
+
1015
+ function App() {
1016
+ return (
1017
+ <React.Suspense fallback={<p>Loading</p>}>
1018
+ <Hello />
1019
+ </React.Suspense>
1020
+ );
1021
+ }
1022
+ __register__(App, 'App');
1023
+
1024
+ return App;
1025
});
1026
1027
expect(container.firstChild).not.toBe(el);
@@ -2053,6 +2087,18 @@ describe('ReactFresh', () => {
2087
}
2088
});
2089
2090
+ it('can remount on signature change within a lazy simple memo wrapper', () => {
2091
+ if (__DEV__) {
2092
+ testRemountingWithWrapper(Hello =>
2093
+ React.lazy(() => ({
2094
+ then(cb) {
2095
+ cb({default: React.memo(Hello)});
2096
+ },
2097
+ })),
2098
+ );
2099
+ }
2100
+ });
2101
+
2102
it('can remount on signature change within forwardRef', () => {
2103
if (__DEV__) {
2104
testRemountingWithWrapper(Hello => React.forwardRef(Hello));
packages/react-reconciler/src/ReactFiberHotReloading.js
+24
-12
@@ -23,7 +23,11 @@ import {
23
MemoComponent,
24
SimpleMemoComponent,
25
} from 'shared/ReactWorkTags';
26
-import {REACT_FORWARD_REF_TYPE, REACT_MEMO_TYPE} from 'shared/ReactSymbols';
26
+import {
27
+ REACT_FORWARD_REF_TYPE,
28
+ REACT_MEMO_TYPE,
29
+ REACT_LAZY_TYPE,
30
+} from 'shared/ReactSymbols';
31
32
type Family = {|
33
currentType: any,
@@ -108,39 +112,47 @@ export function isCompatibleFamilyForHotReloading(
112
113
const prevType = fiber.elementType;
114
const nextType = element.type;
115
+
116
// If we got here, we know types aren't === equal.
117
let needsCompareFamilies = false;
118
+
119
+ const $$typeofNextType =
120
+ typeof nextType === 'object' && nextType !== null
121
+ ? nextType.$$typeof
122
+ : null;
123
+
124
switch (fiber.tag) {
125
case FunctionComponent: {
126
if (typeof nextType === 'function') {
127
needsCompareFamilies = true;
128
+ } else if ($$typeofNextType === REACT_LAZY_TYPE) {
129
+ // We don't know the inner type yet.
130
+ // We're going to assume that the lazy inner type is stable,
131
+ // and so it is sufficient to avoid reconciling it away.
132
+ // We're not going to unwrap or actually use the new lazy type.
133
+ needsCompareFamilies = true;
134
}
135
break;
136
}
137
case ForwardRef: {
121
- if (
122
- typeof nextType === 'object' &&
123
- nextType !== null &&
124
- nextType.$$typeof === REACT_FORWARD_REF_TYPE
125
- ) {
138
+ if ($$typeofNextType === REACT_FORWARD_REF_TYPE) {
139
+ needsCompareFamilies = true;
140
+ } else if ($$typeofNextType === REACT_LAZY_TYPE) {
141
needsCompareFamilies = true;
142
}
143
break;
144
}
145
case MemoComponent:
146
case SimpleMemoComponent: {
132
- if (
133
- typeof nextType === 'object' &&
134
- nextType !== null &&
135
- nextType.$$typeof === REACT_MEMO_TYPE
136
- ) {
147
+ if ($$typeofNextType === REACT_MEMO_TYPE) {
148
// TODO: if it was but can no longer be simple,
149
// we shouldn't set this.
150
needsCompareFamilies = true;
151
+ } else if ($$typeofNextType === REACT_LAZY_TYPE) {
152
+ needsCompareFamilies = true;
153
}
154
break;
155
}
143
- // TODO: maybe support lazy?
156
default:
157
return false;
158
}