@samitouri / QOS-React-2 / commits / 5031ebf6be

Accept promise as element type (#13397)

* Accept promise as element type On the initial render, the element will suspend as if a promise were thrown from inside the body of the unresolved component. Siblings should continue rendering and if the parent is a Placeholder, the promise should be captured by that Placeholder. When the promise resolves, rendering resumes. If the resolved value has a `default` property, it is assumed to be the default export of an ES module, and we use that as the component type. If it does not have a `default` property, we use the resolved value itself. The resolved value is stored as an expando on the promise/thenable. * Use special types of work for lazy components Because reconciliation is a hot path, this adds ClassComponentLazy, FunctionalComponentLazy, and ForwardRefLazy as special types of work. The other types are not supported, but wouldn't be placed into a separate module regardless. * Resolve defaultProps for lazy types * Remove some calls to isContextProvider isContextProvider checks the fiber tag, but it's typically called after we've already refined the type of work. We should get rid of it. I removed some of them in the previous commit, and deleted a few more in this one. I left a few behind because the remaining ones would require additional refactoring that feels outside the scope of this PR. * Remove getLazyComponentTypeIfResolved * Return baseProps instead of null The caller compares the result to baseProps to see if anything changed. * Avoid redundant checks by inlining getFiberTagFromObjectType * Move tag resolution to ReactFiber module * Pass next props to update* functions We should do this with all types of work in the future. * Refine component type before pushing/popping context Removes unnecessary checks. * Replace all occurrences of _reactResult with helper * Move shared thenable logic to `shared` package * Check type of wrapper object before resolving to `default` export * Return resolved tag instead of reassigning

Andrew Clark committed Aug 16, 2018 at 09:21 UTC 5031ebf6beddf88cac15f4d2c9e91f8dbb91d59d
24 files changed +882 -208
packages/react-dom/src/test-utils/ReactTestUtils.js
+5 -1
@@ -11,7 +11,9 @@ import {findCurrentFiberUsingSlowPath} from 'react-reconciler/reflection';
11 import * as ReactInstanceMap from 'shared/ReactInstanceMap';
12 import {
13 ClassComponent,
14 + ClassComponentLazy,
15 FunctionalComponent,
16 + FunctionalComponentLazy,
17 HostComponent,
18 HostText,
19 } from 'shared/ReactTypeOfWork';
@@ -81,7 +83,9 @@ function findAllInRenderedFiberTreeInternal(fiber, test) {
83 node.tag === HostComponent ||
84 node.tag === HostText ||
85 node.tag === ClassComponent ||
84 - node.tag === FunctionalComponent
86 + node.tag === ClassComponentLazy ||
87 + node.tag === FunctionalComponent ||
88 + node.tag === FunctionalComponentLazy
89 ) {
90 const publicInst = node.stateNode;
91 if (test(publicInst)) {
packages/react-reconciler/src/ReactChildFiber.js
+6 -3
@@ -23,6 +23,7 @@ import {
23 import {
24 FunctionalComponent,
25 ClassComponent,
26 + ClassComponentLazy,
27 HostText,
28 HostPortal,
29 Fragment,
@@ -117,7 +118,7 @@ function coerceRef(
118 if (!didWarnAboutStringRefInStrictMode[componentName]) {
119 warningWithoutStack(
120 false,
120 - 'A string ref, "%s", has been found within a strict mode tree. ' +
121 + 'A string ref, "%s", has been found within a strict mode tree. ' +
122 'String refs are a source of potential bugs and should be avoided. ' +
123 'We recommend using createRef() instead.' +
124 '\n%s' +
@@ -137,7 +138,8 @@ function coerceRef(
138 if (owner) {
139 const ownerFiber = ((owner: any): Fiber);
140 invariant(
140 - ownerFiber.tag === ClassComponent,
141 + ownerFiber.tag === ClassComponent ||
142 + ownerFiber.tag === ClassComponentLazy,
143 'Stateless function components cannot have refs.',
144 );
145 inst = ownerFiber.stateNode;
@@ -1307,7 +1309,8 @@ function ChildReconciler(shouldTrackSideEffects) {
1309 // component, throw an error. If Fiber return types are disabled,
1310 // we already threw above.
1311 switch (returnFiber.tag) {
1310 - case ClassComponent: {
1312 + case ClassComponent:
1313 + case ClassComponentLazy: {
1314 if (__DEV__) {
1315 const instance = returnFiber.stateNode;
1316 if (instance.render._isMockFunction) {
packages/react-reconciler/src/ReactCurrentFiber.js
+4
@@ -11,7 +11,9 @@ import ReactSharedInternals from 'shared/ReactSharedInternals';
11 import {
12 IndeterminateComponent,
13 FunctionalComponent,
14 + FunctionalComponentLazy,
15 ClassComponent,
16 + ClassComponentLazy,
17 HostComponent,
18 Mode,
19 } from 'shared/ReactTypeOfWork';
@@ -28,7 +30,9 @@ function describeFiber(fiber: Fiber): string {
30 switch (fiber.tag) {
31 case IndeterminateComponent:
32 case FunctionalComponent:
33 + case FunctionalComponentLazy:
34 case ClassComponent:
35 + case ClassComponentLazy:
36 case HostComponent:
37 case Mode:
38 const owner = fiber._debugOwner;
packages/react-reconciler/src/ReactFiber.js
+79 -50
@@ -33,6 +33,9 @@ import {
33 ContextConsumer,
34 Profiler,
35 PlaceholderComponent,
36 + FunctionalComponentLazy,
37 + ClassComponentLazy,
38 + ForwardRefLazy,
39 } from 'shared/ReactTypeOfWork';
40 import getComponentName from 'shared/getComponentName';
41
@@ -278,8 +281,32 @@ const createFiber = function(
281 return new FiberNode(tag, pendingProps, key, mode);
282 };
283
281 -function shouldConstruct(Component) {
282 - return !!(Component.prototype && Component.prototype.isReactComponent);
284 +function shouldConstruct(Component: Function) {
285 + const prototype = Component.prototype;
286 + return (
287 + typeof prototype === 'object' &&
288 + prototype !== null &&
289 + typeof prototype.isReactComponent === 'object' &&
290 + prototype.isReactComponent !== null
291 + );
292 +}
293 +
294 +export function resolveLazyComponentTag(
295 + fiber: Fiber,
296 + Component: Function,
297 +): void {
298 + if (typeof Component === 'function') {
299 + return shouldConstruct(Component)
300 + ? ClassComponentLazy
301 + : FunctionalComponentLazy;
302 + } else if (
303 + Component !== undefined &&
304 + Component !== null &&
305 + Component.$$typeof
306 + ) {
307 + return ForwardRefLazy;
308 + }
309 + return IndeterminateComponent;
310 }
311
312 // This is used to create an alternate fiber to do work on.
@@ -390,7 +417,7 @@ export function createFiberFromElement(
417 let fiber;
418 const type = element.type;
419 const key = element.key;
393 - let pendingProps = element.props;
420 + const pendingProps = element.props;
421
422 let fiberTag;
423 if (typeof type === 'function') {
@@ -398,7 +425,7 @@ export function createFiberFromElement(
425 } else if (typeof type === 'string') {
426 fiberTag = HostComponent;
427 } else {
401 - switch (type) {
428 + getTag: switch (type) {
429 case REACT_FRAGMENT_TYPE:
430 return createFiberFromFragment(
431 pendingProps.children,
@@ -419,9 +446,54 @@ export function createFiberFromElement(
446 case REACT_PLACEHOLDER_TYPE:
447 fiberTag = PlaceholderComponent;
448 break;
422 - default:
423 - fiberTag = getFiberTagFromObjectType(type, owner);
424 - break;
449 + default: {
450 + if (typeof type === 'object' && type !== null) {
451 + switch (type.$$typeof) {
452 + case REACT_PROVIDER_TYPE:
453 + fiberTag = ContextProvider;
454 + break getTag;
455 + case REACT_CONTEXT_TYPE:
456 + // This is a consumer
457 + fiberTag = ContextConsumer;
458 + break getTag;
459 + case REACT_FORWARD_REF_TYPE:
460 + fiberTag = ForwardRef;
461 + break getTag;
462 + default: {
463 + if (typeof type.then === 'function') {
464 + fiberTag = IndeterminateComponent;
465 + break getTag;
466 + }
467 + }
468 + }
469 + }
470 + let info = '';
471 + if (__DEV__) {
472 + if (
473 + type === undefined ||
474 + (typeof type === 'object' &&
475 + type !== null &&
476 + Object.keys(type).length === 0)
477 + ) {
478 + info +=
479 + ' You likely forgot to export your component from the file ' +
480 + "it's defined in, or you might have mixed up default and " +
481 + 'named imports.';
482 + }
483 + const ownerName = owner ? getComponentName(owner.type) : null;
484 + if (ownerName) {
485 + info += '\n\nCheck the render method of `' + ownerName + '`.';
486 + }
487 + }
488 + invariant(
489 + false,
490 + 'Element type is invalid: expected a string (for built-in ' +
491 + 'components) or a class/function (for composite components) ' +
492 + 'but got: %s.%s',
493 + type == null ? type : typeof type,
494 + info,
495 + );
496 + }
497 }
498 }
499
@@ -437,49 +509,6 @@ export function createFiberFromElement(
509 return fiber;
510 }
511
440 -function getFiberTagFromObjectType(type, owner): TypeOfWork {
441 - const $$typeof =
442 - typeof type === 'object' && type !== null ? type.$$typeof : null;
443 -
444 - switch ($$typeof) {
445 - case REACT_PROVIDER_TYPE:
446 - return ContextProvider;
447 - case REACT_CONTEXT_TYPE:
448 - // This is a consumer
449 - return ContextConsumer;
450 - case REACT_FORWARD_REF_TYPE:
451 - return ForwardRef;
452 - default: {
453 - let info = '';
454 - if (__DEV__) {
455 - if (
456 - type === undefined ||
457 - (typeof type === 'object' &&
458 - type !== null &&
459 - Object.keys(type).length === 0)
460 - ) {
461 - info +=
462 - ' You likely forgot to export your component from the file ' +
463 - "it's defined in, or you might have mixed up default and " +
464 - 'named imports.';
465 - }
466 - const ownerName = owner ? getComponentName(owner.type) : null;
467 - if (ownerName) {
468 - info += '\n\nCheck the render method of `' + ownerName + '`.';
469 - }
470 - }
471 - invariant(
472 - false,
473 - 'Element type is invalid: expected a string (for built-in ' +
474 - 'components) or a class/function (for composite components) ' +
475 - 'but got: %s.%s',
476 - type == null ? type : typeof type,
477 - info,
478 - );
479 - }
480 - }
481 -}
482 -
512 export function createFiberFromFragment(
513 elements: ReactFragment,
514 mode: TypeOfMode,
packages/react-reconciler/src/ReactFiberBeginWork.js
+211 -36
@@ -16,12 +16,15 @@ import checkPropTypes from 'prop-types/checkPropTypes';
16 import {
17 IndeterminateComponent,
18 FunctionalComponent,
19 + FunctionalComponentLazy,
20 ClassComponent,
21 + ClassComponentLazy,
22 HostRoot,
23 HostComponent,
24 HostText,
25 HostPortal,
26 ForwardRef,
27 + ForwardRefLazy,
28 Fragment,
29 Mode,
30 ContextProvider,
@@ -81,6 +84,7 @@ import {
84 getUnmaskedContext,
85 hasContextChanged as hasLegacyContextChanged,
86 pushContextProvider as pushLegacyContextProvider,
87 + isContextProvider as isLegacyContextProvider,
88 pushTopLevelContextObject,
89 invalidateContextProvider,
90 } from './ReactFiberContext';
@@ -96,6 +100,9 @@ import {
100 resumeMountClassInstance,
101 updateClassInstance,
102 } from './ReactFiberClassComponent';
103 +import {readLazyComponentType} from './ReactFiberLazyComponent';
104 +import {getResultFromResolvedThenable} from 'shared/ReactLazyComponent';
105 +import {resolveLazyComponentTag} from './ReactFiber';
106
107 const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
108
@@ -145,10 +152,11 @@ export function reconcileChildren(
152 function updateForwardRef(
153 current: Fiber | null,
154 workInProgress: Fiber,
155 + type: any,
156 + nextProps: any,
157 renderExpirationTime: ExpirationTime,
158 ) {
150 - const render = workInProgress.type.render;
151 - const nextProps = workInProgress.pendingProps;
159 + const render = type.render;
160 const ref = workInProgress.ref;
161 if (hasLegacyContextChanged()) {
162 // Normally we can bail out on props equality but if context has changed
@@ -250,11 +258,11 @@ function markRef(current: Fiber | null, workInProgress: Fiber) {
258 function updateFunctionalComponent(
259 current,
260 workInProgress,
261 + Component,
262 + nextProps: any,
263 renderExpirationTime,
264 ) {
255 - const fn = workInProgress.type;
256 - const nextProps = workInProgress.pendingProps;
257 - const unmaskedContext = getUnmaskedContext(workInProgress);
265 + const unmaskedContext = getUnmaskedContext(workInProgress, Component);
266 const context = getMaskedContext(workInProgress, unmaskedContext);
267
268 let nextChildren;
@@ -262,10 +270,10 @@ function updateFunctionalComponent(
270 if (__DEV__) {
271 ReactCurrentOwner.current = workInProgress;
272 ReactCurrentFiber.setCurrentPhase('render');
265 - nextChildren = fn(nextProps, context);
273 + nextChildren = Component(nextProps, context);
274 ReactCurrentFiber.setCurrentPhase(null);
275 } else {
268 - nextChildren = fn(nextProps, context);
276 + nextChildren = Component(nextProps, context);
277 }
278
279 // React DevTools reads this flag.
@@ -283,12 +291,20 @@ function updateFunctionalComponent(
291 function updateClassComponent(
292 current: Fiber | null,
293 workInProgress: Fiber,
294 + Component: any,
295 + nextProps,
296 renderExpirationTime: ExpirationTime,
297 ) {
298 // Push context providers early to prevent context stack mismatches.
299 // During mounting we don't know the child context yet as the instance doesn't exist.
300 // We will invalidate the child context in finishClassComponent() right after rendering.
291 - const hasContext = pushLegacyContextProvider(workInProgress);
301 + let hasContext;
302 + if (isLegacyContextProvider(Component)) {
303 + hasContext = true;
304 + pushLegacyContextProvider(workInProgress);
305 + } else {
306 + hasContext = false;
307 + }
308 prepareToReadContext(workInProgress, renderExpirationTime);
309
310 let shouldUpdate;
@@ -297,16 +313,23 @@ function updateClassComponent(
313 // In the initial pass we might need to construct the instance.
314 constructClassInstance(
315 workInProgress,
300 - workInProgress.pendingProps,
316 + Component,
317 + nextProps,
318 + renderExpirationTime,
319 + );
320 + mountClassInstance(
321 + workInProgress,
322 + Component,
323 + nextProps,
324 renderExpirationTime,
325 );
303 - mountClassInstance(workInProgress, renderExpirationTime);
304 -
326 shouldUpdate = true;
327 } else {
328 // In a resume, we'll already have an instance we can reuse.
329 shouldUpdate = resumeMountClassInstance(
330 workInProgress,
331 + Component,
332 + nextProps,
333 renderExpirationTime,
334 );
335 }
@@ -314,12 +337,15 @@ function updateClassComponent(
337 shouldUpdate = updateClassInstance(
338 current,
339 workInProgress,
340 + Component,
341 + nextProps,
342 renderExpirationTime,
343 );
344 }
345 return finishClassComponent(
346 current,
347 workInProgress,
348 + Component,
349 shouldUpdate,
350 hasContext,
351 renderExpirationTime,
@@ -329,6 +355,7 @@ function updateClassComponent(
355 function finishClassComponent(
356 current: Fiber | null,
357 workInProgress: Fiber,
358 + Component: any,
359 shouldUpdate: boolean,
360 hasContext: boolean,
361 renderExpirationTime: ExpirationTime,
@@ -341,7 +368,7 @@ function finishClassComponent(
368 if (!shouldUpdate && !didCaptureError) {
369 // Context providers should defer to sCU for rendering
370 if (hasContext) {
344 - invalidateContextProvider(workInProgress, false);
371 + invalidateContextProvider(workInProgress, Component, false);
372 }
373
374 return bailoutOnAlreadyFinishedWork(
@@ -351,7 +378,6 @@ function finishClassComponent(
378 );
379 }
380
354 - const ctor = workInProgress.type;
381 const instance = workInProgress.stateNode;
382
383 // Rerender
@@ -360,7 +386,7 @@ function finishClassComponent(
386 if (
387 didCaptureError &&
388 (!enableGetDerivedStateFromCatch ||
363 - typeof ctor.getDerivedStateFromCatch !== 'function')
389 + typeof Component.getDerivedStateFromCatch !== 'function')
390 ) {
391 // If we captured an error, but getDerivedStateFrom catch is not defined,
392 // unmount all the children. componentDidCatch will schedule an update to
@@ -413,7 +439,7 @@ function finishClassComponent(
439
440 // The context might have changed so we need to recalculate it.
441 if (hasContext) {
416 - invalidateContextProvider(workInProgress, true);
442 + invalidateContextProvider(workInProgress, Component, true);
443 }
444
445 return workInProgress.child;
@@ -568,9 +594,25 @@ function updateHostText(current, workInProgress) {
594 return null;
595 }
596
597 +function resolveDefaultProps(Component, baseProps) {
598 + if (Component && Component.defaultProps) {
599 + // Resolve default props. Taken from ReactElement
600 + const props = Object.assign({}, baseProps);
601 + const defaultProps = Component.defaultProps;
602 + for (let propName in defaultProps) {
603 + if (props[propName] === undefined) {
604 + props[propName] = defaultProps[propName];
605 + }
606 + }
607 + return props;
608 + }
609 + return baseProps;
610 +}
611 +
612 function mountIndeterminateComponent(
613 current,
614 workInProgress,
615 + Component,
616 renderExpirationTime,
617 ) {
618 invariant(
@@ -578,9 +620,61 @@ function mountIndeterminateComponent(
620 'An indeterminate component should never have mounted. This error is ' +
621 'likely caused by a bug in React. Please file an issue.',
622 );
581 - const fn = workInProgress.type;
623 +
624 const props = workInProgress.pendingProps;
583 - const unmaskedContext = getUnmaskedContext(workInProgress);
625 + if (
626 + typeof Component === 'object' &&
627 + Component !== null &&
628 + typeof Component.then === 'function'
629 + ) {
630 + Component = readLazyComponentType(Component);
631 + const resolvedTag = (workInProgress.tag = resolveLazyComponentTag(
632 + workInProgress,
633 + Component,
634 + ));
635 + const resolvedProps = resolveDefaultProps(Component, props);
636 + switch (resolvedTag) {
637 + case FunctionalComponentLazy: {
638 + return updateFunctionalComponent(
639 + current,
640 + workInProgress,
641 + Component,
642 + resolvedProps,
643 + renderExpirationTime,
644 + );
645 + }
646 + case ClassComponentLazy: {
647 + return updateClassComponent(
648 + current,
649 + workInProgress,
650 + Component,
651 + resolvedProps,
652 + renderExpirationTime,
653 + );
654 + }
655 + case ForwardRefLazy: {
656 + return updateForwardRef(
657 + current,
658 + workInProgress,
659 + Component,
660 + resolvedProps,
661 + renderExpirationTime,
662 + );
663 + }
664 + default: {
665 + // This message intentionally doesn't metion ForwardRef because the
666 + // fact that it's a separate type of work is an implementation detail.
667 + invariant(
668 + false,
669 + 'Element type is invalid. Received a promise that resolves to: %s. ' +
670 + 'Promise elements must resolve to a class or function.',
671 + Component,
672 + );
673 + }
674 + }
675 + }
676 +
677 + const unmaskedContext = getUnmaskedContext(workInProgress, Component);
678 const context = getMaskedContext(workInProgress, unmaskedContext);
679
680 prepareToReadContext(workInProgress, renderExpirationTime);
@@ -588,8 +682,11 @@ function mountIndeterminateComponent(
682 let value;
683
684 if (__DEV__) {
591 - if (fn.prototype && typeof fn.prototype.render === 'function') {
592 - const componentName = getComponentName(fn) || 'Unknown';
685 + if (
686 + Component.prototype &&
687 + typeof Component.prototype.render === 'function'
688 + ) {
689 + const componentName = getComponentName(Component) || 'Unknown';
690
691 if (!didWarnAboutBadClass[componentName]) {
692 warningWithoutStack(
@@ -608,9 +705,9 @@ function mountIndeterminateComponent(
705 }
706
707 ReactCurrentOwner.current = workInProgress;
611 - value = fn(props, context);
708 + value = Component(props, context);
709 } else {
613 - value = fn(props, context);
710 + value = Component(props, context);
711 }
712 // React DevTools reads this flag.
713 workInProgress.effectTag |= PerformedWork;
@@ -621,15 +718,19 @@ function mountIndeterminateComponent(
718 typeof value.render === 'function' &&
719 value.$$typeof === undefined
720 ) {
624 - const Component = workInProgress.type;
625 -
721 // Proceed under the assumption that this is a class instance
722 workInProgress.tag = ClassComponent;
723
724 // Push context providers early to prevent context stack mismatches.
725 // During mounting we don't know the child context yet as the instance doesn't exist.
726 // We will invalidate the child context in finishClassComponent() right after rendering.
632 - const hasContext = pushLegacyContextProvider(workInProgress);
727 + let hasContext = false;
728 + if (isLegacyContextProvider(Component)) {
729 + hasContext = true;
730 + pushLegacyContextProvider(workInProgress);
731 + } else {
732 + hasContext = false;
733 + }
734
735 workInProgress.memoizedState =
736 value.state !== null && value.state !== undefined ? value.state : null;
@@ -638,16 +739,18 @@ function mountIndeterminateComponent(
739 if (typeof getDerivedStateFromProps === 'function') {
740 applyDerivedStateFromProps(
741 workInProgress,
742 + Component,
743 getDerivedStateFromProps,
744 props,
745 );
746 }
747
748 adoptClassInstance(workInProgress, value);
647 - mountClassInstance(workInProgress, renderExpirationTime);
749 + mountClassInstance(workInProgress, Component, props, renderExpirationTime);
750 return finishClassComponent(
751 current,
752 workInProgress,
753 + Component,
754 true,
755 hasContext,
756 renderExpirationTime,
@@ -656,8 +759,6 @@ function mountIndeterminateComponent(
759 // Proceed under the assumption that this is a functional component
760 workInProgress.tag = FunctionalComponent;
761 if (__DEV__) {
659 - const Component = workInProgress.type;
660 -
762 if (Component) {
763 warningWithoutStack(
764 !Component.childContextTypes,
@@ -688,8 +789,8 @@ function mountIndeterminateComponent(
789 }
790 }
791
691 - if (typeof fn.getDerivedStateFromProps === 'function') {
692 - const componentName = getComponentName(fn) || 'Unknown';
792 + if (typeof Component.getDerivedStateFromProps === 'function') {
793 + const componentName = getComponentName(Component) || 'Unknown';
794
795 if (!didWarnAboutGetDerivedStateOnFunctionalComponent[componentName]) {
796 warningWithoutStack(
@@ -994,9 +1095,21 @@ function beginWork(
1095 case HostComponent:
1096 pushHostContext(workInProgress);
1097 break;
997 - case ClassComponent:
998 - pushLegacyContextProvider(workInProgress);
1098 + case ClassComponent: {
1099 + const Component = workInProgress.type;
1100 + if (isLegacyContextProvider(Component)) {
1101 + pushLegacyContextProvider(workInProgress);
1102 + }
1103 + break;
1104 + }
1105 + case ClassComponentLazy: {
1106 + const thenable = workInProgress.type;
1107 + const Component = getResultFromResolvedThenable(thenable);
1108 + if (isLegacyContextProvider(Component)) {
1109 + pushLegacyContextProvider(workInProgress);
1110 + }
1111 break;
1112 + }
1113 case HostPortal:
1114 pushHostContainer(
1115 workInProgress,
@@ -1025,24 +1138,65 @@ function beginWork(
1138 workInProgress.expirationTime = NoWork;
1139
1140 switch (workInProgress.tag) {
1028 - case IndeterminateComponent:
1141 + case IndeterminateComponent: {
1142 + const Component = workInProgress.type;
1143 return mountIndeterminateComponent(
1144 current,
1145 workInProgress,
1146 + Component,
1147 renderExpirationTime,
1148 );
1034 - case FunctionalComponent:
1149 + }
1150 + case FunctionalComponent: {
1151 + const Component = workInProgress.type;
1152 + const unresolvedProps = workInProgress.pendingProps;
1153 return updateFunctionalComponent(
1154 current,
1155 workInProgress,
1156 + Component,
1157 + unresolvedProps,
1158 + renderExpirationTime,
1159 + );
1160 + }
1161 + case FunctionalComponentLazy: {
1162 + const thenable = workInProgress.type;
1163 + const Component = getResultFromResolvedThenable(thenable);
1164 + const unresolvedProps = workInProgress.pendingProps;
1165 + const child = updateFunctionalComponent(
1166 + current,
1167 + workInProgress,
1168 + Component,
1169 + resolveDefaultProps(Component, unresolvedProps),
1170 renderExpirationTime,
1171 );
1040 - case ClassComponent:
1172 + workInProgress.memoizedProps = unresolvedProps;
1173 + return child;
1174 + }
1175 + case ClassComponent: {
1176 + const Component = workInProgress.type;
1177 + const unresolvedProps = workInProgress.pendingProps;
1178 return updateClassComponent(
1179 current,
1180 workInProgress,
1181 + Component,
1182 + unresolvedProps,
1183 renderExpirationTime,
1184 );
1185 + }
1186 + case ClassComponentLazy: {
1187 + const thenable = workInProgress.type;
1188 + const Component = getResultFromResolvedThenable(thenable);
1189 + const unresolvedProps = workInProgress.pendingProps;
1190 + const child = updateClassComponent(
1191 + current,
1192 + workInProgress,
1193 + Component,
1194 + resolveDefaultProps(Component, unresolvedProps),
1195 + renderExpirationTime,
1196 + );
1197 + workInProgress.memoizedProps = unresolvedProps;
1198 + return child;
1199 + }
1200 case HostRoot:
1201 return updateHostRoot(current, workInProgress, renderExpirationTime);
1202 case HostComponent:
@@ -1061,8 +1215,29 @@ function beginWork(
1215 workInProgress,
1216 renderExpirationTime,
1217 );
1064 - case ForwardRef:
1065 - return updateForwardRef(current, workInProgress, renderExpirationTime);
1218 + case ForwardRef: {
1219 + const type = workInProgress.type;
1220 + return updateForwardRef(
1221 + current,
1222 + workInProgress,
1223 + type,
1224 + workInProgress.pendingProps,
1225 + renderExpirationTime,
1226 + );
1227 + }
1228 + case ForwardRefLazy:
1229 + const thenable = workInProgress.type;
1230 + const Component = getResultFromResolvedThenable(thenable);
1231 + const unresolvedProps = workInProgress.pendingProps;
1232 + const child = updateForwardRef(
1233 + current,
1234 + workInProgress,
1235 + Component,
1236 + resolveDefaultProps(Component, unresolvedProps),
1237 + renderExpirationTime,
1238 + );
1239 + workInProgress.memoizedProps = unresolvedProps;
1240 + return child;
1241 case Fragment:
1242 return updateFragment(current, workInProgress, renderExpirationTime);
1243 case Mode:
packages/react-reconciler/src/ReactFiberClassComponent.js
+44 -36
@@ -41,7 +41,6 @@ import {
41 cacheContext,
42 getMaskedContext,
43 getUnmaskedContext,
44 - isContextConsumer,
44 hasContextChanged,
45 emptyContextObject,
46 } from './ReactFiberContext';
@@ -131,6 +130,7 @@ if (__DEV__) {
130
131 export function applyDerivedStateFromProps(
132 workInProgress: Fiber,
133 + ctor: any,
134 getDerivedStateFromProps: (props: any, state: any) => any,
135 nextProps: any,
136 ) {
@@ -150,7 +150,7 @@ export function applyDerivedStateFromProps(
150 const partialState = getDerivedStateFromProps(nextProps, prevState);
151
152 if (__DEV__) {
153 - warnOnUndefinedDerivedState(workInProgress.type, partialState);
153 + warnOnUndefinedDerivedState(ctor, partialState);
154 }
155 // Merge the partial state and the previous state.
156 const memoizedState =
@@ -227,6 +227,7 @@ const classComponentUpdater = {
227
228 function checkShouldComponentUpdate(
229 workInProgress,
230 + ctor,
231 oldProps,
232 newProps,
233 oldState,
@@ -234,7 +235,6 @@ function checkShouldComponentUpdate(
235 nextLegacyContext,
236 ) {
237 const instance = workInProgress.stateNode;
237 - const ctor = workInProgress.type;
238 if (typeof instance.shouldComponentUpdate === 'function') {
239 startPhaseTimer(workInProgress, 'shouldComponentUpdate');
240 const shouldUpdate = instance.shouldComponentUpdate(
@@ -265,15 +265,14 @@ function checkShouldComponentUpdate(
265 return true;
266 }
267
268 -function checkClassInstance(workInProgress: Fiber) {
268 +function checkClassInstance(workInProgress: Fiber, ctor: any, newProps: any) {
269 const instance = workInProgress.stateNode;
270 - const type = workInProgress.type;
270 if (__DEV__) {
272 - const name = getComponentName(type) || 'Component';
271 + const name = getComponentName(ctor) || 'Component';
272 const renderPresent = instance.render;
273
274 if (!renderPresent) {
276 - if (type.prototype && typeof type.prototype.render === 'function') {
275 + if (ctor.prototype && typeof ctor.prototype.render === 'function') {
276 warningWithoutStack(
277 false,
278 '%s(...): No `render` method found on the returned component ' +
@@ -336,8 +335,8 @@ function checkClassInstance(workInProgress: Fiber) {
335 name,
336 );
337 if (
339 - type.prototype &&
340 - type.prototype.isPureReactComponent &&
338 + ctor.prototype &&
339 + ctor.prototype.isPureReactComponent &&
340 typeof instance.shouldComponentUpdate !== 'undefined'
341 ) {
342 warningWithoutStack(
@@ -345,7 +344,7 @@ function checkClassInstance(workInProgress: Fiber) {
344 '%s has a method called shouldComponentUpdate(). ' +
345 'shouldComponentUpdate should not be used when extending React.PureComponent. ' +
346 'Please extend React.Component if shouldComponentUpdate is used.',
348 - getComponentName(type) || 'A pure component',
347 + getComponentName(ctor) || 'A pure component',
348 );
349 }
350 const noComponentDidUnmount =
@@ -384,7 +383,7 @@ function checkClassInstance(workInProgress: Fiber) {
383 'UNSAFE_componentWillRecieveProps(). Did you mean UNSAFE_componentWillReceiveProps()?',
384 name,
385 );
387 - const hasMutatedProps = instance.props !== workInProgress.pendingProps;
386 + const hasMutatedProps = instance.props !== newProps;
387 warningWithoutStack(
388 instance.props === undefined || !hasMutatedProps,
389 '%s(...): When calling super() in `%s`, make sure to pass ' +
@@ -404,14 +403,14 @@ function checkClassInstance(workInProgress: Fiber) {
403 if (
404 typeof instance.getSnapshotBeforeUpdate === 'function' &&
405 typeof instance.componentDidUpdate !== 'function' &&
407 - !didWarnAboutGetSnapshotBeforeUpdateWithoutDidUpdate.has(type)
406 + !didWarnAboutGetSnapshotBeforeUpdateWithoutDidUpdate.has(ctor)
407 ) {
409 - didWarnAboutGetSnapshotBeforeUpdateWithoutDidUpdate.add(type);
408 + didWarnAboutGetSnapshotBeforeUpdateWithoutDidUpdate.add(ctor);
409 warningWithoutStack(
410 false,
411 '%s: getSnapshotBeforeUpdate() should be used with componentDidUpdate(). ' +
412 'This component defines getSnapshotBeforeUpdate() only.',
414 - getComponentName(type),
413 + getComponentName(ctor),
414 );
415 }
416
@@ -432,7 +431,7 @@ function checkClassInstance(workInProgress: Fiber) {
431 name,
432 );
433 const noStaticGetSnapshotBeforeUpdate =
435 - typeof type.getSnapshotBeforeUpdate !== 'function';
434 + typeof ctor.getSnapshotBeforeUpdate !== 'function';
435 warningWithoutStack(
436 noStaticGetSnapshotBeforeUpdate,
437 '%s: getSnapshotBeforeUpdate() is defined as a static method ' +
@@ -449,7 +448,7 @@ function checkClassInstance(workInProgress: Fiber) {
448 }
449 if (typeof instance.getChildContext === 'function') {
450 warningWithoutStack(
452 - typeof type.childContextTypes === 'object',
451 + typeof ctor.childContextTypes === 'object',
452 '%s.getChildContext(): childContextTypes must be defined in order to ' +
453 'use getChildContext().',
454 name,
@@ -470,13 +469,14 @@ function adoptClassInstance(workInProgress: Fiber, instance: any): void {
469
470 function constructClassInstance(
471 workInProgress: Fiber,
472 + ctor: any,
473 props: any,
474 renderExpirationTime: ExpirationTime,
475 ): any {
476 - const ctor = workInProgress.type;
477 - const unmaskedContext = getUnmaskedContext(workInProgress);
478 - const needsContext = isContextConsumer(workInProgress);
479 - const context = needsContext
476 + const unmaskedContext = getUnmaskedContext(workInProgress, ctor);
477 + const contextTypes = ctor.contextTypes;
478 + const isContextConsumer = contextTypes !== null && contextTypes !== undefined;
479 + const context = isContextConsumer
480 ? getMaskedContext(workInProgress, unmaskedContext)
481 : emptyContextObject;
482
@@ -585,7 +585,7 @@ function constructClassInstance(
585
586 // Cache unmasked context so we can avoid recreating masked context unless necessary.
587 // ReactFiberContext usually updates this cache but can't for newly-created instances.
588 - if (needsContext) {
588 + if (isContextConsumer) {
589 cacheContext(workInProgress, unmaskedContext, context);
590 }
591
@@ -657,19 +657,18 @@ function callComponentWillReceiveProps(
657 // Invokes the mount life-cycles on a previously never rendered instance.
658 function mountClassInstance(
659 workInProgress: Fiber,
660 + ctor: any,
661 + newProps: any,
662 renderExpirationTime: ExpirationTime,
663 ): void {
662 - const ctor = workInProgress.type;
663 -
664 if (__DEV__) {
665 - checkClassInstance(workInProgress);
665 + checkClassInstance(workInProgress, ctor, newProps);
666 }
667
668 const instance = workInProgress.stateNode;
669 - const props = workInProgress.pendingProps;
670 - const unmaskedContext = getUnmaskedContext(workInProgress);
669 + const unmaskedContext = getUnmaskedContext(workInProgress, ctor);
670
672 - instance.props = props;
671 + instance.props = newProps;
672 instance.state = workInProgress.memoizedState;
673 instance.refs = emptyRefsObject;
674 instance.context = getMaskedContext(workInProgress, unmaskedContext);
@@ -700,7 +699,7 @@ function mountClassInstance(
699 processUpdateQueue(
700 workInProgress,
701 updateQueue,
703 - props,
702 + newProps,
703 instance,
704 renderExpirationTime,
705 );
@@ -709,7 +708,12 @@ function mountClassInstance(
708
709 const getDerivedStateFromProps = ctor.getDerivedStateFromProps;
710 if (typeof getDerivedStateFromProps === 'function') {
712 - applyDerivedStateFromProps(workInProgress, getDerivedStateFromProps, props);
711 + applyDerivedStateFromProps(
712 + workInProgress,
713 + ctor,
714 + getDerivedStateFromProps,
715 + newProps,
716 + );
717 instance.state = workInProgress.memoizedState;
718 }
719
@@ -729,7 +733,7 @@ function mountClassInstance(
733 processUpdateQueue(
734 workInProgress,
735 updateQueue,
732 - props,
736 + newProps,
737 instance,
738 renderExpirationTime,
739 );
@@ -744,17 +748,17 @@ function mountClassInstance(
748
749 function resumeMountClassInstance(
750 workInProgress: Fiber,
751 + ctor: any,
752 + newProps: any,
753 renderExpirationTime: ExpirationTime,
754 ): boolean {
749 - const ctor = workInProgress.type;
755 const instance = workInProgress.stateNode;
756
757 const oldProps = workInProgress.memoizedProps;
753 - const newProps = workInProgress.pendingProps;
758 instance.props = oldProps;
759
760 const oldContext = instance.context;
757 - const nextLegacyUnmaskedContext = getUnmaskedContext(workInProgress);
761 + const nextLegacyUnmaskedContext = getUnmaskedContext(workInProgress, ctor);
762 const nextLegacyContext = getMaskedContext(
763 workInProgress,
764 nextLegacyUnmaskedContext,
@@ -818,6 +822,7 @@ function resumeMountClassInstance(
822 if (typeof getDerivedStateFromProps === 'function') {
823 applyDerivedStateFromProps(
824 workInProgress,
825 + ctor,
826 getDerivedStateFromProps,
827 newProps,
828 );
@@ -828,6 +833,7 @@ function resumeMountClassInstance(
833 checkHasForceUpdateAfterProcessing() ||
834 checkShouldComponentUpdate(
835 workInProgress,
836 + ctor,
837 oldProps,
838 newProps,
839 oldState,
@@ -881,17 +887,17 @@ function resumeMountClassInstance(
887 function updateClassInstance(
888 current: Fiber,
889 workInProgress: Fiber,
890 + ctor: any,
891 + newProps: any,
892 renderExpirationTime: ExpirationTime,
893 ): boolean {
886 - const ctor = workInProgress.type;
894 const instance = workInProgress.stateNode;
895
896 const oldProps = workInProgress.memoizedProps;
890 - const newProps = workInProgress.pendingProps;
897 instance.props = oldProps;
898
899 const oldContext = instance.context;
894 - const nextLegacyUnmaskedContext = getUnmaskedContext(workInProgress);
900 + const nextLegacyUnmaskedContext = getUnmaskedContext(workInProgress, ctor);
901 const nextLegacyContext = getMaskedContext(
902 workInProgress,
903 nextLegacyUnmaskedContext,
@@ -969,6 +975,7 @@ function updateClassInstance(
975 if (typeof getDerivedStateFromProps === 'function') {
976 applyDerivedStateFromProps(
977 workInProgress,
978 + ctor,
979 getDerivedStateFromProps,
980 newProps,
981 );
@@ -979,6 +986,7 @@ function updateClassInstance(
986 checkHasForceUpdateAfterProcessing() ||
987 checkShouldComponentUpdate(
988 workInProgress,
989 + ctor,
990 oldProps,
991 newProps,
992 oldState,
packages/react-reconciler/src/ReactFiberCommitWork.js
+12 -5
@@ -22,6 +22,7 @@ import type {CapturedValue, CapturedError} from './ReactCapturedValue';
22 import {enableProfilerTimer, enableSuspense} from 'shared/ReactFeatureFlags';
23 import {
24 ClassComponent,
25 + ClassComponentLazy,
26 HostRoot,
27 HostComponent,
28 HostText,
@@ -179,7 +180,8 @@ function commitBeforeMutationLifeCycles(
180 finishedWork: Fiber,
181 ): void {
182 switch (finishedWork.tag) {
182 - case ClassComponent: {
183 + case ClassComponent:
184 + case ClassComponentLazy: {
185 if (finishedWork.effectTag & Snapshot) {
186 if (current !== null) {
187 const prevProps = current.memoizedProps;
@@ -235,7 +237,8 @@ function commitLifeCycles(
237 committedExpirationTime: ExpirationTime,
238 ): void {
239 switch (finishedWork.tag) {
238 - case ClassComponent: {
240 + case ClassComponent:
241 + case ClassComponentLazy: {
242 const instance = finishedWork.stateNode;
243 if (finishedWork.effectTag & Update) {
244 if (current === null) {
@@ -281,6 +284,7 @@ function commitLifeCycles(
284 instance = getPublicInstance(finishedWork.child.stateNode);
285 break;
286 case ClassComponent:
287 + case ClassComponentLazy:
288 instance = finishedWork.child.stateNode;
289 break;
290 }
@@ -400,7 +404,8 @@ function commitUnmount(current: Fiber): void {
404 onCommitUnmount(current);
405
406 switch (current.tag) {
403 - case ClassComponent: {
407 + case ClassComponent:
408 + case ClassComponentLazy: {
409 safelyDetachRef(current);
410 const instance = current.stateNode;
411 if (typeof instance.componentWillUnmount === 'function') {
@@ -493,7 +498,8 @@ function commitContainer(finishedWork: Fiber) {
498 }
499
500 switch (finishedWork.tag) {
496 - case ClassComponent: {
501 + case ClassComponent:
502 + case ClassComponentLazy: {
503 return;
504 }
505 case HostComponent: {
@@ -778,7 +784,8 @@ function commitWork(current: Fiber | null, finishedWork: Fiber): void {
784 }
785
786 switch (finishedWork.tag) {
781 - case ClassComponent: {
787 + case ClassComponent:
788 + case ClassComponentLazy: {
789 return;
790 }
791 case HostComponent: {
packages/react-reconciler/src/ReactFiberCompleteWork.js
+19 -3
@@ -23,7 +23,9 @@ import type {
23 import {
24 IndeterminateComponent,
25 FunctionalComponent,
26 + FunctionalComponentLazy,
27 ClassComponent,
28 + ClassComponentLazy,
29 HostRoot,
30 HostComponent,
31 HostText,
@@ -35,9 +37,11 @@ import {
37 Mode,
38 Profiler,
39 PlaceholderComponent,
40 + ForwardRefLazy,
41 } from 'shared/ReactTypeOfWork';
42 import {Placement, Ref, Update} from 'shared/ReactTypeOfSideEffect';
43 import invariant from 'shared/invariant';
44 +import {getResultFromResolvedThenable} from 'shared/ReactLazyComponent';
45
46 import {
47 createInstance,
@@ -59,7 +63,8 @@ import {
63 popHostContainer,
64 } from './ReactFiberHostContext';
65 import {
62 - popContextProvider as popLegacyContextProvider,
66 + isContextProvider as isLegacyContextProvider,
67 + popContext as popLegacyContext,
68 popTopLevelContextObject as popTopLevelLegacyContextObject,
69 } from './ReactFiberContext';
70 import {popProvider} from './ReactFiberNewContext';
@@ -313,10 +318,20 @@ function completeWork(
318
319 switch (workInProgress.tag) {
320 case FunctionalComponent:
321 + case FunctionalComponentLazy:
322 break;
323 case ClassComponent: {
318 - // We are leaving this subtree, so pop context if any.
319 - popLegacyContextProvider(workInProgress);
324 + const Component = workInProgress.type;
325 + if (isLegacyContextProvider(Component)) {
326 + popLegacyContext(workInProgress);
327 + }
328 + break;
329 + }
330 + case ClassComponentLazy: {
331 + const Component = getResultFromResolvedThenable(workInProgress.type);
332 + if (isLegacyContextProvider(Component)) {
333 + popLegacyContext(workInProgress);
334 + }
335 break;
336 }
337 case HostRoot: {
@@ -479,6 +494,7 @@ function completeWork(
494 break;
495 }
496 case ForwardRef:
497 + case ForwardRefLazy:
498 break;
499 case PlaceholderComponent:
500 break;
packages/react-reconciler/src/ReactFiberContext.js
+56 -38
@@ -11,11 +11,16 @@ import type {Fiber} from './ReactFiber';
11 import type {StackCursor} from './ReactFiberStack';
12
13 import {isFiberMounted} from 'react-reconciler/reflection';
14 -import {ClassComponent, HostRoot} from 'shared/ReactTypeOfWork';
14 +import {
15 + ClassComponent,
16 + HostRoot,
17 + ClassComponentLazy,
18 +} from 'shared/ReactTypeOfWork';
19 import getComponentName from 'shared/getComponentName';
20 import invariant from 'shared/invariant';
21 import warningWithoutStack from 'shared/warningWithoutStack';
22 import checkPropTypes from 'prop-types/checkPropTypes';
23 +import {getResultFromResolvedThenable} from 'shared/ReactLazyComponent';
24
25 import * as ReactCurrentFiber from './ReactCurrentFiber';
26 import {startPhaseTimer, stopPhaseTimer} from './ReactDebugFiberPerf';
@@ -41,8 +46,11 @@ let didPerformWorkStackCursor: StackCursor<boolean> = createCursor(false);
46 // pushed the next context provider, and now need to merge their contexts.
47 let previousContext: Object = emptyContextObject;
48
44 -function getUnmaskedContext(workInProgress: Fiber): Object {
45 - const hasOwnContext = isContextProvider(workInProgress);
49 +function getUnmaskedContext(
50 + workInProgress: Fiber,
51 + Component: Function,
52 +): Object {
53 + const hasOwnContext = isContextProvider(Component);
54 if (hasOwnContext) {
55 // If the fiber is a context provider itself, when we read its context
56 // we have already pushed its own child context on the stack. A context
@@ -113,19 +121,12 @@ function hasContextChanged(): boolean {
121 return didPerformWorkStackCursor.current;
122 }
123
116 -function isContextConsumer(fiber: Fiber): boolean {
117 - return fiber.tag === ClassComponent && fiber.type.contextTypes != null;
118 -}
119 -
120 -function isContextProvider(fiber: Fiber): boolean {
121 - return fiber.tag === ClassComponent && fiber.type.childContextTypes != null;
124 +function isContextProvider(type: Function): boolean {
125 + const childContextTypes = type.childContextTypes;
126 + return childContextTypes !== null && childContextTypes !== undefined;
127 }
128
124 -function popContextProvider(fiber: Fiber): void {
125 - if (!isContextProvider(fiber)) {
126 - return;
127 - }
128 -
129 +function popContext(fiber: Fiber): void {
130 pop(didPerformWorkStackCursor, fiber);
131 pop(contextStackCursor, fiber);
132 }
@@ -150,9 +151,12 @@ function pushTopLevelContextObject(
151 push(didPerformWorkStackCursor, didChange, fiber);
152 }
153
153 -function processChildContext(fiber: Fiber, parentContext: Object): Object {
154 +function processChildContext(
155 + fiber: Fiber,
156 + type: any,
157 + parentContext: Object,
158 +): Object {
159 const instance = fiber.stateNode;
155 - const type = fiber.type;
160 const childContextTypes = type.childContextTypes;
161
162 // TODO (bvaughn) Replace this behavior with an invariant() in the future.
@@ -214,10 +218,6 @@ function processChildContext(fiber: Fiber, parentContext: Object): Object {
218 }
219
220 function pushContextProvider(workInProgress: Fiber): boolean {
217 - if (!isContextProvider(workInProgress)) {
218 - return false;
219 - }
220 -
221 const instance = workInProgress.stateNode;
222 // We push the context as early as possible to ensure stack integrity.
223 // If the instance does not exist yet, we will push null at first,
@@ -241,6 +241,7 @@ function pushContextProvider(workInProgress: Fiber): boolean {
241
242 function invalidateContextProvider(
243 workInProgress: Fiber,
244 + type: any,
245 didChange: boolean,
246 ): void {
247 const instance = workInProgress.stateNode;
@@ -254,7 +255,11 @@ function invalidateContextProvider(
255 // Merge parent and own context.
256 // Skip this if we're not updating due to sCU.
257 // This avoids unnecessarily recomputing memoized values.
257 - const mergedContext = processChildContext(workInProgress, previousContext);
258 + const mergedContext = processChildContext(
259 + workInProgress,
260 + type,
261 + previousContext,
262 + );
263 instance.__reactInternalMemoizedMergedChildContext = mergedContext;
264
265 // Replace the old (or empty) context with the new one.
@@ -274,25 +279,39 @@ function findCurrentUnmaskedContext(fiber: Fiber): Object {
279 // Currently this is only used with renderSubtreeIntoContainer; not sure if it
280 // makes sense elsewhere
281 invariant(
277 - isFiberMounted(fiber) && fiber.tag === ClassComponent,
282 + isFiberMounted(fiber) &&
283 + (fiber.tag === ClassComponent || fiber.tag === ClassComponentLazy),
284 'Expected subtree parent to be a mounted class component. ' +
285 'This error is likely caused by a bug in React. Please file an issue.',
286 );
287
282 - let node: Fiber = fiber;
283 - while (node.tag !== HostRoot) {
284 - if (isContextProvider(node)) {
285 - return node.stateNode.__reactInternalMemoizedMergedChildContext;
288 + let node = fiber;
289 + do {
290 + switch (node.tag) {
291 + case HostRoot:
292 + return node.stateNode.context;
293 + case ClassComponent: {
294 + const Component = node.type;
295 + if (isContextProvider(Component)) {
296 + return node.stateNode.__reactInternalMemoizedMergedChildContext;
297 + }
298 + break;
299 + }
300 + case ClassComponentLazy: {
301 + const Component = getResultFromResolvedThenable(node.type);
302 + if (isContextProvider(Component)) {
303 + return node.stateNode.__reactInternalMemoizedMergedChildContext;
304 + }
305 + break;
306 + }
307 }
287 - const parent = node.return;
288 - invariant(
289 - parent,
290 - 'Found unexpected detached subtree parent. ' +
291 - 'This error is likely caused by a bug in React. Please file an issue.',
292 - );
293 - node = parent;
294 - }
295 - return node.stateNode.context;
308 + node = node.return;
309 + } while (node !== null);
310 + invariant(
311 + false,
312 + 'Found unexpected detached subtree parent. ' +
313 + 'This error is likely caused by a bug in React. Please file an issue.',
314 + );
315 }
316
317 export {
@@ -300,12 +319,11 @@ export {
319 cacheContext,
320 getMaskedContext,
321 hasContextChanged,
303 - isContextConsumer,
304 - isContextProvider,
305 - popContextProvider,
322 + popContext,
323 popTopLevelContextObject,
324 pushTopLevelContextObject,
325 processChildContext,
326 + isContextProvider,
327 pushContextProvider,
328 invalidateContextProvider,
329 findCurrentUnmaskedContext,
packages/react-reconciler/src/ReactFiberLazyComponent.js new
+55
@@ -0,0 +1,55 @@
1 +/**
2 + * Copyright (c) 2013-present, Facebook, Inc.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +import type {Thenable} from 'shared/ReactLazyComponent';
11 +
12 +import {Resolved, Rejected, Pending} from 'shared/ReactLazyComponent';
13 +
14 +export function readLazyComponentType<T>(thenable: Thenable<T>): T {
15 + const status = thenable._reactStatus;
16 + switch (status) {
17 + case Resolved:
18 + const Component: T = thenable._reactResult;
19 + return Component;
20 + case Rejected:
21 + throw thenable._reactResult;
22 + case Pending:
23 + throw thenable;
24 + default: {
25 + thenable._reactStatus = Pending;
26 + thenable.then(
27 + resolvedValue => {
28 + if (thenable._reactStatus === Pending) {
29 + thenable._reactStatus = Resolved;
30 + if (typeof resolvedValue === 'object' && resolvedValue !== null) {
31 + // If the `default` property is not empty, assume it's the result
32 + // of an async import() and use that. Otherwise, use the
33 + // resolved value itself.
34 + const defaultExport = (resolvedValue: any).default;
35 + resolvedValue =
36 + typeof defaultExport !== undefined && defaultExport !== null
37 + ? defaultExport
38 + : resolvedValue;
39 + } else {
40 + resolvedValue = resolvedValue;
41 + }
42 + thenable._reactResult = resolvedValue;
43 + }
44 + },
45 + error => {
46 + if (thenable._reactStatus === Pending) {
47 + thenable._reactStatus = Rejected;
48 + thenable._reactResult = error;
49 + }
50 + },
51 + );
52 + throw thenable;
53 + }
54 + }
55 +}
packages/react-reconciler/src/ReactFiberNewContext.js
+9 -2
@@ -23,7 +23,11 @@ import {isPrimaryRenderer} from './ReactFiberHostConfig';
23 import {createCursor, push, pop} from './ReactFiberStack';
24 import maxSigned31BitInt from './maxSigned31BitInt';
25 import {NoWork} from './ReactFiberExpirationTime';
26 -import {ContextProvider, ClassComponent} from 'shared/ReactTypeOfWork';
26 +import {
27 + ContextProvider,
28 + ClassComponent,
29 + ClassComponentLazy,
30 +} from 'shared/ReactTypeOfWork';
31
32 import invariant from 'shared/invariant';
33 import warning from 'shared/warning';
@@ -159,7 +163,10 @@ export function propagateContextChange(
163 ) {
164 // Match! Schedule an update on this fiber.
165
162 - if (fiber.tag === ClassComponent) {
166 + if (
167 + fiber.tag === ClassComponent ||
168 + fiber.tag === ClassComponentLazy
169 + ) {
170 // Schedule a force update on the work-in-progress.
171 const update = createUpdate(renderExpirationTime);
172 update.tag = ForceUpdate;
packages/react-reconciler/src/ReactFiberReconciler.js
+21 -5
@@ -23,17 +23,22 @@ import {
23 findCurrentHostFiberWithNoPortals,
24 } from 'react-reconciler/reflection';
25 import * as ReactInstanceMap from 'shared/ReactInstanceMap';
26 -import {HostComponent} from 'shared/ReactTypeOfWork';
26 +import {
27 + HostComponent,
28 + ClassComponent,
29 + ClassComponentLazy,
30 +} from 'shared/ReactTypeOfWork';
31 import getComponentName from 'shared/getComponentName';
32 import invariant from 'shared/invariant';
33 import warningWithoutStack from 'shared/warningWithoutStack';
34 +import {getResultFromResolvedThenable} from 'shared/ReactLazyComponent';
35
36 import {getPublicInstance} from './ReactFiberHostConfig';
37 import {
38 findCurrentUnmaskedContext,
34 - isContextProvider,
39 processChildContext,
40 emptyContextObject,
41 + isContextProvider as isLegacyContextProvider,
42 } from './ReactFiberContext';
43 import {createFiberRoot} from './ReactFiberRoot';
44 import * as ReactFiberDevToolsHook from './ReactFiberDevToolsHook';
@@ -91,9 +96,20 @@ function getContextForSubtree(
96
97 const fiber = ReactInstanceMap.get(parentComponent);
98 const parentContext = findCurrentUnmaskedContext(fiber);
94 - return isContextProvider(fiber)
95 - ? processChildContext(fiber, parentContext)
96 - : parentContext;
99 +
100 + if (fiber.tag === ClassComponent) {
101 + const Component = fiber.type;
102 + if (isLegacyContextProvider(Component)) {
103 + return processChildContext(fiber, Component, parentContext);
104 + }
105 + } else if (fiber.tag === ClassComponentLazy) {
106 + const Component = getResultFromResolvedThenable(fiber.type);
107 + if (isLegacyContextProvider(Component)) {
108 + return processChildContext(fiber, Component, parentContext);
109 + }
110 + }
111 +
112 + return parentContext;
113 }
114
115 function scheduleRootUpdate(
packages/react-reconciler/src/ReactFiberScheduler.js
+23 -5
@@ -36,6 +36,7 @@ import {
36 import {
37 HostRoot,
38 ClassComponent,
39 + ClassComponentLazy,
40 HostComponent,
41 ContextProvider,
42 HostPortal,
@@ -51,6 +52,7 @@ import {
52 import getComponentName from 'shared/getComponentName';
53 import invariant from 'shared/invariant';
54 import warningWithoutStack from 'shared/warningWithoutStack';
55 +import {getResultFromResolvedThenable} from 'shared/ReactLazyComponent';
56
57 import {
58 scheduleTimeout,
@@ -110,8 +112,9 @@ import {AsyncMode, ProfileMode} from './ReactTypeOfMode';
112 import {enqueueUpdate, resetCurrentlyProcessingQueue} from './ReactUpdateQueue';
113 import {createCapturedValue} from './ReactCapturedValue';
114 import {
115 + isContextProvider as isLegacyContextProvider,
116 popTopLevelContextObject as popTopLevelLegacyContextObject,
114 - popContextProvider as popLegacyContextProvider,
117 + popContext as popLegacyContext,
118 } from './ReactFiberContext';
119 import {popProvider, resetContextDependences} from './ReactFiberNewContext';
120 import {popHostContext, popHostContainer} from './ReactFiberHostContext';
@@ -287,9 +290,20 @@ if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
290 case HostComponent:
291 popHostContext(failedUnitOfWork);
292 break;
290 - case ClassComponent:
291 - popLegacyContextProvider(failedUnitOfWork);
293 + case ClassComponent: {
294 + const Component = failedUnitOfWork.type;
295 + if (isLegacyContextProvider(Component)) {
296 + popLegacyContext(failedUnitOfWork);
297 + }
298 + break;
299 + }
300 + case ClassComponentLazy: {
301 + const Component = getResultFromResolvedThenable(failedUnitOfWork.type);
302 + if (isLegacyContextProvider(Component)) {
303 + popLegacyContext(failedUnitOfWork);
304 + }
305 break;
306 + }
307 case HostPortal:
308 popHostContainer(failedUnitOfWork);
309 break;
@@ -1292,6 +1306,7 @@ function dispatch(
1306 while (fiber !== null) {
1307 switch (fiber.tag) {
1308 case ClassComponent:
1309 + case ClassComponentLazy:
1310 const ctor = fiber.type;
1311 const instance = fiber.stateNode;
1312 if (
@@ -1499,7 +1514,7 @@ function scheduleWork(fiber: Fiber, expirationTime: ExpirationTime) {
1514 recordScheduleUpdate();
1515
1516 if (__DEV__) {
1502 - if (fiber.tag === ClassComponent) {
1517 + if (fiber.tag === ClassComponent || fiber.tag === ClassComponentLazy) {
1518 const instance = fiber.stateNode;
1519 warnAboutInvalidUpdates(instance);
1520 }
@@ -1507,7 +1522,10 @@ function scheduleWork(fiber: Fiber, expirationTime: ExpirationTime) {
1522
1523 const root = scheduleWorkToRoot(fiber, expirationTime);
1524 if (root === null) {
1510 - if (__DEV__ && fiber.tag === ClassComponent) {
1525 + if (
1526 + __DEV__ &&
1527 + (fiber.tag === ClassComponent || fiber.tag === ClassComponentLazy)
1528 + ) {
1529 warnAboutUpdateOnUnmounted(fiber);
1530 }
1531 return;
packages/react-reconciler/src/ReactFiberTreeReflection.js
+5 -1
@@ -17,6 +17,7 @@ import ReactSharedInternals from 'shared/ReactSharedInternals';
17 import getComponentName from 'shared/getComponentName';
18 import {
19 ClassComponent,
20 + ClassComponentLazy,
21 HostComponent,
22 HostRoot,
23 HostPortal,
@@ -66,7 +67,10 @@ export function isFiberMounted(fiber: Fiber): boolean {
67 export function isMounted(component: React$Component<any, any>): boolean {
68 if (__DEV__) {
69 const owner = (ReactCurrentOwner.current: any);
69 - if (owner !== null && owner.tag === ClassComponent) {
70 + if (
71 + owner !== null &&
72 + (owner.tag === ClassComponent || owner.tag === ClassComponentLazy)
73 + ) {
74 const ownerFiber: Fiber = owner;
75 const instance = ownerFiber.stateNode;
76 warningWithoutStack(
packages/react-reconciler/src/ReactFiberUnwindWork.js
+36 -4
@@ -18,6 +18,7 @@ import {
18 IndeterminateComponent,
19 FunctionalComponent,
20 ClassComponent,
21 + ClassComponentLazy,
22 HostRoot,
23 HostComponent,
24 HostPortal,
@@ -47,7 +48,8 @@ import {
48 import {logError} from './ReactFiberCommitWork';
49 import {popHostContainer, popHostContext} from './ReactFiberHostContext';
50 import {
50 - popContextProvider as popLegacyContextProvider,
51 + isContextProvider as isLegacyContextProvider,
52 + popContext as popLegacyContext,
53 popTopLevelContextObject as popTopLevelLegacyContextObject,
54 } from './ReactFiberContext';
55 import {popProvider} from './ReactFiberNewContext';
@@ -249,7 +251,10 @@ function throwException(
251 sourceFiber.tag = FunctionalComponent;
252 }
253
252 - if (sourceFiber.tag === ClassComponent) {
254 + if (
255 + sourceFiber.tag === ClassComponent ||
256 + sourceFiber.tag === ClassComponentLazy
257 + ) {
258 // We're going to commit this fiber even though it didn't
259 // complete. But we shouldn't call any lifecycle methods or
260 // callbacks. Remove all lifecycle effect tags.
@@ -343,6 +348,7 @@ function throwException(
348 return;
349 }
350 case ClassComponent:
351 + case ClassComponentLazy:
352 // Capture and retry
353 const errorInfo = value;
354 const ctor = workInProgress.type;
@@ -380,7 +386,22 @@ function unwindWork(
386 ) {
387 switch (workInProgress.tag) {
388 case ClassComponent: {
383 - popLegacyContextProvider(workInProgress);
389 + const Component = workInProgress.type;
390 + if (isLegacyContextProvider(Component)) {
391 + popLegacyContext(workInProgress);
392 + }
393 + const effectTag = workInProgress.effectTag;
394 + if (effectTag & ShouldCapture) {
395 + workInProgress.effectTag = (effectTag & ~ShouldCapture) | DidCapture;
396 + return workInProgress;
397 + }
398 + return null;
399 + }
400 + case ClassComponentLazy: {
401 + const Component = workInProgress.type._reactResult;
402 + if (isLegacyContextProvider(Component)) {
403 + popLegacyContext(workInProgress);
404 + }
405 const effectTag = workInProgress.effectTag;
406 if (effectTag & ShouldCapture) {
407 workInProgress.effectTag = (effectTag & ~ShouldCapture) | DidCapture;
@@ -426,7 +447,18 @@ function unwindWork(
447 function unwindInterruptedWork(interruptedWork: Fiber) {
448 switch (interruptedWork.tag) {
449 case ClassComponent: {
429 - popLegacyContextProvider(interruptedWork);
450 + const childContextTypes = interruptedWork.type.childContextTypes;
451 + if (childContextTypes !== null && childContextTypes !== undefined) {
452 + popLegacyContext(interruptedWork);
453 + }
454 + break;
455 + }
456 + case ClassComponentLazy: {
457 + const childContextTypes =
458 + interruptedWork.type._reactResult.childContextTypes;
459 + if (childContextTypes !== null && childContextTypes !== undefined) {
460 + popLegacyContext(interruptedWork);
461 + }
462 break;
463 }
464 case HostRoot: {
packages/react-reconciler/src/ReactUpdateQueue.js
+2 -2
@@ -93,7 +93,7 @@ import {
93 ShouldCapture,
94 DidCapture,
95 } from 'shared/ReactTypeOfSideEffect';
96 -import {ClassComponent} from 'shared/ReactTypeOfWork';
96 +import {ClassComponent, ClassComponentLazy} from 'shared/ReactTypeOfWork';
97
98 import {
99 debugRenderPhaseSideEffects,
@@ -275,7 +275,7 @@ export function enqueueUpdate<State>(fiber: Fiber, update: Update<State>) {
275
276 if (__DEV__) {
277 if (
278 - fiber.tag === ClassComponent &&
278 + (fiber.tag === ClassComponent || fiber.tag === ClassComponentLazy) &&
279 (currentlyProcessingQueue === queue1 ||
280 (queue2 !== null && currentlyProcessingQueue === queue2)) &&
281 !didWarnUpdateInsideUpdate
packages/react-reconciler/src/__tests__/ReactIncrementalSideEffects-test.internal.js
+1 -1
@@ -1204,7 +1204,7 @@ describe('ReactIncrementalSideEffects', () => {
1204
1205 ReactNoop.render(<Foo />);
1206 expect(ReactNoop.flush).toWarnDev(
1207 - 'Warning: A string ref, "bar", has been found within a strict mode tree.',
1207 + 'Warning: A string ref, "bar", has been found within a strict mode tree.',
1208 );
1209
1210 expect(fooInstance.refs.bar.test).toEqual('test');
packages/react-reconciler/src/__tests__/ReactSuspense-test.internal.js
+198
@@ -1347,6 +1347,204 @@ describe('ReactSuspense', () => {
1347 });
1348 });
1349
1350 + describe('Promise as element type', () => {
1351 + it('accepts a promise as an element type', async () => {
1352 + const LazyText = Promise.resolve(Text);
1353 +
1354 + ReactNoop.render(
1355 + <Placeholder fallback={<Text text="Loading..." />}>
1356 + <LazyText text="Hi" />
1357 + </Placeholder>,
1358 + );
1359 + expect(ReactNoop.flush()).toEqual(['Loading...']);
1360 + expect(ReactNoop.getChildren()).toEqual([]);
1361 +
1362 + await LazyText;
1363 +
1364 + expect(ReactNoop.flush()).toEqual(['Hi']);
1365 + expect(ReactNoop.getChildren()).toEqual([span('Hi')]);
1366 +
1367 + // Should not suspend on update
1368 + ReactNoop.render(
1369 + <Placeholder fallback={<Text text="Loading..." />}>
1370 + <LazyText text="Hi again" />
1371 + </Placeholder>,
1372 + );
1373 + expect(ReactNoop.flush()).toEqual(['Hi again']);
1374 + expect(ReactNoop.getChildren()).toEqual([span('Hi again')]);
1375 + });
1376 +
1377 + it('throws if promise rejects', async () => {
1378 + const LazyText = Promise.reject(new Error('Bad network'));
1379 +
1380 + ReactNoop.render(
1381 + <Placeholder fallback={<Text text="Loading..." />}>
1382 + <LazyText text="Hi" />
1383 + </Placeholder>,
1384 + );
1385 + expect(ReactNoop.flush()).toEqual(['Loading...']);
1386 +
1387 + await LazyText.catch(() => {});
1388 +
1389 + expect(() => ReactNoop.flush()).toThrow('Bad network');
1390 + });
1391 +
1392 + it('mount and reorder', async () => {
1393 + class Child extends React.Component {
1394 + componentDidMount() {
1395 + ReactNoop.yield('Did mount: ' + this.props.label);
1396 + }
1397 + componentDidUpdate() {
1398 + ReactNoop.yield('Did update: ' + this.props.label);
1399 + }
1400 + render() {
1401 + return <Text text={this.props.label} />;
1402 + }
1403 + }
1404 +
1405 + const LazyChildA = Promise.resolve(Child);
1406 + const LazyChildB = Promise.resolve(Child);
1407 +
1408 + function Parent({swap}) {
1409 + return (
1410 + <Placeholder fallback={<Text text="Loading..." />}>
1411 + {swap
1412 + ? [
1413 + <LazyChildB key="B" label="B" />,
1414 + <LazyChildA key="A" label="A" />,
1415 + ]
1416 + : [
1417 + <LazyChildA key="A" label="A" />,
1418 + <LazyChildB key="B" label="B" />,
1419 + ]}
1420 + </Placeholder>
1421 + );
1422 + }
1423 +
1424 + ReactNoop.render(<Parent swap={false} />);
1425 + expect(ReactNoop.flush()).toEqual(['Loading...']);
1426 + expect(ReactNoop.getChildren()).toEqual([]);
1427 +
1428 + await LazyChildA;
1429 + await LazyChildB;
1430 +
1431 + expect(ReactNoop.flush()).toEqual([
1432 + 'A',
1433 + 'B',
1434 + 'Did mount: A',
1435 + 'Did mount: B',
1436 + ]);
1437 + expect(ReactNoop.getChildren()).toEqual([span('A'), span('B')]);
1438 +
1439 + // Swap the position of A and B
1440 + ReactNoop.render(<Parent swap={true} />);
1441 + expect(ReactNoop.flush()).toEqual([
1442 + 'B',
1443 + 'A',
1444 + 'Did update: B',
1445 + 'Did update: A',
1446 + ]);
1447 + expect(ReactNoop.getChildren()).toEqual([span('B'), span('A')]);
1448 + });
1449 +
1450 + it('uses `default` property, if it exists', async () => {
1451 + const LazyText = Promise.resolve({default: Text});
1452 +
1453 + ReactNoop.render(
1454 + <Placeholder fallback={<Text text="Loading..." />}>
1455 + <LazyText text="Hi" />
1456 + </Placeholder>,
1457 + );
1458 + expect(ReactNoop.flush()).toEqual(['Loading...']);
1459 + expect(ReactNoop.getChildren()).toEqual([]);
1460 +
1461 + await LazyText;
1462 +
1463 + expect(ReactNoop.flush()).toEqual(['Hi']);
1464 + expect(ReactNoop.getChildren()).toEqual([span('Hi')]);
1465 +
1466 + // Should not suspend on update
1467 + ReactNoop.render(
1468 + <Placeholder fallback={<Text text="Loading..." />}>
1469 + <LazyText text="Hi again" />
1470 + </Placeholder>,
1471 + );
1472 + expect(ReactNoop.flush()).toEqual(['Hi again']);
1473 + expect(ReactNoop.getChildren()).toEqual([span('Hi again')]);
1474 + });
1475 +
1476 + it('resolves defaultProps, on mount and update', async () => {
1477 + function T(props) {
1478 + return <Text {...props} />;
1479 + }
1480 + T.defaultProps = {text: 'Hi'};
1481 + const LazyText = Promise.resolve(T);
1482 +
1483 + ReactNoop.render(
1484 + <Placeholder fallback={<Text text="Loading..." />}>
1485 + <LazyText />
1486 + </Placeholder>,
1487 + );
1488 + expect(ReactNoop.flush()).toEqual(['Loading...']);
1489 + expect(ReactNoop.getChildren()).toEqual([]);
1490 +
1491 + await LazyText;
1492 +
1493 + expect(ReactNoop.flush()).toEqual(['Hi']);
1494 + expect(ReactNoop.getChildren()).toEqual([span('Hi')]);
1495 +
1496 + T.defaultProps = {text: 'Hi again'};
1497 +
1498 + ReactNoop.render(
1499 + <Placeholder fallback={<Text text="Loading..." />}>
1500 + <LazyText text="Hi again" />
1501 + </Placeholder>,
1502 + );
1503 + expect(ReactNoop.flush()).toEqual(['Hi again']);
1504 + expect(ReactNoop.getChildren()).toEqual([span('Hi again')]);
1505 + });
1506 +
1507 + it('resolves defaultProps without breaking memoization', async () => {
1508 + function LazyImpl(props) {
1509 + ReactNoop.yield('Lazy');
1510 + return (
1511 + <Fragment>
1512 + <Text text={props.siblingText} />
1513 + {props.children}
1514 + </Fragment>
1515 + );
1516 + }
1517 + LazyImpl.defaultProps = {siblingText: 'Sibling'};
1518 + const Lazy = Promise.resolve(LazyImpl);
1519 +
1520 + class Stateful extends React.Component {
1521 + state = {text: 'A'};
1522 + render() {
1523 + return <Text text={this.state.text} />;
1524 + }
1525 + }
1526 +
1527 + const stateful = React.createRef(null);
1528 + ReactNoop.render(
1529 + <Placeholder fallback={<Text text="Loading..." />}>
1530 + <Lazy>
1531 + <Stateful ref={stateful} />
1532 + </Lazy>
1533 + </Placeholder>,
1534 + );
1535 + expect(ReactNoop.flush()).toEqual(['Loading...']);
1536 + expect(ReactNoop.getChildren()).toEqual([]);
1537 + await Lazy;
1538 + expect(ReactNoop.flush()).toEqual(['Lazy', 'Sibling', 'A']);
1539 + expect(ReactNoop.getChildren()).toEqual([span('Sibling'), span('A')]);
1540 +
1541 + // Lazy should not re-render
1542 + stateful.current.setState({text: 'B'});
1543 + expect(ReactNoop.flush()).toEqual(['B']);
1544 + expect(ReactNoop.getChildren()).toEqual([span('Sibling'), span('B')]);
1545 + });
1546 + });
1547 +
1548 it('does not call lifecycles of a suspended component', async () => {
1549 class TextWithLifecycle extends React.Component {
1550 componentDidMount() {
packages/react-test-renderer/src/ReactTestRenderer.js
+29
@@ -17,7 +17,9 @@ import {findCurrentFiberUsingSlowPath} from 'react-reconciler/reflection';
17 import {
18 Fragment,
19 FunctionalComponent,
20 + FunctionalComponentLazy,
21 ClassComponent,
22 + ClassComponentLazy,
23 HostComponent,
24 HostPortal,
25 HostText,
@@ -27,6 +29,7 @@ import {
29 Mode,
30 ForwardRef,
31 Profiler,
32 + ForwardRefLazy,
33 } from 'shared/ReactTypeOfWork';
34 import invariant from 'shared/invariant';
35 import ReactVersion from 'shared/ReactVersion';
@@ -148,6 +151,17 @@ function toTree(node: ?Fiber) {
151 instance: node.stateNode,
152 rendered: childrenToTree(node.child),
153 };
154 + case ClassComponentLazy: {
155 + const thenable = node.type;
156 + const type = thenable._reactResult;
157 + return {
158 + nodeType: 'component',
159 + type,
160 + props: {...node.memoizedProps},
161 + instance: node.stateNode,
162 + rendered: childrenToTree(node.child),
163 + };
164 + }
165 case FunctionalComponent:
166 return {
167 nodeType: 'component',
@@ -156,6 +170,17 @@ function toTree(node: ?Fiber) {
170 instance: null,
171 rendered: childrenToTree(node.child),
172 };
173 + case FunctionalComponentLazy: {
174 + const thenable = node.type;
175 + const type = thenable._reactResult;
176 + return {
177 + nodeType: 'component',
178 + type: type,
179 + props: {...node.memoizedProps},
180 + instance: node.stateNode,
181 + rendered: childrenToTree(node.child),
182 + };
183 + }
184 case HostComponent: {
185 return {
186 nodeType: 'host',
@@ -173,6 +198,7 @@ function toTree(node: ?Fiber) {
198 case Mode:
199 case Profiler:
200 case ForwardRef:
201 + case ForwardRefLazy:
202 return childrenToTree(node.child);
203 default:
204 invariant(
@@ -198,9 +224,12 @@ function wrapFiber(fiber: Fiber): ReactTestInstance {
224
225 const validWrapperTypes = new Set([
226 FunctionalComponent,
227 + FunctionalComponentLazy,
228 ClassComponent,
229 + ClassComponentLazy,
230 HostComponent,
231 ForwardRef,
232 + ForwardRefLazy,
233 // Normally skipped, but used when there's more than one root child.
234 HostRoot,
235 ]);
packages/react/src/__tests__/ReactStrictMode-test.internal.js
+2 -2
@@ -759,7 +759,7 @@ describe('ReactStrictMode', () => {
759 expect(() => {
760 renderer = ReactTestRenderer.create(<OuterComponent />);
761 }).toWarnDev(
762 - 'Warning: A string ref, "somestring", has been found within a strict mode tree. ' +
762 + 'Warning: A string ref, "somestring", has been found within a strict mode tree. ' +
763 'String refs are a source of potential bugs and should be avoided. ' +
764 'We recommend using createRef() instead.\n\n' +
765 ' in StrictMode (at **)\n' +
@@ -801,7 +801,7 @@ describe('ReactStrictMode', () => {
801 expect(() => {
802 renderer = ReactTestRenderer.create(<OuterComponent />);
803 }).toWarnDev(
804 - 'Warning: A string ref, "somestring", has been found within a strict mode tree. ' +
804 + 'Warning: A string ref, "somestring", has been found within a strict mode tree. ' +
805 'String refs are a source of potential bugs and should be avoided. ' +
806 'We recommend using createRef() instead.\n\n' +
807 ' in InnerComponent (at **)\n' +
packages/shared/ReactLazyComponent.js new
+36
@@ -0,0 +1,36 @@
1 +/**
2 + * Copyright (c) 2013-present, Facebook, Inc.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +export type Thenable<T> = {
11 + then(resolve: (T) => mixed, reject: (mixed) => mixed): mixed,
12 + _reactStatus?: 0 | 1 | 2,
13 + _reactResult: any,
14 +};
15 +
16 +type ResolvedThenable<T> = {
17 + then(resolve: (T) => mixed, reject: (mixed) => mixed): mixed,
18 + _reactStatus?: 1,
19 + _reactResult: T,
20 +};
21 +
22 +export const Pending = 0;
23 +export const Resolved = 1;
24 +export const Rejected = 2;
25 +
26 +export function getResultFromResolvedThenable<T>(
27 + thenable: ResolvedThenable<T>,
28 +): T {
29 + return thenable._reactResult;
30 +}
31 +
32 +export function refineResolvedThenable<T>(
33 + thenable: Thenable<T>,
34 +): ResolvedThenable<T> | null {
35 + return thenable._reactStatus === Resolved ? thenable._reactResult : null;
36 +}
packages/shared/ReactTypeOfWork.js
+13 -13
@@ -28,18 +28,18 @@ export type TypeOfWork =
28
29 export const IndeterminateComponent = 0; // Before we know whether it is functional or class
30 export const FunctionalComponent = 1;
31 -export const ClassComponent = 2;
32 -export const HostRoot = 3; // Root of a host tree. Could be nested inside another node.
33 -export const HostPortal = 4; // A subtree. Could be an entry point to a different renderer.
34 -export const HostComponent = 5;
35 -export const HostText = 6;
36 -export const CallComponent_UNUSED = 7;
37 -export const CallHandlerPhase_UNUSED = 8;
38 -export const ReturnComponent_UNUSED = 9;
39 -export const Fragment = 10;
40 -export const Mode = 11;
41 -export const ContextConsumer = 12;
42 -export const ContextProvider = 13;
43 -export const ForwardRef = 14;
31 +export const FunctionalComponentLazy = 2;
32 +export const ClassComponent = 3;
33 +export const ClassComponentLazy = 4;
34 +export const HostRoot = 5; // Root of a host tree. Could be nested inside another node.
35 +export const HostPortal = 6; // A subtree. Could be an entry point to a different renderer.
36 +export const HostComponent = 7;
37 +export const HostText = 8;
38 +export const Fragment = 9;
39 +export const Mode = 10;
40 +export const ContextConsumer = 11;
41 +export const ContextProvider = 12;
42 +export const ForwardRef = 13;
43 +export const ForwardRefLazy = 14;
44 export const Profiler = 15;
45 export const PlaceholderComponent = 16;
packages/shared/getComponentName.js
+14
@@ -7,6 +7,8 @@
7 * @flow
8 */
9
10 +import type {Thenable} from 'shared/ReactLazyComponent';
11 +
12 import warningWithoutStack from 'shared/warningWithoutStack';
13 import {
14 REACT_ASYNC_MODE_TYPE,
@@ -19,6 +21,10 @@ import {
21 REACT_STRICT_MODE_TYPE,
22 REACT_PLACEHOLDER_TYPE,
23 } from 'shared/ReactSymbols';
24 +import {
25 + getResultFromResolvedThenable,
26 + refineResolvedThenable,
27 +} from 'shared/ReactLazyComponent';
28
29 function getComponentName(type: mixed): string | null {
30 if (type == null) {
@@ -67,6 +73,14 @@ function getComponentName(type: mixed): string | null {
73 ? `ForwardRef(${functionName})`
74 : 'ForwardRef';
75 }
76 + if (typeof type.then === 'function') {
77 + const thenable: Thenable<mixed> = (type: any);
78 + const resolvedThenable = refineResolvedThenable(thenable);
79 + if (resolvedThenable) {
80 + const Component = getResultFromResolvedThenable(resolvedThenable);
81 + return getComponentName(Component);
82 + }
83 + }
84 }
85 return null;
86 }
packages/shared/isValidElementType.js
+2 -1
@@ -30,7 +30,8 @@ export default function isValidElementType(type: mixed) {
30 type === REACT_PLACEHOLDER_TYPE ||
31 (typeof type === 'object' &&
32 type !== null &&
33 - (type.$$typeof === REACT_PROVIDER_TYPE ||
33 + (typeof type.then === 'function' ||
34 + type.$$typeof === REACT_PROVIDER_TYPE ||
35 type.$$typeof === REACT_CONTEXT_TYPE ||
36 type.$$typeof === REACT_FORWARD_REF_TYPE))
37 );