@samitouri / QOS-React / commits / e71a6393e6

[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;