diff --git a/packages/react-reconciler/src/ReactDebugFiberPerf.js b/packages/react-reconciler/src/ReactDebugFiberPerf.js
index 99be2fe897..818c821d2f 100644
--- a/packages/react-reconciler/src/ReactDebugFiberPerf.js
+++ b/packages/react-reconciler/src/ReactDebugFiberPerf.js
@@ -20,6 +20,7 @@ import {
ContextProvider,
ContextConsumer,
Mode,
+ SuspenseComponent,
} from 'shared/ReactWorkTags';
type MeasurementPhase =
@@ -315,7 +316,10 @@ export function stopFailedWorkTimer(fiber: Fiber): void {
return;
}
fiber._debugIsCurrentlyTiming = false;
- const warning = 'An error was thrown inside this error boundary';
+ const warning =
+ fiber.tag === SuspenseComponent
+ ? 'Rendering was suspended'
+ : 'An error was thrown inside this error boundary';
endFiberMark(fiber, null, warning);
}
}
diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.js b/packages/react-reconciler/src/ReactFiberBeginWork.js
index c6bcf94324..7a7e91ef3f 100644
--- a/packages/react-reconciler/src/ReactFiberBeginWork.js
+++ b/packages/react-reconciler/src/ReactFiberBeginWork.js
@@ -56,7 +56,7 @@ import ReactStrictModeWarnings from './ReactStrictModeWarnings';
import warning from 'shared/warning';
import warningWithoutStack from 'shared/warningWithoutStack';
import * as ReactCurrentFiber from './ReactCurrentFiber';
-import {cancelWorkTimer} from './ReactDebugFiberPerf';
+import {startWorkTimer, cancelWorkTimer} from './ReactDebugFiberPerf';
import {
mountChildFibers,
@@ -720,11 +720,15 @@ function mountIndeterminateComponent(
Component !== null &&
typeof Component.then === 'function'
) {
+ // We can't start a User Timing measurement with correct label yet.
+ // Cancel and resume right after we know the tag.
+ cancelWorkTimer(workInProgress);
Component = readLazyComponentType(Component);
const resolvedTag = (workInProgress.tag = resolveLazyComponentTag(
workInProgress,
Component,
));
+ startWorkTimer(workInProgress);
const resolvedProps = resolveDefaultProps(Component, props);
let child;
switch (resolvedTag) {
diff --git a/packages/react-reconciler/src/__tests__/ReactIncrementalPerf-test.internal.js b/packages/react-reconciler/src/__tests__/ReactIncrementalPerf-test.internal.js
index 801e64b148..a6bb1a7663 100644
--- a/packages/react-reconciler/src/__tests__/ReactIncrementalPerf-test.internal.js
+++ b/packages/react-reconciler/src/__tests__/ReactIncrementalPerf-test.internal.js
@@ -555,6 +555,58 @@ describe('ReactDebugFiberPerf', () => {
expect(getFlameChart()).toMatchSnapshot();
});
+ it('supports pure', () => {
+ const PureFoo = React.pure(function Foo() {
+ return
;
+ });
+ ReactNoop.render(
+
+
+ ,
+ );
+ ReactNoop.flush();
+ expect(getFlameChart()).toMatchSnapshot();
+ });
+
+ it('supports Suspense and lazy', async () => {
+ function Spinner() {
+ return ;
+ }
+
+ let resolve;
+ const LazyFoo = React.lazy(
+ () =>
+ new Promise(r => {
+ resolve = r;
+ }),
+ );
+
+ ReactNoop.render(
+
+ }>
+
+
+ ,
+ );
+ ReactNoop.flush();
+ expect(getFlameChart()).toMatchSnapshot();
+
+ resolve(function Foo() {
+ return ;
+ });
+ await LazyFoo;
+
+ ReactNoop.render(
+
+
+
+
+ ,
+ );
+ ReactNoop.flush();
+ expect(getFlameChart()).toMatchSnapshot();
+ });
+
it('does not schedule an extra callback if setState is called during a synchronous commit phase', () => {
class Component extends React.Component {
state = {step: 1};
diff --git a/packages/react-reconciler/src/__tests__/__snapshots__/ReactIncrementalPerf-test.internal.js.snap b/packages/react-reconciler/src/__tests__/__snapshots__/ReactIncrementalPerf-test.internal.js.snap
index dcef20d968..24f4454f5e 100644
--- a/packages/react-reconciler/src/__tests__/__snapshots__/ReactIncrementalPerf-test.internal.js.snap
+++ b/packages/react-reconciler/src/__tests__/__snapshots__/ReactIncrementalPerf-test.internal.js.snap
@@ -362,6 +362,40 @@ exports[`ReactDebugFiberPerf skips parents during setState 1`] = `
"
`;
+exports[`ReactDebugFiberPerf supports Suspense and lazy 1`] = `
+"⚛ (Waiting for async callback... will force flush in 5250 ms)
+
+⚛ (React Tree Reconciliation: Completed Root)
+ ⚛ Parent [mount]
+ ⛔ Suspense [mount] Warning: Rendering was suspended
+ ⚛ Suspense [mount]
+ ⚛ Spinner [mount]
+"
+`;
+
+exports[`ReactDebugFiberPerf supports Suspense and lazy 2`] = `
+"⚛ (Waiting for async callback... will force flush in 5250 ms)
+
+⚛ (React Tree Reconciliation: Completed Root)
+ ⚛ Parent [mount]
+ ⛔ Suspense [mount] Warning: Rendering was suspended
+ ⚛ Suspense [mount]
+ ⚛ Spinner [mount]
+
+⚛ (Waiting for async callback... will force flush in 5250 ms)
+
+⚛ (React Tree Reconciliation: Completed Root)
+ ⚛ Parent [mount]
+ ⚛ Suspense [mount]
+ ⚛ Foo [mount]
+
+⚛ (Committing Changes)
+ ⚛ (Committing Snapshot Effects: 0 Total)
+ ⚛ (Committing Host Effects: 1 Total)
+ ⚛ (Calling Lifecycle Methods: 0 Total)
+"
+`;
+
exports[`ReactDebugFiberPerf supports portals 1`] = `
"⚛ (Waiting for async callback... will force flush in 5250 ms)
@@ -376,6 +410,20 @@ exports[`ReactDebugFiberPerf supports portals 1`] = `
"
`;
+exports[`ReactDebugFiberPerf supports pure 1`] = `
+"⚛ (Waiting for async callback... will force flush in 5250 ms)
+
+⚛ (React Tree Reconciliation: Completed Root)
+ ⚛ Parent [mount]
+ ⚛ Pure(Foo) [mount]
+
+⚛ (Committing Changes)
+ ⚛ (Committing Snapshot Effects: 0 Total)
+ ⚛ (Committing Host Effects: 1 Total)
+ ⚛ (Calling Lifecycle Methods: 0 Total)
+"
+`;
+
exports[`ReactDebugFiberPerf warns if an in-progress update is interrupted 1`] = `
"⚛ (Waiting for async callback... will force flush in 5250 ms)
diff --git a/packages/shared/getComponentName.js b/packages/shared/getComponentName.js
index 1b91ee04dd..4e89efef4a 100644
--- a/packages/shared/getComponentName.js
+++ b/packages/shared/getComponentName.js
@@ -16,6 +16,7 @@ import {
REACT_FORWARD_REF_TYPE,
REACT_FRAGMENT_TYPE,
REACT_PORTAL_TYPE,
+ REACT_PURE_TYPE,
REACT_PROFILER_TYPE,
REACT_PROVIDER_TYPE,
REACT_STRICT_MODE_TYPE,
@@ -23,6 +24,18 @@ import {
} from 'shared/ReactSymbols';
import {refineResolvedThenable} from 'shared/ReactLazyComponent';
+function getWrappedName(
+ outerType: mixed,
+ innerType: any,
+ wrapperName: string,
+): string {
+ const functionName = innerType.displayName || innerType.name || '';
+ return (
+ (outerType: any).displayName ||
+ (functionName !== '' ? `${wrapperName}(${functionName})` : wrapperName)
+ );
+}
+
function getComponentName(type: mixed): string | null {
if (type == null) {
// Host root, text node or just invalid type.
@@ -64,12 +77,9 @@ function getComponentName(type: mixed): string | null {
case REACT_PROVIDER_TYPE:
return 'Context.Provider';
case REACT_FORWARD_REF_TYPE:
- const renderFn = (type.render: any);
- const functionName = renderFn.displayName || renderFn.name || '';
- return (
- (type: any).displayName ||
- (functionName !== '' ? `ForwardRef(${functionName})` : 'ForwardRef')
- );
+ 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);