Skip to content

Commit c42302b

Browse files
committed
unsubscribe from effect
1 parent 25cf8a2 commit c42302b

File tree

1 file changed

+35
-6
lines changed

1 file changed

+35
-6
lines changed

packages/react/src/index.ts

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED as internals,
88
} from "react";
99
import React from "react";
10-
import {useSyncExternalStore} from "use-sync-external-store/shim"
10+
import { useSyncExternalStore } from "use-sync-external-store/shim";
1111
import {
1212
signal,
1313
computed,
@@ -22,6 +22,11 @@ export { signal, computed, batch, effect, Signal, type ReadonlySignal };
2222

2323
const Empty = Object.freeze([]);
2424

25+
/**
26+
* React uses a different entry-point depending on NODE_ENV env var
27+
*/
28+
const __DEV__ = process.env.NODE_ENV !== "production";
29+
2530
/**
2631
* Install a middleware into React.createElement to replace any Signals in props with their value.
2732
* @todo this likely needs to be duplicated for jsx()...
@@ -81,19 +86,43 @@ function setCurrentUpdater(updater?: Effect) {
8186
function createEffectStore() {
8287
let updater!: Effect;
8388
let version = 0;
89+
let onChangeNotifyReact: (() => void) | undefined;
8490

85-
effect(function (this: Effect) {
91+
const unsubscribe = effect(function (this: Effect) {
8692
updater = this;
8793
});
8894

95+
96+
updater._callback = function () {
97+
if (!onChangeNotifyReact) {
98+
/**
99+
* In dev, lazily unsubscribe self if React isn't subscribed to the store,
100+
* in other words, if the component is not mounted anymore.
101+
*
102+
* We do this to deal with StrictMode double rendering React quirks.
103+
* Only one of the renders is actually mounted.
104+
*/
105+
return void unsubscribe();
106+
}
107+
108+
version = (version + 1) | 0;
109+
onChangeNotifyReact();
110+
};
111+
89112
return {
90113
updater,
91114
subscribe(onStoreChange: () => void) {
92-
updater._callback = function () {
93-
version = (version + 1) & ~(1 << 31);
94-
onStoreChange();
115+
onChangeNotifyReact = onStoreChange;
116+
117+
return function () {
118+
/**
119+
* In StrictMode (in dev mode), React will play with subscribe/unsubscribe/subscribe in double renders,
120+
* We don't really want to unsubscribe during React's play-time and can't reliably know which of renders
121+
* will end up actually being mounted, so we defer unsubscribe to the updater._callback.
122+
*/
123+
if (!__DEV__) unsubscribe();
124+
onChangeNotifyReact = undefined;
95125
};
96-
return function _unsubscribe() {};
97126
},
98127
getSnapshot() {
99128
return version;

0 commit comments

Comments
 (0)