@@ -106,19 +106,20 @@ function createEffectStore(): EffectStore {
106106// To track when we are entering and exiting a component render (i.e. before and
107107// after React renders a component), we track how the dispatcher changes.
108108// Outside of a component rendering, the dispatcher is set to an instance that
109- // errors or warns when any hooks are called (this is too prevent hooks from
110- // being used outside of components) . Right before React renders a component,
111- // the dispatcher is set to a valid one. Right after React finishes rendering a
112- // component, the dispatcher is set to an erroring one again. This erroring
113- // dispatcher is called `ContextOnlyDispatcher` in React's source.
109+ // errors or warns when any hooks are called. This behavior is prevents hooks
110+ // from being used outside of components. Right before React renders a
111+ // component, the dispatcher is set to a valid one. Right after React finishes
112+ // rendering a component, the dispatcher is set to an erroring one again. This
113+ // erroring dispatcher is called the `ContextOnlyDispatcher` in React's source.
114114//
115- // So, we use this getter and setter to monitor the changes to the current
116- // ReactDispatcher. When the dispatcher changes from the ContextOnlyDispatcher
117- // to a valid dispatcher, we assume we are entering a component render. At this
118- // point, we setup our auto-subscriptions for any signals used in the component.
119- // We do this by creating an effect and manually starting the effect. We use
120- // `useReducer` to get access to a `rerender` function that we can use to
121- // manually trigger a rerender when a signal we've subscribed changes.
115+ // So, we watch the getter and setter on `ReactCurrentDispatcher.current` to
116+ // monitor the changes to the current ReactDispatcher. When the dispatcher
117+ // changes from the ContextOnlyDispatcher to a valid dispatcher, we assume we
118+ // are entering a component render. At this point, we setup our
119+ // auto-subscriptions for any signals used in the component. We do this by
120+ // creating an effect and manually starting the effect. We use
121+ // `useSyncExternalStore` to trigger rerenders on the component when any signals
122+ // it uses changes.
122123//
123124// When the dispatcher changes from a valid dispatcher back to the
124125// ContextOnlyDispatcher, we assume we are exiting a component render. At this
@@ -137,13 +138,13 @@ function createEffectStore(): EffectStore {
137138//
138139// When a Component's function body invokes useReducer, useState, or useMemo,
139140// this change in dispatcher should not signal that we are exiting a component
140- // render. We ignore this change cuz this erroring dispatcher does not pass
141- // the ContextOnlyDispatcher check and so does not affect our logic .
141+ // render. We ignore this change by detecting these dispatchers as different
142+ // from ContextOnlyDispatcher and other valid dispatchers .
142143//
143144// - The `use` hook will change the dispatcher to from a valid update dispatcher
144145// to a valid mount dispatcher in some cases. Similarly to useReducer
145146// mentioned above, we should not signal that we are exiting a component
146- // during this change. Because these other dispatchers do not pass the
147+ // during this change. Because these other valid dispatchers do not pass the
147148// ContextOnlyDispatcher check, they do not affect our logic.
148149let lock = false ;
149150let currentDispatcher : ReactDispatcher | null = null ;
@@ -157,13 +158,20 @@ Object.defineProperty(ReactInternals.ReactCurrentDispatcher, "current", {
157158 return ;
158159 }
159160
161+ const currentDispatcherType = getDispatcherType ( currentDispatcher ) ;
162+ const nextDispatcherType = getDispatcherType ( nextDispatcher ) ;
163+
164+ // We are entering a component render if the current dispatcher is the
165+ // ContextOnlyDispatcher and the next dispatcher is a valid dispatcher.
160166 const isEnteringComponentRender =
161- isContextOnlyDispatcher ( currentDispatcher ) &&
162- ! isContextOnlyDispatcher ( nextDispatcher ) ;
167+ currentDispatcherType === ContextOnlyDispatcherType &&
168+ nextDispatcherType === ValidDispatcherType ;
163169
170+ // We are exiting a component render if the current dispatcher is a valid
171+ // dispatcher and the next dispatcher is the ContextOnlyDispatcher.
164172 const isExitingComponentRender =
165- ! isContextOnlyDispatcher ( currentDispatcher ) &&
166- isContextOnlyDispatcher ( nextDispatcher ) ;
173+ currentDispatcherType === ValidDispatcherType &&
174+ nextDispatcherType === ContextOnlyDispatcherType ;
167175
168176 // Update the current dispatcher now so the hooks inside of the
169177 // useSyncExternalStore shim get the right dispatcher.
@@ -190,13 +198,17 @@ Object.defineProperty(ReactInternals.ReactCurrentDispatcher, "current", {
190198 } ,
191199} ) ;
192200
201+ const ValidDispatcherType = 0 ;
202+ const ContextOnlyDispatcherType = 1 ;
203+ const ErroringDispatcherType = 2 ;
204+
193205// We inject a useSyncExternalStore into every function component via
194206// CurrentDispatcher. This prevents injecting into anything other than a
195207// function component render.
196- const dispatcherTypeCache = new Map ( ) ;
197- function isContextOnlyDispatcher ( dispatcher : ReactDispatcher | null ) {
208+ const dispatcherTypeCache = new Map < ReactDispatcher , number > ( ) ;
209+ function getDispatcherType ( dispatcher : ReactDispatcher | null ) : number {
198210 // Treat null the same as the ContextOnlyDispatcher.
199- if ( ! dispatcher ) return true ;
211+ if ( ! dispatcher ) return ContextOnlyDispatcherType ;
200212
201213 const cached = dispatcherTypeCache . get ( dispatcher ) ;
202214 if ( cached !== undefined ) return cached ;
@@ -206,9 +218,17 @@ function isContextOnlyDispatcher(dispatcher: ReactDispatcher | null) {
206218 // for this dispatcher's useCallback implementation to determine if it is a
207219 // ContextOnlyDispatcher. All other dispatchers, erroring or not, define
208220 // functions with arguments and so fail this check.
209- const isContextOnlyDispatcher = dispatcher . useCallback . length < 2 ;
210- dispatcherTypeCache . set ( dispatcher , isContextOnlyDispatcher ) ;
211- return isContextOnlyDispatcher ;
221+ let type : number ;
222+ if ( dispatcher . useCallback . length < 2 ) {
223+ type = ContextOnlyDispatcherType ;
224+ } else if ( / I n v a l i d / . test ( dispatcher . useCallback as any ) ) {
225+ type = ErroringDispatcherType ;
226+ } else {
227+ type = ValidDispatcherType ;
228+ }
229+
230+ dispatcherTypeCache . set ( dispatcher , type ) ;
231+ return type ;
212232}
213233
214234function WrapJsx < T > ( jsx : T ) : T {
0 commit comments