@samitouri / QOS-React / commits / 7b971c0a55

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 });