Bugfix for #13886 (#13896)
Fixes a bug where a lazy component does not cache the result of its constructor.
Andrew Clark committed
Oct 19, 2018 at 13:57 UTC
36db538226c45fc427abe4438851854892ee75c4
2 files changed
+66
-15
packages/react-reconciler/src/ReactFiberLazyComponent.js
+14
-7
@@ -7,21 +7,27 @@
7
* @flow
8
*/
9
10
-import type {LazyComponent} from 'shared/ReactLazyComponent';
10
+import type {LazyComponent, Thenable} from 'shared/ReactLazyComponent';
11
12
import {Resolved, Rejected, Pending} from 'shared/ReactLazyComponent';
13
import warning from 'shared/warning';
14
15
export function readLazyComponentType<T>(lazyComponent: LazyComponent<T>): T {
16
const status = lazyComponent._status;
17
+ const result = lazyComponent._result;
18
switch (status) {
18
- case Resolved:
19
- const Component: T = lazyComponent._result;
19
+ case Resolved: {
20
+ const Component: T = result;
21
return Component;
21
- case Rejected:
22
- throw lazyComponent._result;
23
- case Pending:
24
- throw lazyComponent;
22
+ }
23
+ case Rejected: {
24
+ const error: mixed = result;
25
+ throw error;
26
+ }
27
+ case Pending: {
28
+ const thenable: Thenable<T, mixed> = result;
29
+ throw thenable;
30
+ }
31
default: {
32
lazyComponent._status = Pending;
33
const ctor = lazyComponent._ctor;
@@ -52,6 +58,7 @@ export function readLazyComponentType<T>(lazyComponent: LazyComponent<T>): T {
58
}
59
},
60
);
61
+ lazyComponent._result = thenable;
62
throw thenable;
63
}
64
}
packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js
+52
-8
@@ -21,6 +21,10 @@ describe('ReactLazy', () => {
21
return props.text;
22
}
23
24
+ function delay(ms) {
25
+ return new Promise(resolve => setTimeout(() => resolve(), ms));
26
+ }
27
+
28
async function fakeImport(result) {
29
return {default: result};
30
}
@@ -40,7 +44,7 @@ describe('ReactLazy', () => {
44
expect(root).toFlushAndYield(['Loading...']);
45
expect(root).toMatchRenderedOutput(null);
46
43
- await LazyText;
47
+ await Promise.resolve();
48
49
expect(root).toFlushAndYield(['Hi']);
50
expect(root).toMatchRenderedOutput('Hi');
@@ -55,6 +59,47 @@ describe('ReactLazy', () => {
59
expect(root).toMatchRenderedOutput('Hi again');
60
});
61
62
+ it('multiple lazy components', async () => {
63
+ function Foo() {
64
+ return <Text text="Foo" />;
65
+ }
66
+
67
+ function Bar() {
68
+ return <Text text="Bar" />;
69
+ }
70
+
71
+ const promiseForFoo = delay(1000).then(() => fakeImport(Foo));
72
+ const promiseForBar = delay(2000).then(() => fakeImport(Bar));
73
+
74
+ const LazyFoo = lazy(() => promiseForFoo);
75
+ const LazyBar = lazy(() => promiseForBar);
76
+
77
+ const root = ReactTestRenderer.create(
78
+ <Suspense fallback={<Text text="Loading..." />}>
79
+ <LazyFoo />
80
+ <LazyBar />
81
+ </Suspense>,
82
+ {
83
+ unstable_isConcurrent: true,
84
+ },
85
+ );
86
+
87
+ expect(root).toFlushAndYield(['Loading...']);
88
+ expect(root).toMatchRenderedOutput(null);
89
+
90
+ jest.advanceTimersByTime(1000);
91
+ await promiseForFoo;
92
+
93
+ expect(root).toFlushAndYield(['Foo', 'Loading...']);
94
+ expect(root).toMatchRenderedOutput(null);
95
+
96
+ jest.advanceTimersByTime(1000);
97
+ await promiseForBar;
98
+
99
+ expect(root).toFlushAndYield(['Foo', 'Bar']);
100
+ expect(root).toMatchRenderedOutput('FooBar');
101
+ });
102
+
103
it('does not support arbitrary promises, only module objects', async () => {
104
spyOnDev(console, 'error');
105
@@ -71,7 +116,7 @@ describe('ReactLazy', () => {
116
expect(root).toFlushAndYield(['Loading...']);
117
expect(root).toMatchRenderedOutput(null);
118
74
- await LazyText;
119
+ await Promise.resolve();
120
121
if (__DEV__) {
122
expect(console.error).toHaveBeenCalledTimes(1);
@@ -100,7 +145,7 @@ describe('ReactLazy', () => {
145
expect(root).toMatchRenderedOutput(null);
146
147
try {
103
- await LazyText;
148
+ await Promise.resolve();
149
} catch (e) {}
150
151
expect(root).toFlushAndThrow('Bad network');
@@ -176,7 +221,7 @@ describe('ReactLazy', () => {
221
expect(root).toFlushAndYield(['Loading...']);
222
expect(root).toMatchRenderedOutput(null);
223
179
- await LazyText;
224
+ await Promise.resolve();
225
226
expect(root).toFlushAndYield(['Hi']);
227
expect(root).toMatchRenderedOutput('Hi');
@@ -226,7 +271,7 @@ describe('ReactLazy', () => {
271
expect(root).toFlushAndYield(['Loading...']);
272
expect(root).toMatchRenderedOutput(null);
273
229
- await Lazy;
274
+ await Promise.resolve();
275
276
expect(root).toFlushAndYield(['Lazy', 'Sibling', 'A']);
277
expect(root).toMatchRenderedOutput('SiblingA');
@@ -256,7 +301,7 @@ describe('ReactLazy', () => {
301
expect(root).toFlushAndYield(['Started loading', 'Loading...']);
302
expect(root).toMatchRenderedOutput(null);
303
259
- await LazyFoo;
304
+ await Promise.resolve();
305
306
expect(() => {
307
expect(root).toFlushAndYield(['A', 'B']);
@@ -303,8 +348,7 @@ describe('ReactLazy', () => {
348
expect(root).toMatchRenderedOutput(null);
349
expect(ref.current).toBe(null);
350
306
- await LazyClass;
307
- await LazyForwardRef;
351
+ await Promise.resolve();
352
353
expect(root).toFlushAndYield(['Foo', 'forwardRef', 'Bar']);
354
expect(root).toMatchRenderedOutput('FooBar');