@@ -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,65 @@ 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 redundant 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 if a redundant activity exists, it comes right
90+ // before the current activity, hence having a profilerCount one less than the existing count.
91+ const redundantActivity = profiledComponents [ String ( `${ name } ${ profilerCount - 1 } ` ) ] ;
92+
93+ if ( redundantActivity ) {
94+ // tslint:disable-next-line:no-unsafe-any
95+ ( tracingIntegration as any ) . constructor . cancelActivity ( redundantActivity ) ;
96+ } else {
97+ // If an redundant activity didn't exist, we can store the current activity to
98+ // check later. We have to do this inside an else block because of the case of
99+ // the edge case where two components may share a single components name.
100+ profiledComponents [ String ( `${ name } ${ profilerCount } ` ) ] = activity ;
101+ }
102+ }
103+
104+ profilerCount += 1 ;
105+ return activity ;
61106} ;
62107
63108export type ProfilerProps = {
0 commit comments