Current behavior for excluding Component render with unchanged props from Components track (#34822)
If we rerender with the same props, the render time will not be accounted for in the Components track. The attached test reproduces the behavior observed in https://codesandbox.io/p/sandbox/patient-fast-j94f2g: <img width="1118" height="354" alt="CleanShot 2025-10-13 at 00 13 41@2x" src="https://github.com/user-attachments/assets/4be10ee9-d529-4d98-9035-4f26f9587f52" />
Sebastian "Sebbie" Silbermann committed
Oct 13, 2025 at 23:14 UTC
7b971c0a5536f7cd4573ff574921463acf947b14
1 file changed
+125
-10
packages/react-reconciler/src/__tests__/ReactPerformanceTrack-test.js
+125
-10
@@ -15,9 +15,20 @@ let act;
15
let useEffect;
16
17
describe('ReactPerformanceTracks', () => {
18
+ const performanceMeasureCalls = [];
19
+
20
beforeEach(() => {
21
+ performanceMeasureCalls.length = 0;
22
Object.defineProperty(performance, 'measure', {
20
- value: jest.fn(),
23
+ value: jest.fn((measureName, reusableOptions) => {
24
+ performanceMeasureCalls.push([
25
+ measureName,
26
+ {
27
+ // React will mutate the options it passes to performance.measure.
28
+ ...reusableOptions,
29
+ },
30
+ ]);
31
+ }),
32
configurable: true,
33
});
34
console.timeStamp = () => {};
@@ -32,6 +43,19 @@ describe('ReactPerformanceTracks', () => {
43
useEffect = React.useEffect;
44
});
45
46
+ function getConsoleTimestampEntries() {
47
+ try {
48
+ return console.timeStamp.mock.calls.filter(call => {
49
+ const [, startTime, endTime] = call;
50
+
51
+ const isRegisterTrackCall = startTime !== 0.003 && endTime !== 0.003;
52
+ return isRegisterTrackCall;
53
+ });
54
+ } finally {
55
+ console.timeStamp.mockClear();
56
+ }
57
+ }
58
+
59
// @gate __DEV__ && enableComponentPerformanceTrack
60
it('shows a hint if an update is triggered by a deeply equal object', async () => {
61
const App = function App({items}) {
@@ -45,7 +69,7 @@ describe('ReactPerformanceTracks', () => {
69
ReactNoop.render(<App items={items} />);
70
});
71
48
- expect(performance.measure.mock.calls).toEqual([
72
+ expect(performanceMeasureCalls).toEqual([
73
[
74
'Mount',
75
{
@@ -62,14 +86,14 @@ describe('ReactPerformanceTracks', () => {
86
},
87
],
88
]);
65
- performance.measure.mockClear();
89
+ performanceMeasureCalls.length = 0;
90
91
Scheduler.unstable_advanceTime(10);
92
await act(() => {
93
ReactNoop.render(<App items={items.concat('4')} />);
94
});
95
72
- expect(performance.measure.mock.calls).toEqual([
96
+ expect(performanceMeasureCalls).toEqual([
97
[
98
'App',
99
{
@@ -105,7 +129,7 @@ describe('ReactPerformanceTracks', () => {
129
ReactNoop.render(<App items={items} />);
130
});
131
108
- expect(performance.measure.mock.calls).toEqual([
132
+ expect(performanceMeasureCalls).toEqual([
133
[
134
'Mount',
135
{
@@ -122,14 +146,14 @@ describe('ReactPerformanceTracks', () => {
146
},
147
],
148
]);
125
- performance.measure.mockClear();
149
+ performanceMeasureCalls.length = 0;
150
151
Scheduler.unstable_advanceTime(10);
152
await act(() => {
153
ReactNoop.render(<App items={items.concat('-1')} />);
154
});
155
132
- expect(performance.measure.mock.calls).toEqual([
156
+ expect(performanceMeasureCalls).toEqual([
157
[
158
'App',
159
{
@@ -171,7 +195,7 @@ describe('ReactPerformanceTracks', () => {
195
ReactNoop.render(<App data={{buffer: null}} />);
196
});
197
174
- expect(performance.measure.mock.calls).toEqual([
198
+ expect(performanceMeasureCalls).toEqual([
199
[
200
'Mount',
201
{
@@ -188,7 +212,7 @@ describe('ReactPerformanceTracks', () => {
212
},
213
],
214
]);
191
- performance.measure.mockClear();
215
+ performanceMeasureCalls.length = 0;
216
217
Scheduler.unstable_advanceTime(10);
218
@@ -197,7 +221,7 @@ describe('ReactPerformanceTracks', () => {
221
ReactNoop.render(<App data={{buffer: bigData}} />);
222
});
223
200
- expect(performance.measure.mock.calls).toEqual([
224
+ expect(performanceMeasureCalls).toEqual([
225
[
226
'App',
227
{
@@ -324,4 +348,95 @@ describe('ReactPerformanceTracks', () => {
348
],
349
]);
350
});
351
+
352
+ // @gate __DEV__ && enableComponentPerformanceTrack
353
+ it('includes spans for Components with no prop changes', async () => {
354
+ function Left({value}) {
355
+ Scheduler.unstable_advanceTime(5000);
356
+ }
357
+ function Right() {
358
+ Scheduler.unstable_advanceTime(10000);
359
+ }
360
+
361
+ await act(() => {
362
+ ReactNoop.render(
363
+ <>
364
+ <Left value={1} />
365
+ <Right />
366
+ </>,
367
+ );
368
+ });
369
+
370
+ expect(performanceMeasureCalls).toEqual([
371
+ [
372
+ 'Mount',
373
+ {
374
+ detail: {
375
+ devtools: {
376
+ color: 'warning',
377
+ properties: null,
378
+ tooltipText: 'Mount',
379
+ track: 'Components ⚛',
380
+ },
381
+ },
382
+ end: 5000,
383
+ start: 0,
384
+ },
385
+ ],
386
+ [
387
+ 'Mount',
388
+ {
389
+ detail: {
390
+ devtools: {
391
+ color: 'warning',
392
+ properties: null,
393
+ tooltipText: 'Mount',
394
+ track: 'Components ⚛',
395
+ },
396
+ },
397
+ end: 15000,
398
+ start: 5000,
399
+ },
400
+ ],
401
+ ]);
402
+ performanceMeasureCalls.length = 0;
403
+ getConsoleTimestampEntries();
404
+
405
+ Scheduler.unstable_advanceTime(1000);
406
+
407
+ await act(() => {
408
+ ReactNoop.render(
409
+ <>
410
+ <Left value={2} />
411
+ <Right />
412
+ </>,
413
+ );
414
+ });
415
+
416
+ expect(performanceMeasureCalls).toEqual([
417
+ [
418
+ 'Left',
419
+ {
420
+ detail: {
421
+ devtools: {
422
+ color: 'error',
423
+ properties: [
424
+ ['Changed Props', ''],
425
+ ['– value', '1'],
426
+ ['+ value', '2'],
427
+ ],
428
+ tooltipText: 'Left',
429
+ track: 'Components ⚛',
430
+ },
431
+ },
432
+ end: 21000,
433
+ start: 16000,
434
+ },
435
+ ],
436
+ ]);
437
+ expect(getConsoleTimestampEntries()).toEqual([
438
+ ['Render', 16000, 31000, 'Blocking', 'Scheduler ⚛', 'primary-dark'],
439
+ ]);
440
+ performanceMeasureCalls.length = 0;
441
+ });
442
});