[Perf Tracks] Don't enumerate typed array props in dev (#36913)
udit committed
Jul 2, 2026 at 14:51 UTC
e71a6393e66b0d2add46ba2b2c5db563a0563828
2 files changed
+83
-105
packages/react-reconciler/src/__tests__/ReactPerformanceTrack-test.js
+63
-105
@@ -232,111 +232,69 @@ describe('ReactPerformanceTracks', () => {
232
['Changed Props', ''],
233
[' data', ''],
234
['- buffer', 'null'],
235
- ['+ buffer', 'Uint8Array'],
236
- ['+ 0', '0'],
237
- ['+ 1', '0'],
238
- ['+ 2', '0'],
239
- ['+ 3', '0'],
240
- ['+ 4', '0'],
241
- ['+ 5', '0'],
242
- ['+ 6', '0'],
243
- ['+ 7', '0'],
244
- ['+ 8', '0'],
245
- ['+ 9', '0'],
246
- ['+ 10', '0'],
247
- ['+ 11', '0'],
248
- ['+ 12', '0'],
249
- ['+ 13', '0'],
250
- ['+ 14', '0'],
251
- ['+ 15', '0'],
252
- ['+ 16', '0'],
253
- ['+ 17', '0'],
254
- ['+ 18', '0'],
255
- ['+ 19', '0'],
256
- ['+ 20', '0'],
257
- ['+ 21', '0'],
258
- ['+ 22', '0'],
259
- ['+ 23', '0'],
260
- ['+ 24', '0'],
261
- ['+ 25', '0'],
262
- ['+ 26', '0'],
263
- ['+ 27', '0'],
264
- ['+ 28', '0'],
265
- ['+ 29', '0'],
266
- ['+ 30', '0'],
267
- ['+ 31', '0'],
268
- ['+ 32', '0'],
269
- ['+ 33', '0'],
270
- ['+ 34', '0'],
271
- ['+ 35', '0'],
272
- ['+ 36', '0'],
273
- ['+ 37', '0'],
274
- ['+ 38', '0'],
275
- ['+ 39', '0'],
276
- ['+ 40', '0'],
277
- ['+ 41', '0'],
278
- ['+ 42', '0'],
279
- ['+ 43', '0'],
280
- ['+ 44', '0'],
281
- ['+ 45', '0'],
282
- ['+ 46', '0'],
283
- ['+ 47', '0'],
284
- ['+ 48', '0'],
285
- ['+ 49', '0'],
286
- ['+ 50', '0'],
287
- ['+ 51', '0'],
288
- ['+ 52', '0'],
289
- ['+ 53', '0'],
290
- ['+ 54', '0'],
291
- ['+ 55', '0'],
292
- ['+ 56', '0'],
293
- ['+ 57', '0'],
294
- ['+ 58', '0'],
295
- ['+ 59', '0'],
296
- ['+ 60', '0'],
297
- ['+ 61', '0'],
298
- ['+ 62', '0'],
299
- ['+ 63', '0'],
300
- ['+ 64', '0'],
301
- ['+ 65', '0'],
302
- ['+ 66', '0'],
303
- ['+ 67', '0'],
304
- ['+ 68', '0'],
305
- ['+ 69', '0'],
306
- ['+ 70', '0'],
307
- ['+ 71', '0'],
308
- ['+ 72', '0'],
309
- ['+ 73', '0'],
310
- ['+ 74', '0'],
311
- ['+ 75', '0'],
312
- ['+ 76', '0'],
313
- ['+ 77', '0'],
314
- ['+ 78', '0'],
315
- ['+ 79', '0'],
316
- ['+ 80', '0'],
317
- ['+ 81', '0'],
318
- ['+ 82', '0'],
319
- ['+ 83', '0'],
320
- ['+ 84', '0'],
321
- ['+ 85', '0'],
322
- ['+ 86', '0'],
323
- ['+ 87', '0'],
324
- ['+ 88', '0'],
325
- ['+ 89', '0'],
326
- ['+ 90', '0'],
327
- ['+ 91', '0'],
328
- ['+ 92', '0'],
329
- ['+ 93', '0'],
330
- ['+ 94', '0'],
331
- ['+ 95', '0'],
332
- ['+ 96', '0'],
333
- ['+ 97', '0'],
334
- ['+ 98', '0'],
335
- ['+ 99', '0'],
336
- [
337
- '+ Only 100 properties are shown. React will not log more properties of this object.',
338
- '',
339
- ],
235
+ ['+ buffer', 'Uint8Array(1000)'],
236
+ ],
237
+ tooltipText: 'App',
238
+ track: 'Components ⚛',
239
+ },
240
+ },
241
+ end: 31,
242
+ start: 21,
243
+ },
244
+ ],
245
+ ]);
246
+ });
247
+
248
+ // @gate __DEV__ && enableComponentPerformanceTrack
249
+ it('shows the type and length of typed arrays instead of enumerating them', async () => {
250
+ const App = function App({buffer}) {
251
+ Scheduler.unstable_advanceTime(10);
252
+ React.useEffect(() => {}, [buffer]);
253
+ };
254
+
255
+ Scheduler.unstable_advanceTime(1);
256
+ await act(() => {
257
+ ReactNoop.render(<App buffer={new Uint8Array(0)} />);
258
+ });
259
+
260
+ expect(performanceMeasureCalls).toEqual([
261
+ [
262
+ 'Mount',
263
+ {
264
+ detail: {
265
+ devtools: {
266
+ color: 'warning',
267
+ properties: null,
268
+ tooltipText: 'Mount',
269
+ track: 'Components ⚛',
270
+ },
271
+ },
272
+ end: 11,
273
+ start: 1,
274
+ },
275
+ ],
276
+ ]);
277
+ performanceMeasureCalls.length = 0;
278
+
279
+ Scheduler.unstable_advanceTime(10);
280
+
281
+ // A typed array can hold millions of elements. React must not enumerate
282
+ // them element-by-element, which would freeze rendering (issue #36200).
283
+ await act(() => {
284
+ ReactNoop.render(<App buffer={new Float32Array(1000)} />);
285
+ });
286
+
287
+ expect(performanceMeasureCalls).toEqual([
288
+ [
289
+ 'App',
290
+ {
291
+ detail: {
292
+ devtools: {
293
+ color: 'primary-dark',
294
+ properties: [
295
+ ['Changed Props', ''],
296
+ ['-\xa0buffer', 'Uint8Array(0)'],
297
+ ['+\xa0buffer', 'Float32Array(1000)'],
298
],
299
tooltipText: 'App',
300
track: 'Components ⚛',
packages/shared/ReactPerformanceTrackProperties.js
+20
@@ -61,6 +61,14 @@ export function addObjectToProperties(
61
indent: number,
62
prefix: string,
63
): void {
64
+ if (ArrayBuffer.isView(object)) {
65
+ // Typed arrays (e.g. Uint8Array, Float32Array) can hold millions of
66
+ // elements. Enumerating them with for...in forces the engine to
67
+ // materialize a key for every index, which can freeze the page. Their
68
+ // contents aren't useful to show here so we skip them. DataView has no
69
+ // enumerable properties to show anyway.
70
+ return;
71
+ }
72
let addedProperties = 0;
73
for (const key in object) {
74
if (hasOwnProperty.call(object, key) && key[0] !== '_') {
@@ -165,6 +173,18 @@ export function addValueToProperties(
173
// $FlowFixMe[method-unbinding]
174
const objectToString = Object.prototype.toString.call(value);
175
let objectName = objectToString.slice(8, objectToString.length - 1);
176
+ if (ArrayBuffer.isView(value)) {
177
+ // Typed arrays can hold millions of elements. Showing the type and
178
+ // length is more useful than enumerating every index (which is also
179
+ // prohibitively slow, see addObjectToProperties). DataView is the
180
+ // only view without a length; show just its type.
181
+ const length = (value as any).length;
182
+ desc =
183
+ typeof length === 'number'
184
+ ? objectName + '(' + length + ')'
185
+ : objectName;
186
+ break;
187
+ }
188
if (objectName === 'Array') {
189
const array: Array<any> = value as any;
190
const didTruncate = array.length > OBJECT_WIDTH_LIMIT;