Description
Is your feature request related to a problem? Please describe.
The upcoming React Concurrent mode will be capable of working on different branches at the same time and then discard or merge them depending on the situation. React libraries like react-easy-state
need to prepare for renders that will start but will never make it to the DOM.
Currently, the reactions (observe
) created internally by view
are disposed (unobserve
) on the unmount phase (the return function of useEffect
). That will no longer be possible in React Concurrent because some renders will not make it to the mount/unmount phase.
This means we need to find an alternative way to dispose the reactions of the components that are discarded or we will have a memory leak.
Describe alternatives you've considered or seen elsewhere
I have been studying how the people of MobX has solved this.
They added support for this in the v2 of mobx-react-lite
. These are the changes they did to their useObserver
in the v2 version: mobxjs/mobx-react-lite@v1.5.2...v2.0.0#diff-33c284d6bf49fd504d1250e7090d24ab73dae525efcc6376363ec89722a0df32
They added two fixes:
-
Do not trigger rerenders for components that are not mounted.
I have already done a PR to do that in Fix React's StrictMode warning: unmounted component rerender #227, but that doesn't solve the memory leak.
-
Create an ad-hoc garbage collector for the reactions of unmounted components:
A couple of months ago they started using the new FinalizationRegistry API to get a callback when a component is garbage collected by the JS engine and also dispose the corresponding reaction, but they still have the ad-hoc garbage collector as fallback for old browsers.
- Discussion: Explore using `FinalizationRegistry` for reactions disposal in react concurrent mode mobxjs/mobx#2562
- Code: https://github.com/mobxjs/mobx/blob/main/packages/mobx-react-lite/src/utils/createReactionCleanupTrackingUsingFinalizationRegister.ts
Describe the solution you'd like
I think the solution of MobX is unnecessarily complicated. I guess they must need to do so because of the way MobX stores reactions internally, but I don't think using a manual garbage collection is the correct approach for react-easy-state
.
What I would do is to create a new type of weak reactions, that are stored in a WeakMap
inside the observable
(instead of the current Map
) and are garbage collected automatically by the JS engine when the reference to the reaction is lost.
This would require the exposure of a new weakObserve
API from @nx-js/observer-util
that will save those reactions in a WeakMap
here: https://github.com/nx-js/observer-util/blob/master/src/store.js#L1-L7
const connectionStore = new WeakMap();
const connectionWeakStore = new WeakMap();
export function storeObservable(obj) {
// save normal observe() reactions
connectionStore.set(obj, new Map());
// save weakObserve() reactions
connectionWeakStore.set(obj, new WeakMap());
}
Weak reactions would be declared with the new API and are only held in memory while the reference is stored somewhere:
observe(() => {
// I am going to run forever.
});
const reaction = weakObserve(() => {
// I am going to run only while `reaction` is referenced somewhere.
});
The reference to reaction
will be held internally by the components using view
because they need it for the unmount phase. But if a component is garbage collected, the reference is lost and the reaction will go away with it.
I will be glad to work on a PR to do this but I would like to know @solkimicreb's opinion first to make sure that this is the right approach 🙂