You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
changed the title [-]React DevTools 'primaryFibers ' can keep unmounted DOM/Fibers alive[/-][+]React DevTools "primaryFibers" Set might retain references to unmounted DOM elements (and their Fibers)[/+]on Dec 16, 2019
changed the title [-]React DevTools "primaryFibers" Set might retain references to unmounted DOM elements (and their Fibers)[/-][+]React DevTools might retain references to unmounted DOM elements (and their Fibers)[/+]on Dec 16, 2019
I'm not necessarily convinced that the above screenshot is showing what this issue suggests. I believe it's possible that this indicates that component rendered null where it previously rendered a DOM element, and the "alternate" Fiber still has a reference to the (now unmounted) DOM element. In this case both DevTools and React itself would have references to both Fibers as well as the unmounted DOM element.
We do clear pointers for both "current" and "alternate" in detachFiberbut this does not include the stateNode property:
I'm not sure I would call this a "leak", since the retained DOM elements would go away once the leaf Fiber itself went away, but it could at least exhibit the behavior your Memory profile shows in the picture above. If one was to argue this was a leak, then I believe it would be considered a React leak rather than a DevTools leak.
Edit I think we also leak Fibers in this way, since the stateNode may have a reference to its previous children - which themselves have references to their Fibers via the "__reactInternalInstance" field.
The thing that caused the above temporarily "leak" is the alternate pointer- which will be cleared during the next render. That being said, the reason the temporary "leak" retains so much (the whole subtree) is because we weren't nulling out the stateNode field. PR #17666 changes this behavior so that we now will null that field. This should make our memory snapshot comparisons less noisy in the future.
In other words, hopefully this will show that DevTools isn't actually leaking DOM nodes (and Fibers) but if it is at least we should be able to identify them less ambiguously now.
It does look like DevTools isn't recording unmounts for list-item Fibers in this repro, which causes it to retain them after they've been unmounted.
I thinks a React bug though. (React is supposed to explicitly notify DevTools of unmounts and it doesn't seem to be.) I wonder what it is about react-virtual that triggers this behavior that doesn't repro with a library like react-window. This might have been a bad lead here. I think the reason I'm initially not seeing calls to commit unmount are because react-virtual grows a pool of items initially. Still digging. It's definitely the list items that are leaking though. Just not sure why yet.
Update I'm not convinced this is the entire problem, but it does seem like DevTools isn't properly cleaning out all of the alternate entries in the fiberToId Map. I think this is because the alternate is being cleared out synchronously by React when a Fiber gets unmounted (as part of our memory cleanup flag) but DevTools is asynchronously processing the untracking in order to support fast Refresh:
Also as side note, i would expect to see similar behaviour in react-window, as in windowing nature there is lot of mounting/unmounting, only difference to react-virtual is, that it's hook based and react-window on classes.
Activity
[-]React DevTools 'primaryFibers ' can keep unmounted DOM/Fibers alive[/-][+]React DevTools "primaryFibers" Set might retain references to unmounted DOM elements (and their Fibers)[/+][-]React DevTools "primaryFibers" Set might retain references to unmounted DOM elements (and their Fibers)[/-][+]React DevTools might retain references to unmounted DOM elements (and their Fibers)[/+]bvaughn commentedon Dec 16, 2019
I'm not necessarily convinced that the above screenshot is showing what this issue suggests. I believe it's possible that this indicates that component rendered
null
where it previously rendered a DOM element, and the "alternate" Fiber still has a reference to the (now unmounted) DOM element. In this case both DevTools and React itself would have references to both Fibers as well as the unmounted DOM element.We do clear pointers for both "current" and "alternate" in
detachFiber
but this does not include thestateNode
property:react/packages/react-reconciler/src/ReactFiberCommitWork.js
Lines 889 to 909 in 0cf22a5
The retain behavior I describe above can be verified with the following example app code:
I'm not sure I would call this a "leak", since the retained DOM elements would go away once the leaf Fiber itself went away, but it could at least exhibit the behavior your Memory profile shows in the picture above. If one was to argue this was a leak, then I believe it would be considered a React leak rather than a DevTools leak.
Edit I think we also leak Fibers in this way, since the
stateNode
may have a reference to its previous children - which themselves have references to their Fibers via the "__reactInternalInstance" field.bvaughn commentedon Dec 20, 2019
Quick update about this issue and PR #17666
The thing that caused the above temporarily "leak" is the alternate pointer- which will be cleared during the next render. That being said, the reason the temporary "leak" retains so much (the whole subtree) is because we weren't nulling out the
stateNode
field. PR #17666 changes this behavior so that we now will null that field. This should make our memory snapshot comparisons less noisy in the future.In other words, hopefully this will show that DevTools isn't actually leaking DOM nodes (and Fibers) but if it is at least we should be able to identify them less ambiguously now.
piecyk commentedon Sep 17, 2021
Looks like there is a memory leak in DevTools, was noticed in TanStack/virtual#196
Created small demo, it can be observer while scrolling a list, memory increase because of detached fiber and dom elements
https://github.com/piecyk/react-devtools-memory-leak-17092021
It happens also in production build, noticed that elements aren't removed from
fiberToIDMap
after they are unmounted, something fishy there 🤔bvaughn commentedon Sep 17, 2021
Thanks for sharing a repro, @piecyk. I'll take a look.
bvaughn commentedon Sep 17, 2021
It does look like DevTools isn't recording unmounts for list-item Fibers in this repro, which causes it to retain them after they've been unmounted.
I thinks a React bug though. (React is supposed to explicitly notify DevTools of unmounts and it doesn't seem to be.) I wonder what it is aboutThis might have been a bad lead here. I think the reason I'm initially not seeing calls to commit unmount are becausereact-virtual
that triggers this behavior that doesn't repro with a library likereact-window
.react-virtual
grows a pool of items initially. Still digging. It's definitely the list items that are leaking though. Just not sure why yet.Update I'm not convinced this is the entire problem, but it does seem like DevTools isn't properly cleaning out all of the alternate entries in the
fiberToId
Map. I think this is because thealternate
is being cleared out synchronously by React when a Fiber gets unmounted (as part of our memory cleanup flag) but DevTools is asynchronously processing the untracking in order to support fast Refresh:react/packages/react-devtools-shared/src/backend/renderer.js
Lines 1126 to 1139 in bc9bb87
The previous update is the problem. I think I have a fix. Will be pushing shortly.
piecyk commentedon Sep 17, 2021
Also as side note, i would expect to see similar behaviour in react-window, as in windowing nature there is lot of mounting/unmounting, only difference to react-virtual is, that it's hook based and react-window on classes.
bvaughn commentedon Sep 17, 2021
This fix will go out with the next DevTools release.
piecyk commentedon Sep 17, 2021
Thanks @bvaughn 👍