diff --git a/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js b/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js index b062ad8ab94be..769679c9a7013 100644 --- a/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js @@ -178,13 +178,13 @@ describe('ReactLazy', () => { await Promise.resolve(); + expect(Scheduler).toFlushAndThrow('Element type is invalid'); if (__DEV__) { - expect(console.error).toHaveBeenCalledTimes(1); + expect(console.error).toHaveBeenCalledTimes(3); expect(console.error.calls.argsFor(0)[0]).toContain( 'Expected the result of a dynamic import() call', ); } - expect(Scheduler).toFlushAndThrow('Element type is invalid'); }); it('throws if promise rejects', async () => { diff --git a/packages/react/src/ReactLazy.js b/packages/react/src/ReactLazy.js index 24156b69ac2d3..0bc05df6411cd 100644 --- a/packages/react/src/ReactLazy.js +++ b/packages/react/src/ReactLazy.js @@ -28,7 +28,7 @@ type PendingPayload = { type ResolvedPayload = { _status: 1, - _result: T, + _result: {default: T}, }; type RejectedPayload = { @@ -53,33 +53,21 @@ function lazyInitializer(payload: Payload): T { const ctor = payload._result; const thenable = ctor(); // Transition to the next state. - const pending: PendingPayload = (payload: any); - pending._status = Pending; - pending._result = thenable; + // This might throw either because it's missing or throws. If so, we treat it + // as still uninitialized and try again next time. Which is the same as what + // happens if the ctor or any wrappers processing the ctor throws. This might + // end up fixing it if the resolution was a concurrency bug. thenable.then( moduleObject => { - if (payload._status === Pending) { - const defaultExport = moduleObject.default; - if (__DEV__) { - if (defaultExport === undefined) { - console.error( - 'lazy: Expected the result of a dynamic import() call. ' + - 'Instead received: %s\n\nYour code should look like: \n ' + - // Break up imports to avoid accidentally parsing them as dependencies. - 'const MyComponent = lazy(() => imp' + - "ort('./MyComponent'))", - moduleObject, - ); - } - } + if (payload._status === Pending || payload._status === Uninitialized) { // Transition to the next state. const resolved: ResolvedPayload = (payload: any); resolved._status = Resolved; - resolved._result = defaultExport; + resolved._result = moduleObject; } }, error => { - if (payload._status === Pending) { + if (payload._status === Pending || payload._status === Uninitialized) { // Transition to the next state. const rejected: RejectedPayload = (payload: any); rejected._status = Rejected; @@ -87,9 +75,42 @@ function lazyInitializer(payload: Payload): T { } }, ); + if (payload._status === Uninitialized) { + // In case, we're still uninitialized, then we're waiting for the thenable + // to resolve. Set it as pending in the meantime. + const pending: PendingPayload = (payload: any); + pending._status = Pending; + pending._result = thenable; + } } if (payload._status === Resolved) { - return payload._result; + const moduleObject = payload._result; + if (__DEV__) { + if (moduleObject === undefined) { + console.error( + 'lazy: Expected the result of a dynamic import() call. ' + + 'Instead received: %s\n\nYour code should look like: \n ' + + // Break up imports to avoid accidentally parsing them as dependencies. + 'const MyComponent = lazy(() => imp' + + "ort('./MyComponent'))\n\n" + + 'Did you accidentally put curly braces around the import?', + moduleObject, + ); + } + } + if (__DEV__) { + if (!('default' in moduleObject)) { + console.error( + 'lazy: Expected the result of a dynamic import() call. ' + + 'Instead received: %s\n\nYour code should look like: \n ' + + // Break up imports to avoid accidentally parsing them as dependencies. + 'const MyComponent = lazy(() => imp' + + "ort('./MyComponent'))", + moduleObject, + ); + } + } + return moduleObject.default; } else { throw payload._result; }