Problem
After merging this PR #33, I observed that it is possible to happen the memory leak.
The repro on this issue. You can check the memory profiling.
https://github.com/ojj1123/overlay-kit-memory-leak
The points of the repro is below:
- SPA (using
react-router-dom
)
- I didn't call
unmount
callback and didn't set the custom overlayId
.
Every calling overlay.open()
w/o the custom overlayId
, the new random id is generated by randomId()
and then the overlayItem
with that id
is stored to overlayData
. Even if unmounting OverlayProvider
, the overlayItem
s are not GC'd.
I think that the problem is not to call unmount
. In other word, the users of overlay-kit
need to use unmount
callback for preventing memory leak. And as SPA, there are no refresh or reload so the overlay state is not refreshed.
When close is executed, the overlay changes isOpen to false, making it disappear from the screen, but it remains in memory and the DOM. Additionally, I assumed that executing close without unmount means the user is aware of this and intentionally keeps the overlay in memory. As a result, close does not modify current.
#44 (comment)
You said the users intent to remain the overlays in memory, but without the custom id, the same overlay is never re-used since every calling overlay.open
, the new overlay is generated. So I'm wondering what's the advantage of storing the overlay states.
Workarounds
I came up with some workaround, but I didn't try that yet.
Clean-up
As useOverlay of slash, it has clean-up function to unmount overlay. Like this, overlay-kit
need to clean up the overlay state.
WeakMap
If you want to cache the state, we can use WeakMap
. So you change overlayData
from object to WeakMap
setting the keys to overlayItem
:
export type OverlayData = {
current: OverlayId | null;
overlayOrderList: OverlayId[];
overlayData: WeakMap<OverlayItem, any>;
};
If no refer to OverlayItem
, that overlayItem
will be GC'd. But as the situation below, maybe that are not GC'd:
- In
OverlayProvider
, all of overlayItem
are already referred.
- If the users use
overlayData
using useOverlayData
, it can't be GC'd.
Revert #33
As #33, you changed the management of the overlay state from React's internal state(useReducer
) to external state(useSyncExternalStore
). So even if OverlayProvider
is unmounted, the overlay state is not cleared. Therefore, if you manage it as React's internal state, you will be able to initialize the state when OverlayProvider
is unmounted.
Docs
You can just let the users know you need to call unmount
in the Document. But if the user don't take care of it, the memory leak occurs. So I think that it is good for this library to provider unmounting overlay properly.
Other bug
Making the repro, I found other bug:
If unmounting OverlayProvider
and mounting OverlayProvider
again, all of isOpen
set to true
. This is because when mounting OverlayProvider
, effect function is called in ContentOverlayController
. You can see this bug in my repro.
|
useEffect(() => { |
|
onMountedRef.current(); |
|
}, []); |