[DevTools] Fix memory leak when unmounting hoistables (#35741)
Sebastian "Sebbie" Silbermann committed
Feb 10, 2026 at 13:09 UTC
f23aa1d9f5034e7cfbd3e6015ce102bb72a005a0
2 files changed
+56
-1
packages/react-devtools-shared/src/__tests__/store-test.js
+55
@@ -3616,4 +3616,59 @@ describe('Store', () => {
3616
<div>
3617
`);
3618
});
3619
+
3620
+ // @reactVersion >= 19
3621
+ it('cleans up host hoistables', async () => {
3622
+ function Left() {
3623
+ return (
3624
+ <style href="test.css" precedence="medium">
3625
+ {'* {color:black}'}
3626
+ </style>
3627
+ );
3628
+ }
3629
+
3630
+ function Right() {
3631
+ return (
3632
+ <style href="test.css" precedence="medium">
3633
+ {'* {color:black}'}
3634
+ </style>
3635
+ );
3636
+ }
3637
+
3638
+ await actAsync(() => {
3639
+ render(
3640
+ <>
3641
+ <Left />
3642
+ <Right />
3643
+ </>,
3644
+ );
3645
+ });
3646
+
3647
+ // Ensure we're still testing deduplicated hoistables.
3648
+ expect(document.head.querySelectorAll('style')).toHaveLength(1);
3649
+ expect(store).toMatchInlineSnapshot(`
3650
+ [root]
3651
+ <Left>
3652
+ <Right>
3653
+ `);
3654
+ let style = document.head.querySelector('style');
3655
+ let styleID = agent.getIDForHostInstance(style).id;
3656
+ expect(store.containsElement(styleID)).toBe(true);
3657
+
3658
+ await actAsync(() => {
3659
+ render(
3660
+ <>
3661
+ <Right />
3662
+ </>,
3663
+ );
3664
+ });
3665
+
3666
+ expect(store).toMatchInlineSnapshot(`
3667
+ [root]
3668
+ <Right>
3669
+ `);
3670
+ style = document.head.querySelector('style');
3671
+ styleID = agent.getIDForHostInstance(style).id;
3672
+ expect(store.containsElement(styleID)).toBe(true);
3673
+ });
3674
});
packages/react-devtools-shared/src/backend/fiber/renderer.js
+1
-1
@@ -991,8 +991,8 @@ function releaseHostResource(
991
// eslint-disable-next-line no-for-of-loops/no-for-of-loops
992
for (const firstInstance of resourceInstances) {
993
publicInstanceToDevToolsInstanceMap.set(
994
+ publicInstance,
995
firstInstance,
995
- nearestInstance,
996
);
997
break;
998
}