7
7
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED as internals ,
8
8
} from "react" ;
9
9
import React from "react" ;
10
- import { useSyncExternalStore } from "use-sync-external-store/shim"
10
+ import { useSyncExternalStore } from "use-sync-external-store/shim" ;
11
11
import {
12
12
signal ,
13
13
computed ,
@@ -22,6 +22,11 @@ export { signal, computed, batch, effect, Signal, type ReadonlySignal };
22
22
23
23
const Empty = Object . freeze ( [ ] ) ;
24
24
25
+ /**
26
+ * React uses a different entry-point depending on NODE_ENV env var
27
+ */
28
+ const __DEV__ = process . env . NODE_ENV !== "production" ;
29
+
25
30
/**
26
31
* Install a middleware into React.createElement to replace any Signals in props with their value.
27
32
* @todo this likely needs to be duplicated for jsx()...
@@ -81,19 +86,43 @@ function setCurrentUpdater(updater?: Effect) {
81
86
function createEffectStore ( ) {
82
87
let updater ! : Effect ;
83
88
let version = 0 ;
89
+ let onChangeNotifyReact : ( ( ) => void ) | undefined ;
84
90
85
- effect ( function ( this : Effect ) {
91
+ const unsubscribe = effect ( function ( this : Effect ) {
86
92
updater = this ;
87
93
} ) ;
88
94
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
+
89
112
return {
90
113
updater,
91
114
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 ;
95
125
} ;
96
- return function _unsubscribe ( ) { } ;
97
126
} ,
98
127
getSnapshot ( ) {
99
128
return version ;
0 commit comments