[DevTools] Measure when reconnecting Suspense (#35098)
Sebastian "Sebbie" Silbermann committed
Nov 10, 2025 at 20:55 UTC
ce4054ebdd550237cf20a509f8503d4624cbaffa
2 files changed
+107
packages/react-devtools-shared/src/__tests__/store-test.js
+96
@@ -3298,4 +3298,100 @@ describe('Store', () => {
3298
<Suspense name="Inner" rects={[{x:1,y:2,width:6,height:1}]}>
3299
`);
3300
});
3301
+
3302
+ // @reactVersion >= 19.0
3303
+ it('measures rects when reconnecting', async () => {
3304
+ function Component({children, promise}) {
3305
+ let content = '';
3306
+ if (promise) {
3307
+ const value = readValue(promise);
3308
+ if (typeof value === 'string') {
3309
+ content += value;
3310
+ }
3311
+ }
3312
+ return (
3313
+ <div>
3314
+ {content}
3315
+ {children}
3316
+ </div>
3317
+ );
3318
+ }
3319
+
3320
+ function App({outer, inner}) {
3321
+ return (
3322
+ <React.Suspense
3323
+ name="outer"
3324
+ fallback={<Component key="outer-fallback">loading outer</Component>}>
3325
+ <Component key="outer-content" promise={outer}>
3326
+ outer content
3327
+ </Component>
3328
+ <React.Suspense
3329
+ name="inner"
3330
+ fallback={
3331
+ <Component key="inner-fallback">loading inner</Component>
3332
+ }>
3333
+ <Component key="inner-content" promise={inner}>
3334
+ inner content
3335
+ </Component>
3336
+ </React.Suspense>
3337
+ </React.Suspense>
3338
+ );
3339
+ }
3340
+
3341
+ await actAsync(() => {
3342
+ render(<App outer={null} inner={null} />);
3343
+ });
3344
+
3345
+ expect(store).toMatchInlineSnapshot(`
3346
+ [root]
3347
+ ▾ <App>
3348
+ ▾ <Suspense name="outer">
3349
+ <Component key="outer-content">
3350
+ ▾ <Suspense name="inner">
3351
+ <Component key="inner-content">
3352
+ [suspense-root] rects={[{x:1,y:2,width:13,height:1}, {x:1,y:2,width:13,height:1}]}
3353
+ <Suspense name="outer" rects={[{x:1,y:2,width:13,height:1}, {x:1,y:2,width:13,height:1}]}>
3354
+ <Suspense name="inner" rects={[{x:1,y:2,width:13,height:1}]}>
3355
+ `);
3356
+
3357
+ let outerResolve;
3358
+ const outerPromise = new Promise(resolve => {
3359
+ outerResolve = resolve;
3360
+ });
3361
+
3362
+ let innerResolve;
3363
+ const innerPromise = new Promise(resolve => {
3364
+ innerResolve = resolve;
3365
+ });
3366
+ await actAsync(() => {
3367
+ render(<App outer={outerPromise} inner={innerPromise} />);
3368
+ });
3369
+
3370
+ expect(store).toMatchInlineSnapshot(`
3371
+ [root]
3372
+ ▾ <App>
3373
+ ▾ <Suspense name="outer">
3374
+ <Component key="outer-fallback">
3375
+ [suspense-root] rects={[{x:1,y:2,width:13,height:1}, {x:1,y:2,width:13,height:1}, {x:1,y:2,width:13,height:1}]}
3376
+ <Suspense name="outer" rects={[{x:1,y:2,width:13,height:1}, {x:1,y:2,width:13,height:1}]}>
3377
+ <Suspense name="inner" rects={[{x:1,y:2,width:13,height:1}]}>
3378
+ `);
3379
+
3380
+ await actAsync(() => {
3381
+ outerResolve('..');
3382
+ innerResolve('.');
3383
+ });
3384
+
3385
+ expect(store).toMatchInlineSnapshot(`
3386
+ [root]
3387
+ ▾ <App>
3388
+ ▾ <Suspense name="outer">
3389
+ <Component key="outer-content">
3390
+ ▾ <Suspense name="inner">
3391
+ <Component key="inner-content">
3392
+ [suspense-root] rects={[{x:1,y:2,width:15,height:1}, {x:1,y:2,width:14,height:1}]}
3393
+ <Suspense name="outer" rects={[{x:1,y:2,width:15,height:1}, {x:1,y:2,width:14,height:1}]}>
3394
+ <Suspense name="inner" rects={[{x:1,y:2,width:14,height:1}]}>
3395
+ `);
3396
+ });
3397
});
packages/react-devtools-shared/src/backend/fiber/renderer.js
+11
@@ -2586,6 +2586,17 @@ export function attach(
2586
}
2587
}
2588
} else {
2589
+ const suspenseNode = fiberInstance.suspenseNode;
2590
+ if (suspenseNode !== null && fiber.memoizedState === null) {
2591
+ // We're reconnecting an unsuspended Suspense. Measure to see if anything changed.
2592
+ const prevRects = suspenseNode.rects;
2593
+ const nextRects = measureInstance(fiberInstance);
2594
+ if (!areEqualRects(prevRects, nextRects)) {
2595
+ suspenseNode.rects = nextRects;
2596
+ recordSuspenseResize(suspenseNode);
2597
+ }
2598
+ }
2599
+
2600
const {key} = fiber;
2601
const displayName = getDisplayNameForFiber(fiber);
2602
const elementType = getElementTypeForFiber(fiber);