@samitouri / QOS-React-2 / commits / ce4054ebdd

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