-
-
Notifications
You must be signed in to change notification settings - Fork 533
[BUG] Memory leak via anchorsBySelect #1102
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Hi @ivan-palatov. Thanks for reporting it. We already have a fix for this in #1091, but we're probably not merging the new prop the PR introduces, it was something we we're just testing out. We'll clean up the PR and merge the fix for this soon. Thanks again. |
@gabrieljablonski It still leaves a smaller leak, I've made a review comment showing it in the pr. |
Also, I believe there might be a possible leak here. The I might be wrong on this one, but I think the code should be changed like this: useEffect(() => {
// rest of useEffect ...
// Notice this line here
const tooltip = tooltipRef.current;
return () => {
if (closeOnScroll) {
window.removeEventListener("scroll", handleScrollResize);
anchorScrollParent?.removeEventListener("scroll", handleScrollResize);
tooltipScrollParent?.removeEventListener("scroll", handleScrollResize);
}
if (closeOnResize) {
window.removeEventListener("resize", handleScrollResize);
} else {
updateTooltipCleanup?.();
}
if (shouldOpenOnClick) {
window.removeEventListener("click", handleClickOutsideAnchors);
}
if (closeOnEsc) {
window.removeEventListener("keydown", handleEsc);
}
if (clickable && !shouldOpenOnClick) {
tooltip?.removeEventListener("mouseenter", handleMouseEnterTooltip);
tooltip?.removeEventListener("mouseleave", handleMouseLeaveTooltip);
}
for (const { event, listener } of enabledEvents) {
for (const ref of elementRefs) {
ref.removeEventListener(event, listener);
}
}
enabledEvents.forEach(({ event, listener }) => {
elementRefs.forEach((ref) => {
ref.current?.removeEventListener(event, listener)
})
})
};
/**
* rendered is also a dependency to ensure anchor observers are re-registered
* since `tooltipRef` becomes stale after removing/adding the tooltip to the DOM
*/
}, [
activeAnchor,
updateTooltipPosition,
anchorRefs,
rendered,
anchorsBySelect,
closeOnEsc,
events,
]); |
Good catch, will update it.
I'm like 99% sure this wouldn't leak the listeners, since the ref is being set through the React Too lazy to test this specifically (it would probably take a bit of work to isolate it since the leak would be veeery minor). I'll probably just do the I'll have the time to properly review the PR and test it out only next week. If this is affecting your app badly enough, let us know so we can release a usable beta version. That way you can keep working until the official release with the fix. |
We have temporarily removed About the ref thing I think the same, but my eslint was complaining about it, so I thought it was worth pointing it out. It wouldn't hurt either way. |
Fixed on |
Bug description
The
Tooltip
component accumulates anchors to elements inanchorsBySelect
state without ever releasing old, already unused elements. It causes applications to have massive memory leaks andDetached
elements in memory, which eventualy leads to the death of the app.Version of Package
v5.21.5
To Reproduce
Sorry, I dont have time to setup a proper bench for it, but this should suffice.
Expected behavior
Detached nodes should not be increasing.
anchorsBySelect
should not be updated with theold + new
, old values must be filtered first.Screenshots

The screenshot from our app in production using
react-tooltip
. If we remore<Tooltip />
from the root of the app, those detached nodes are gone.Additional context
The problem happens because you are not filtering out the old
anchorsBySelect
state and just add the new elements on top of it hereSo those elements are never actualy released from memory and always hang there, causing massive memory leaks.
To fix this either make use of the
removedNodes
fromMutationObserver
like soOr just do a
document.querySelectorAll(selector)
on mutation, which, tbh, could be faster than those iterations, transforms and array merges.The text was updated successfully, but these errors were encountered: