@@ -10,6 +10,11 @@ const TRACING_GETTER = ({
1010 id : 'Tracing' ,
1111} as any ) as IntegrationClass < Integration > ;
1212
13+ // https://stackoverflow.com/questions/52702466/detect-react-reactdom-development-production-build
14+ function isReactInDevMode ( ) : boolean {
15+ return '_self' in React . createElement ( 'div' ) ;
16+ }
17+
1318/**
1419 *
1520 * Based on implementation from Preact:
@@ -39,25 +44,69 @@ function afterNextFrame(callback: Function): void {
3944 timeout = window . setTimeout ( done , 100 ) ;
4045}
4146
47+ let profilerCount = 0 ;
48+
49+ const profiledComponents : {
50+ [ key : string ] : number ;
51+ } = { } ;
52+
4253/**
4354 * getInitActivity pushes activity based on React component mount
4455 * @param name displayName of component that started activity
4556 */
4657const getInitActivity = ( name : string ) : number | null => {
4758 const tracingIntegration = getCurrentHub ( ) . getIntegration ( TRACING_GETTER ) ;
4859
49- if ( tracingIntegration ! == null ) {
50- // tslint:disable-next-line:no-unsafe-any
51- return ( tracingIntegration as any ) . constructor . pushActivity ( name , {
52- description : `< ${ name } >` ,
53- op : 'react' ,
54- } ) ;
60+ if ( tracingIntegration = == null ) {
61+ logger . warn (
62+ `Unable to profile component ${ name } due to invalid Tracing Integration. Please make sure to setup the Tracing integration.` ,
63+ ) ;
64+
65+ return null ;
5566 }
5667
57- logger . warn (
58- `Unable to profile component ${ name } due to invalid Tracing Integration. Please make sure to setup the Tracing integration.` ,
59- ) ;
60- return null ;
68+ // tslint:disable-next-line:no-unsafe-any
69+ const activity = ( tracingIntegration as any ) . constructor . pushActivity ( name , {
70+ description : `<${ name } >` ,
71+ op : 'react' ,
72+ } ) as number ;
73+
74+ /**
75+ * If an activity was already generated, this the component is in React.StrictMode.
76+ * React.StrictMode will call constructors and setState hooks twice, effectively
77+ * creating redudant spans for every render (ex. two <App /> spans, two <Link /> spans)
78+ *
79+ * React.StrictMode only has this behaviour in Development Mode
80+ * See: https://reactjs.org/docs/strict-mode.html
81+ *
82+ * To account for this, we track all profiled components, and cancel activities that
83+ * we recognize to be coming from redundant push activity calls. It is important to note
84+ * that it is the first call to push activity that is invalid, as that is the one caused
85+ * by React.StrictMode.
86+ *
87+ */
88+ if ( isReactInDevMode ( ) ) {
89+ // We can make the guarantee here that old activity comes right before the current
90+ // activity, hence having a profilerCount one less than the existing count.
91+ const oldActivity = profiledComponents [ String ( `${ name } ${ profilerCount - 1 } ` ) ] ;
92+
93+ if ( oldActivity ) {
94+ // if we cancel an old activity, we can be sure that the existing activity will
95+ // never be a redundant call caused by React.StrictMode as the old activity was
96+ // that redudant call.
97+
98+ // tslint:disable-next-line:no-unsafe-any
99+ ( tracingIntegration as any ) . constructor . cancelActivity ( oldActivity ) ;
100+ } else {
101+ // If an oldActivity didn't exist, we can store this activity to check later.
102+ // We have to do this inside an else block because of the case of the edge case
103+ // where two components may share a single components name.
104+ profiledComponents [ String ( `${ name } ${ profilerCount } ` ) ] = activity ;
105+ }
106+ }
107+
108+ profilerCount += 1 ;
109+ return activity ;
61110} ;
62111
63112export type ProfilerProps = {
0 commit comments