diff --git a/packages/react-dom/src/__tests__/ReactServerRendering-test.js b/packages/react-dom/src/__tests__/ReactServerRendering-test.js
index 27b30f9bb03e2..f15cb5557e5c8 100644
--- a/packages/react-dom/src/__tests__/ReactServerRendering-test.js
+++ b/packages/react-dom/src/__tests__/ReactServerRendering-test.js
@@ -582,11 +582,6 @@ describe('ReactDOMServer', () => {
);
ReactDOMServer.renderToString();
}).toThrow('ReactDOMServer does not yet support lazy-loaded components.');
-
- expect(() => {
- const FooPromise = {then() {}};
- ReactDOMServer.renderToString();
- }).toThrow('ReactDOMServer does not yet support lazy-loaded components.');
});
it('should throw (in dev) when children are mutated during render', () => {
diff --git a/packages/react-dom/src/__tests__/ReactServerRenderingHydration-test.js b/packages/react-dom/src/__tests__/ReactServerRenderingHydration-test.js
index 5d1ad4eb526ca..17b8932e6973c 100644
--- a/packages/react-dom/src/__tests__/ReactServerRenderingHydration-test.js
+++ b/packages/react-dom/src/__tests__/ReactServerRenderingHydration-test.js
@@ -444,15 +444,18 @@ describe('ReactDOMServerHydration', () => {
});
it('should be able to use lazy components after hydrating', async () => {
- const Lazy = new Promise(resolve => {
- setTimeout(
- () =>
- resolve(function World() {
- return 'world';
- }),
- 1000,
- );
- });
+ const Lazy = React.lazy(
+ () =>
+ new Promise(resolve => {
+ setTimeout(
+ () =>
+ resolve(function World() {
+ return 'world';
+ }),
+ 1000,
+ );
+ }),
+ );
class HelloWorld extends React.Component {
state = {isClient: false};
componentDidMount() {
diff --git a/packages/react-reconciler/src/ReactFiber.js b/packages/react-reconciler/src/ReactFiber.js
index 3f508efe63f2d..3ad8fccdcddfa 100644
--- a/packages/react-reconciler/src/ReactFiber.js
+++ b/packages/react-reconciler/src/ReactFiber.js
@@ -60,6 +60,7 @@ import {
REACT_CONCURRENT_MODE_TYPE,
REACT_SUSPENSE_TYPE,
REACT_PURE_TYPE,
+ REACT_LAZY_TYPE,
} from 'shared/ReactSymbols';
let hasBadMapPolyfill;
@@ -461,12 +462,9 @@ export function createFiberFromElement(
case REACT_PURE_TYPE:
fiberTag = PureComponent;
break getTag;
- default: {
- if (typeof type.then === 'function') {
- fiberTag = IndeterminateComponent;
- break getTag;
- }
- }
+ case REACT_LAZY_TYPE:
+ fiberTag = IndeterminateComponent;
+ break getTag;
}
}
let info = '';
diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.js b/packages/react-reconciler/src/ReactFiberBeginWork.js
index 36fe0dc1dec7d..08687d4f941fd 100644
--- a/packages/react-reconciler/src/ReactFiberBeginWork.js
+++ b/packages/react-reconciler/src/ReactFiberBeginWork.js
@@ -110,6 +110,7 @@ import {
createFiberFromFragment,
createWorkInProgress,
} from './ReactFiber';
+import {REACT_LAZY_TYPE} from 'shared/ReactSymbols';
const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
@@ -729,7 +730,7 @@ function mountIndeterminateComponent(
if (
typeof Component === 'object' &&
Component !== null &&
- typeof Component.then === 'function'
+ Component.$$typeof === REACT_LAZY_TYPE
) {
// We can't start a User Timing measurement with correct label yet.
// Cancel and resume right after we know the tag.
diff --git a/packages/react-reconciler/src/ReactFiberLazyComponent.js b/packages/react-reconciler/src/ReactFiberLazyComponent.js
index 05d9d193c9268..8595e0e2a3cfd 100644
--- a/packages/react-reconciler/src/ReactFiberLazyComponent.js
+++ b/packages/react-reconciler/src/ReactFiberLazyComponent.js
@@ -7,26 +7,28 @@
* @flow
*/
-import type {Thenable} from 'shared/ReactLazyComponent';
+import type {LazyComponentThenable} from 'shared/ReactLazyComponent';
import {Resolved, Rejected, Pending} from 'shared/ReactLazyComponent';
-export function readLazyComponentType(thenable: Thenable): T {
- const status = thenable._reactStatus;
+export function readLazyComponentType(
+ thenable: LazyComponentThenable,
+): T {
+ const status = thenable._status;
switch (status) {
case Resolved:
- const Component: T = thenable._reactResult;
+ const Component: T = thenable._result;
return Component;
case Rejected:
- throw thenable._reactResult;
+ throw thenable._result;
case Pending:
throw thenable;
default: {
- thenable._reactStatus = Pending;
+ thenable._status = Pending;
thenable.then(
resolvedValue => {
- if (thenable._reactStatus === Pending) {
- thenable._reactStatus = Resolved;
+ if (thenable._status === Pending) {
+ thenable._status = Resolved;
if (typeof resolvedValue === 'object' && resolvedValue !== null) {
// If the `default` property is not empty, assume it's the result
// of an async import() and use that. Otherwise, use the
@@ -39,13 +41,13 @@ export function readLazyComponentType(thenable: Thenable): T {
} else {
resolvedValue = resolvedValue;
}
- thenable._reactResult = resolvedValue;
+ thenable._result = resolvedValue;
}
},
error => {
- if (thenable._reactStatus === Pending) {
- thenable._reactStatus = Rejected;
- thenable._reactResult = error;
+ if (thenable._status === Pending) {
+ thenable._status = Rejected;
+ thenable._result = error;
}
},
);
diff --git a/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js b/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js
new file mode 100644
index 0000000000000..e39fc79c83884
--- /dev/null
+++ b/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js
@@ -0,0 +1,309 @@
+let React;
+let ReactTestRenderer;
+let ReactFeatureFlags;
+let Suspense;
+let lazy;
+
+describe('ReactLazy', () => {
+ beforeEach(() => {
+ jest.resetModules();
+ ReactFeatureFlags = require('shared/ReactFeatureFlags');
+ ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
+ ReactFeatureFlags.replayFailedUnitOfWorkWithInvokeGuardedCallback = false;
+ React = require('react');
+ Suspense = React.unstable_Suspense;
+ lazy = React.lazy;
+ ReactTestRenderer = require('react-test-renderer');
+ });
+
+ function Text(props) {
+ ReactTestRenderer.unstable_yield(props.text);
+ return props.text;
+ }
+
+ it('suspends until module has loaded', async () => {
+ const LazyText = lazy(async () => Text);
+
+ const root = ReactTestRenderer.create(
+ }>
+
+ ,
+ {
+ unstable_isConcurrent: true,
+ },
+ );
+
+ expect(root).toFlushAndYield(['Loading...']);
+ expect(root).toMatchRenderedOutput(null);
+
+ await LazyText;
+
+ expect(root).toFlushAndYield(['Hi']);
+ expect(root).toMatchRenderedOutput('Hi');
+
+ // Should not suspend on update
+ root.update(
+ }>
+
+ ,
+ );
+ expect(root).toFlushAndYield(['Hi again']);
+ expect(root).toMatchRenderedOutput('Hi again');
+ });
+
+ it('uses `default` property, if it exists', async () => {
+ const LazyText = lazy(async () => ({default: Text}));
+
+ const root = ReactTestRenderer.create(
+ }>
+
+ ,
+ {
+ unstable_isConcurrent: true,
+ },
+ );
+ expect(root).toFlushAndYield(['Loading...']);
+ expect(root).toMatchRenderedOutput(null);
+
+ await LazyText;
+
+ expect(root).toFlushAndYield(['Hi']);
+ expect(root).toMatchRenderedOutput('Hi');
+
+ // Should not suspend on update
+ root.update(
+ }>
+
+ ,
+ );
+ expect(root).toFlushAndYield(['Hi again']);
+ expect(root).toMatchRenderedOutput('Hi again');
+ });
+
+ it('throws if promise rejects', async () => {
+ const LazyText = lazy(async () => {
+ throw new Error('Bad network');
+ });
+
+ const root = ReactTestRenderer.create(
+ }>
+
+ ,
+ {
+ unstable_isConcurrent: true,
+ },
+ );
+
+ expect(root).toFlushAndYield(['Loading...']);
+ expect(root).toMatchRenderedOutput(null);
+
+ try {
+ await LazyText;
+ } catch (e) {}
+
+ expect(root).toFlushAndThrow('Bad network');
+ });
+
+ it('mount and reorder', async () => {
+ class Child extends React.Component {
+ componentDidMount() {
+ ReactTestRenderer.unstable_yield('Did mount: ' + this.props.label);
+ }
+ componentDidUpdate() {
+ ReactTestRenderer.unstable_yield('Did update: ' + this.props.label);
+ }
+ render() {
+ return ;
+ }
+ }
+
+ const LazyChildA = lazy(async () => Child);
+ const LazyChildB = lazy(async () => Child);
+
+ function Parent({swap}) {
+ return (
+ }>
+ {swap
+ ? [
+ ,
+ ,
+ ]
+ : [
+ ,
+ ,
+ ]}
+
+ );
+ }
+
+ const root = ReactTestRenderer.create(, {
+ unstable_isConcurrent: true,
+ });
+
+ expect(root).toFlushAndYield(['Loading...']);
+ expect(root).toMatchRenderedOutput(null);
+
+ await LazyChildA;
+ await LazyChildB;
+
+ expect(root).toFlushAndYield(['A', 'B', 'Did mount: A', 'Did mount: B']);
+ expect(root).toMatchRenderedOutput('AB');
+
+ // Swap the position of A and B
+ root.update();
+ expect(root).toFlushAndYield(['B', 'A', 'Did update: B', 'Did update: A']);
+ expect(root).toMatchRenderedOutput('BA');
+ });
+
+ it('resolves defaultProps, on mount and update', async () => {
+ function T(props) {
+ return ;
+ }
+ T.defaultProps = {text: 'Hi'};
+ const LazyText = lazy(async () => T);
+
+ const root = ReactTestRenderer.create(
+ }>
+
+ ,
+ {
+ unstable_isConcurrent: true,
+ },
+ );
+
+ expect(root).toFlushAndYield(['Loading...']);
+ expect(root).toMatchRenderedOutput(null);
+
+ await LazyText;
+
+ expect(root).toFlushAndYield(['Hi']);
+ expect(root).toMatchRenderedOutput('Hi');
+
+ T.defaultProps = {text: 'Hi again'};
+ root.update(
+ }>
+
+ ,
+ );
+ expect(root).toFlushAndYield(['Hi again']);
+ expect(root).toMatchRenderedOutput('Hi again');
+ });
+
+ it('resolves defaultProps without breaking memoization', async () => {
+ function LazyImpl(props) {
+ ReactTestRenderer.unstable_yield('Lazy');
+ return (
+
+
+ {props.children}
+
+ );
+ }
+ LazyImpl.defaultProps = {siblingText: 'Sibling'};
+ const Lazy = lazy(async () => LazyImpl);
+
+ class Stateful extends React.Component {
+ state = {text: 'A'};
+ render() {
+ return ;
+ }
+ }
+
+ const stateful = React.createRef(null);
+
+ const root = ReactTestRenderer.create(
+ }>
+
+
+
+ ,
+ {
+ unstable_isConcurrent: true,
+ },
+ );
+ expect(root).toFlushAndYield(['Loading...']);
+ expect(root).toMatchRenderedOutput(null);
+
+ await Lazy;
+
+ expect(root).toFlushAndYield(['Lazy', 'Sibling', 'A']);
+ expect(root).toMatchRenderedOutput('SiblingA');
+
+ // Lazy should not re-render
+ stateful.current.setState({text: 'B'});
+ expect(root).toFlushAndYield(['B']);
+ expect(root).toMatchRenderedOutput('SiblingB');
+ });
+
+ it('includes lazy-loaded component in warning stack', async () => {
+ const LazyFoo = lazy(() => {
+ ReactTestRenderer.unstable_yield('Started loading');
+ const Foo = props => {[, ]}
;
+ return Promise.resolve(Foo);
+ });
+
+ const root = ReactTestRenderer.create(
+ }>
+
+ ,
+ {
+ unstable_isConcurrent: true,
+ },
+ );
+
+ expect(root).toFlushAndYield(['Started loading', 'Loading...']);
+ expect(root).toMatchRenderedOutput(null);
+
+ await LazyFoo;
+
+ expect(() => {
+ expect(root).toFlushAndYield(['A', 'B']);
+ }).toWarnDev(' in Text (at **)\n' + ' in Foo (at **)');
+ expect(root).toMatchRenderedOutput(AB
);
+ });
+
+ it('supports class and forwardRef components', async () => {
+ const LazyClass = lazy(async () => {
+ class Foo extends React.Component {
+ render() {
+ return ;
+ }
+ }
+ return Foo;
+ });
+
+ const LazyForwardRef = lazy(async () => {
+ class Bar extends React.Component {
+ render() {
+ return ;
+ }
+ }
+ return React.forwardRef((props, ref) => {
+ ReactTestRenderer.unstable_yield('forwardRef');
+ return ;
+ });
+ });
+
+ const ref = React.createRef();
+ const root = ReactTestRenderer.create(
+ }>
+
+
+ ,
+ {
+ unstable_isConcurrent: true,
+ },
+ );
+
+ expect(root).toFlushAndYield(['Loading...']);
+ expect(root).toMatchRenderedOutput(null);
+ expect(ref.current).toBe(null);
+
+ await LazyClass;
+ await LazyForwardRef;
+
+ expect(root).toFlushAndYield(['Foo', 'forwardRef', 'Bar']);
+ expect(root).toMatchRenderedOutput('FooBar');
+ expect(ref.current).not.toBe(null);
+ });
+});
diff --git a/packages/react-reconciler/src/__tests__/ReactPure-test.internal.js b/packages/react-reconciler/src/__tests__/ReactPure-test.internal.js
index ca1bb36851641..d1a9f5542b92c 100644
--- a/packages/react-reconciler/src/__tests__/ReactPure-test.internal.js
+++ b/packages/react-reconciler/src/__tests__/ReactPure-test.internal.js
@@ -42,9 +42,12 @@ describe('pure', () => {
function Indirection(props, ref) {
return ;
}
- return Promise.resolve(React.forwardRef(Indirection));
+ return React.lazy(async () => React.forwardRef(Indirection));
+ });
+ sharedTests('lazy', (...args) => {
+ const Pure = React.pure(...args);
+ return React.lazy(async () => Pure);
});
- sharedTests('lazy', (...args) => Promise.resolve(React.pure(...args)));
function sharedTests(label, pure) {
describe(`${label}`, () => {
diff --git a/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.internal.js b/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.internal.js
index 3fbd5e4e77254..5071e52b4b7d4 100644
--- a/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.internal.js
+++ b/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.internal.js
@@ -6,7 +6,6 @@ let ReactCache;
let Suspense;
let StrictMode;
let ConcurrentMode;
-let lazy;
let cache;
let TextResource;
@@ -28,7 +27,6 @@ describe('ReactSuspenseWithNoopRenderer', () => {
Suspense = React.unstable_Suspense;
StrictMode = React.StrictMode;
ConcurrentMode = React.unstable_ConcurrentMode;
- lazy = React.lazy;
function invalidateCache() {
cache = ReactCache.createCache(invalidateCache);
@@ -50,12 +48,12 @@ describe('ReactSuspenseWithNoopRenderer', () => {
textResourceShouldFail = false;
});
- function div(...children) {
- children = children.map(
- c => (typeof c === 'string' ? {text: c, hidden: false} : c),
- );
- return {type: 'div', children, prop: undefined, hidden: false};
- }
+ // function div(...children) {
+ // children = children.map(
+ // c => (typeof c === 'string' ? {text: c, hidden: false} : c),
+ // );
+ // return {type: 'div', children, prop: undefined, hidden: false};
+ // }
function span(prop) {
return {type: 'span', children: [], prop, hidden: false};
@@ -1416,294 +1414,6 @@ describe('ReactSuspenseWithNoopRenderer', () => {
});
});
- describe('Promise as element type', () => {
- it('accepts a promise as an element type', async () => {
- const LazyText = Promise.resolve(Text);
-
- ReactNoop.render(
- }>
-
- ,
- );
- expect(ReactNoop.flush()).toEqual(['Loading...']);
- expect(ReactNoop.getChildren()).toEqual([]);
-
- await LazyText;
-
- expect(ReactNoop.flush()).toEqual(['Hi']);
- expect(ReactNoop.getChildren()).toEqual([span('Hi')]);
-
- // Should not suspend on update
- ReactNoop.render(
- }>
-
- ,
- );
- expect(ReactNoop.flush()).toEqual(['Hi again']);
- expect(ReactNoop.getChildren()).toEqual([span('Hi again')]);
- });
-
- it('throws if promise rejects', async () => {
- const LazyText = Promise.reject(new Error('Bad network'));
-
- ReactNoop.render(
- }>
-
- ,
- );
- expect(ReactNoop.flush()).toEqual(['Loading...']);
-
- await LazyText.catch(() => {});
-
- expect(() => ReactNoop.flush()).toThrow('Bad network');
- });
-
- it('mount and reorder', async () => {
- class Child extends React.Component {
- componentDidMount() {
- ReactNoop.yield('Did mount: ' + this.props.label);
- }
- componentDidUpdate() {
- ReactNoop.yield('Did update: ' + this.props.label);
- }
- render() {
- return ;
- }
- }
-
- const LazyChildA = Promise.resolve(Child);
- const LazyChildB = Promise.resolve(Child);
-
- function Parent({swap}) {
- return (
- }>
- {swap
- ? [
- ,
- ,
- ]
- : [
- ,
- ,
- ]}
-
- );
- }
-
- ReactNoop.render();
- expect(ReactNoop.flush()).toEqual(['Loading...']);
- expect(ReactNoop.getChildren()).toEqual([]);
-
- await LazyChildA;
- await LazyChildB;
-
- expect(ReactNoop.flush()).toEqual([
- 'A',
- 'B',
- 'Did mount: A',
- 'Did mount: B',
- ]);
- expect(ReactNoop.getChildren()).toEqual([span('A'), span('B')]);
-
- // Swap the position of A and B
- ReactNoop.render();
- expect(ReactNoop.flush()).toEqual([
- 'B',
- 'A',
- 'Did update: B',
- 'Did update: A',
- ]);
- expect(ReactNoop.getChildren()).toEqual([span('B'), span('A')]);
- });
-
- it('uses `default` property, if it exists', async () => {
- const LazyText = Promise.resolve({default: Text});
-
- ReactNoop.render(
- }>
-
- ,
- );
- expect(ReactNoop.flush()).toEqual(['Loading...']);
- expect(ReactNoop.getChildren()).toEqual([]);
-
- await LazyText;
-
- expect(ReactNoop.flush()).toEqual(['Hi']);
- expect(ReactNoop.getChildren()).toEqual([span('Hi')]);
-
- // Should not suspend on update
- ReactNoop.render(
- }>
-
- ,
- );
- expect(ReactNoop.flush()).toEqual(['Hi again']);
- expect(ReactNoop.getChildren()).toEqual([span('Hi again')]);
- });
-
- it('resolves defaultProps, on mount and update', async () => {
- function T(props) {
- return ;
- }
- T.defaultProps = {text: 'Hi'};
- const LazyText = Promise.resolve(T);
-
- ReactNoop.render(
- }>
-
- ,
- );
- expect(ReactNoop.flush()).toEqual(['Loading...']);
- expect(ReactNoop.getChildren()).toEqual([]);
-
- await LazyText;
-
- expect(ReactNoop.flush()).toEqual(['Hi']);
- expect(ReactNoop.getChildren()).toEqual([span('Hi')]);
-
- T.defaultProps = {text: 'Hi again'};
-
- ReactNoop.render(
- }>
-
- ,
- );
- expect(ReactNoop.flush()).toEqual(['Hi again']);
- expect(ReactNoop.getChildren()).toEqual([span('Hi again')]);
- });
-
- it('resolves defaultProps without breaking memoization', async () => {
- function LazyImpl(props) {
- ReactNoop.yield('Lazy');
- return (
-
-
- {props.children}
-
- );
- }
- LazyImpl.defaultProps = {siblingText: 'Sibling'};
- const Lazy = Promise.resolve(LazyImpl);
-
- class Stateful extends React.Component {
- state = {text: 'A'};
- render() {
- return ;
- }
- }
-
- const stateful = React.createRef(null);
- ReactNoop.render(
- }>
-
-
-
- ,
- );
- expect(ReactNoop.flush()).toEqual(['Loading...']);
- expect(ReactNoop.getChildren()).toEqual([]);
- await Lazy;
- expect(ReactNoop.flush()).toEqual(['Lazy', 'Sibling', 'A']);
- expect(ReactNoop.getChildren()).toEqual([span('Sibling'), span('A')]);
-
- // Lazy should not re-render
- stateful.current.setState({text: 'B'});
- expect(ReactNoop.flush()).toEqual(['B']);
- expect(ReactNoop.getChildren()).toEqual([span('Sibling'), span('B')]);
- });
-
- it('lazy-load using React.lazy', async () => {
- const LazyText = lazy(() => {
- ReactNoop.yield('Started loading');
- return Promise.resolve(Text);
- });
-
- ReactNoop.render(
- }>
-
-
-
- ,
- );
- // Render first two siblings. The lazy component should not have
- // started loading yet.
- ReactNoop.flushThrough(['A', 'B']);
-
- // Flush the rest.
- expect(ReactNoop.flush()).toEqual(['Started loading', 'Loading...']);
- expect(ReactNoop.getChildren()).toEqual([]);
-
- await LazyText;
-
- expect(ReactNoop.flush()).toEqual(['A', 'B', 'C']);
- expect(ReactNoop.getChildren()).toEqual([
- span('A'),
- span('B'),
- span('C'),
- ]);
- });
-
- it('includes lazy-loaded component in warning stack', async () => {
- const LazyFoo = lazy(() => {
- ReactNoop.yield('Started loading');
- const Foo = props => (
- {[, ]}
- );
- return Promise.resolve(Foo);
- });
-
- ReactNoop.render(
- }>
-
- ,
- );
- expect(ReactNoop.flush()).toEqual(['Started loading', 'Loading...']);
- expect(ReactNoop.getChildren()).toEqual([]);
-
- await LazyFoo;
- expect(() => {
- expect(ReactNoop.flush()).toEqual(['A', 'B']);
- }).toWarnDev(' in Text (at **)\n' + ' in Foo (at **)');
- expect(ReactNoop.getChildren()).toEqual([div(span('A'), span('B'))]);
- });
-
- it('supports class and forwardRef components', async () => {
- const LazyClass = lazy(() => {
- class Foo extends React.Component {
- render() {
- return ;
- }
- }
- return Promise.resolve(Foo);
- });
-
- const LazyForwardRef = lazy(() => {
- const Bar = React.forwardRef((props, ref) => (
-
- ));
- return Promise.resolve(Bar);
- });
-
- const ref = React.createRef();
- ReactNoop.render(
- }>
-
-
- ,
- );
- expect(ReactNoop.flush()).toEqual(['Loading...']);
- expect(ReactNoop.getChildren()).toEqual([]);
- expect(ref.current).toBe(null);
-
- await LazyClass;
- await LazyForwardRef;
- expect(ReactNoop.flush()).toEqual(['Foo', 'Bar']);
- expect(ReactNoop.getChildren()).toEqual([span('Foo'), span('Bar')]);
- expect(ref.current).not.toBe(null);
- });
- });
-
it('does not call lifecycles of a suspended component', async () => {
class TextWithLifecycle extends React.Component {
componentDidMount() {
diff --git a/packages/react/src/ReactLazy.js b/packages/react/src/ReactLazy.js
index 85fa687ec3d6a..4964f0ca7328b 100644
--- a/packages/react/src/ReactLazy.js
+++ b/packages/react/src/ReactLazy.js
@@ -5,13 +5,20 @@
* LICENSE file in the root directory of this source tree.
*/
+import type {LazyComponentThenable} from 'shared/ReactLazyComponent';
+
type Thenable = {
then(resolve: (T) => mixed, reject: (mixed) => mixed): R,
};
-export function lazy(ctor: () => Thenable) {
+import {REACT_LAZY_TYPE} from 'shared/ReactSymbols';
+
+export function lazy(
+ ctor: () => Thenable,
+): LazyComponentThenable {
let thenable = null;
return {
+ $$typeof: REACT_LAZY_TYPE,
then(resolve, reject) {
if (thenable === null) {
// Lazily create thenable by wrapping in an extra thenable.
@@ -21,7 +28,7 @@ export function lazy(ctor: () => Thenable) {
return thenable.then(resolve, reject);
},
// React uses these fields to store the result.
- _reactStatus: -1,
- _reactResult: null,
+ _status: -1,
+ _result: null,
};
}
diff --git a/packages/shared/ReactLazyComponent.js b/packages/shared/ReactLazyComponent.js
index 0d980f71834a3..f98d663a48519 100644
--- a/packages/shared/ReactLazyComponent.js
+++ b/packages/shared/ReactLazyComponent.js
@@ -7,16 +7,18 @@
* @flow
*/
-export type Thenable = {
+export type LazyComponentThenable = {
+ $$typeof: Symbol | number,
then(resolve: (T) => mixed, reject: (mixed) => mixed): mixed,
- _reactStatus?: 0 | 1 | 2,
- _reactResult: any,
+ _status: 0 | 1 | 2,
+ _result: any,
};
-type ResolvedThenable = {
+type ResolvedLazyComponentThenable = {
+ $$typeof: Symbol | number,
then(resolve: (T) => mixed, reject: (mixed) => mixed): mixed,
- _reactStatus?: 1,
- _reactResult: T,
+ _status: 1,
+ _result: any,
};
export const Pending = 0;
@@ -24,13 +26,13 @@ export const Resolved = 1;
export const Rejected = 2;
export function getResultFromResolvedThenable(
- thenable: ResolvedThenable,
+ thenable: ResolvedLazyComponentThenable,
): T {
- return thenable._reactResult;
+ return thenable._result;
}
export function refineResolvedThenable(
- thenable: Thenable,
-): ResolvedThenable | null {
- return thenable._reactStatus === Resolved ? thenable._reactResult : null;
+ thenable: LazyComponentThenable,
+): ResolvedLazyComponentThenable | null {
+ return thenable._status === Resolved ? thenable._result : null;
}
diff --git a/packages/shared/ReactSymbols.js b/packages/shared/ReactSymbols.js
index 774c1bbe20385..2b8211474c384 100644
--- a/packages/shared/ReactSymbols.js
+++ b/packages/shared/ReactSymbols.js
@@ -42,6 +42,7 @@ export const REACT_SUSPENSE_TYPE = hasSymbol
? Symbol.for('react.suspense')
: 0xead1;
export const REACT_PURE_TYPE = hasSymbol ? Symbol.for('react.pure') : 0xead3;
+export const REACT_LAZY_TYPE = hasSymbol ? Symbol.for('react.lazy') : 0xead4;
const MAYBE_ITERATOR_SYMBOL = typeof Symbol === 'function' && Symbol.iterator;
const FAUX_ITERATOR_SYMBOL = '@@iterator';
diff --git a/packages/shared/getComponentName.js b/packages/shared/getComponentName.js
index 4e89efef4acfa..7185017e5899f 100644
--- a/packages/shared/getComponentName.js
+++ b/packages/shared/getComponentName.js
@@ -7,7 +7,7 @@
* @flow
*/
-import type {Thenable} from 'shared/ReactLazyComponent';
+import type {LazyComponentThenable} from 'shared/ReactLazyComponent';
import warningWithoutStack from 'shared/warningWithoutStack';
import {
@@ -21,6 +21,7 @@ import {
REACT_PROVIDER_TYPE,
REACT_STRICT_MODE_TYPE,
REACT_SUSPENSE_TYPE,
+ REACT_LAZY_TYPE,
} from 'shared/ReactSymbols';
import {refineResolvedThenable} from 'shared/ReactLazyComponent';
@@ -80,12 +81,12 @@ function getComponentName(type: mixed): string | null {
return getWrappedName(type, type.render, 'ForwardRef');
case REACT_PURE_TYPE:
return getWrappedName(type, type.render, 'Pure');
- }
- if (typeof type.then === 'function') {
- const thenable: Thenable = (type: any);
- const resolvedThenable = refineResolvedThenable(thenable);
- if (resolvedThenable) {
- return getComponentName(resolvedThenable);
+ case REACT_LAZY_TYPE: {
+ const thenable: LazyComponentThenable = (type: any);
+ const resolvedThenable = refineResolvedThenable(thenable);
+ if (resolvedThenable) {
+ return getComponentName(resolvedThenable);
+ }
}
}
}
diff --git a/packages/shared/isValidElementType.js b/packages/shared/isValidElementType.js
index 8682a3fb53932..f662ce19fa699 100644
--- a/packages/shared/isValidElementType.js
+++ b/packages/shared/isValidElementType.js
@@ -17,6 +17,7 @@ import {
REACT_STRICT_MODE_TYPE,
REACT_SUSPENSE_TYPE,
REACT_PURE_TYPE,
+ REACT_LAZY_TYPE,
} from 'shared/ReactSymbols';
export default function isValidElementType(type: mixed) {
@@ -31,7 +32,7 @@ export default function isValidElementType(type: mixed) {
type === REACT_SUSPENSE_TYPE ||
(typeof type === 'object' &&
type !== null &&
- (typeof type.then === 'function' ||
+ (type.$$typeof === REACT_LAZY_TYPE ||
type.$$typeof === REACT_PURE_TYPE ||
type.$$typeof === REACT_PROVIDER_TYPE ||
type.$$typeof === REACT_CONTEXT_TYPE ||