@@ -14,6 +14,7 @@ import type {ReactPriorityLevel} from './SchedulerWithReactIntegration';
14
14
import type { Interaction } from 'scheduler/src/Tracing' ;
15
15
import type { SuspenseConfig } from './ReactFiberSuspenseConfig' ;
16
16
import type { SuspenseState } from './ReactFiberSuspenseComponent' ;
17
+ import type { Effect as HookEffect } from './ReactFiberHooks' ;
17
18
18
19
import {
19
20
warnAboutDeprecatedLifecycles ,
@@ -257,7 +258,8 @@ let rootDoesHavePassiveEffects: boolean = false;
257
258
let rootWithPendingPassiveEffects : FiberRoot | null = null ;
258
259
let pendingPassiveEffectsRenderPriority : ReactPriorityLevel = NoPriority ;
259
260
let pendingPassiveEffectsExpirationTime : ExpirationTime = NoWork ;
260
- let pendingUnmountedPassiveEffectDestroyFunctions : Array < ( ) => void > = [ ] ;
261
+ let pendingPassiveHookEffectsMount : Array < HookEffect | Fiber > = [ ] ;
262
+ let pendingPassiveHookEffectsUnmount : Array < HookEffect | Fiber > = [ ] ;
261
263
262
264
let rootsWithPendingDiscreteUpdates : Map <
263
265
FiberRoot ,
@@ -2168,11 +2170,28 @@ export function flushPassiveEffects() {
2168
2170
}
2169
2171
}
2170
2172
2171
- export function enqueuePendingPassiveEffectDestroyFn (
2172
- destroy : ( ) = > void ,
2173
+ export function enqueuePendingPassiveHookEffectMount (
2174
+ fiber : Fiber ,
2175
+ effect : HookEffect ,
2176
+ ) : void {
2177
+ if ( deferPassiveEffectCleanupDuringUnmount ) {
2178
+ pendingPassiveHookEffectsMount . push ( effect , fiber ) ;
2179
+ if ( ! rootDoesHavePassiveEffects ) {
2180
+ rootDoesHavePassiveEffects = true ;
2181
+ scheduleCallback ( NormalPriority , ( ) => {
2182
+ flushPassiveEffects ( ) ;
2183
+ return null ;
2184
+ } ) ;
2185
+ }
2186
+ }
2187
+ }
2188
+
2189
+ export function enqueuePendingPassiveHookEffectUnmount (
2190
+ fiber : Fiber ,
2191
+ effect : HookEffect ,
2173
2192
) : void {
2174
2193
if ( deferPassiveEffectCleanupDuringUnmount ) {
2175
- pendingUnmountedPassiveEffectDestroyFunctions . push ( destroy ) ;
2194
+ pendingPassiveHookEffectsUnmount . push ( effect , fiber ) ;
2176
2195
if ( ! rootDoesHavePassiveEffects ) {
2177
2196
rootDoesHavePassiveEffects = true ;
2178
2197
scheduleCallback ( NormalPriority , ( ) => {
@@ -2183,6 +2202,11 @@ export function enqueuePendingPassiveEffectDestroyFn(
2183
2202
}
2184
2203
}
2185
2204
2205
+ function invokePassiveEffectCreate ( effect : HookEffect ) : void {
2206
+ const create = effect . create ;
2207
+ effect . destroy = create ( ) ;
2208
+ }
2209
+
2186
2210
function flushPassiveEffectsImpl ( ) {
2187
2211
if ( rootWithPendingPassiveEffects === null ) {
2188
2212
return false ;
@@ -2201,45 +2225,95 @@ function flushPassiveEffectsImpl() {
2201
2225
const prevInteractions = pushInteractions ( root ) ;
2202
2226
2203
2227
if ( deferPassiveEffectCleanupDuringUnmount ) {
2204
- // Flush any pending passive effect destroy functions that belong to
2205
- // components that were unmounted during the most recent commit.
2206
- for (
2207
- let i = 0 ;
2208
- i < pendingUnmountedPassiveEffectDestroyFunctions . length ;
2209
- i ++
2210
- ) {
2211
- const destroy = pendingUnmountedPassiveEffectDestroyFunctions [ i ] ;
2212
- invokeGuardedCallback ( null , destroy , null ) ;
2228
+ // It's important that ALL pending passive effect destroy functions are called
2229
+ // before ANY passive effect create functions are called.
2230
+ // Otherwise effects in sibling components might interfere with each other.
2231
+ // e.g. a destroy function in one component may unintentionally override a ref
2232
+ // value set by a create function in another component.
2233
+ // Layout effects have the same constraint.
2234
+
2235
+ // First pass: Destroy stale passive effects.
2236
+ let unmountEffects = pendingPassiveHookEffectsUnmount ;
2237
+ pendingPassiveHookEffectsUnmount = [ ] ;
2238
+ for ( let i = 0 ; i < unmountEffects . length ; i += 2 ) {
2239
+ const effect = ( ( unmountEffects [ i ] : any ) : HookEffect ) ;
2240
+ const fiber = ( ( unmountEffects [ i + 1 ] : any ) : Fiber ) ;
2241
+ const destroy = effect . destroy ;
2242
+ effect . destroy = undefined ;
2243
+ if ( typeof destroy === 'function' ) {
2244
+ if ( __DEV__ ) {
2245
+ setCurrentDebugFiberInDEV ( fiber ) ;
2246
+ invokeGuardedCallback ( null , destroy , null ) ;
2247
+ if ( hasCaughtError ( ) ) {
2248
+ invariant ( fiber !== null , 'Should be working on an effect.' ) ;
2249
+ const error = clearCaughtError ( ) ;
2250
+ captureCommitPhaseError ( fiber , error ) ;
2251
+ }
2252
+ resetCurrentDebugFiberInDEV ( ) ;
2253
+ } else {
2254
+ try {
2255
+ destroy ( ) ;
2256
+ } catch ( error ) {
2257
+ invariant ( fiber !== null , 'Should be working on an effect.' ) ;
2258
+ captureCommitPhaseError ( fiber , error ) ;
2259
+ }
2260
+ }
2261
+ }
2213
2262
}
2214
- pendingUnmountedPassiveEffectDestroyFunctions . length = 0 ;
2215
- }
2216
2263
2217
- // Note: This currently assumes there are no passive effects on the root
2218
- // fiber, because the root is not part of its own effect list. This could
2219
- // change in the future.
2220
- let effect = root . current . firstEffect ;
2221
- while ( effect !== null ) {
2222
- if ( __DEV__ ) {
2223
- setCurrentDebugFiberInDEV ( effect ) ;
2224
- invokeGuardedCallback ( null , commitPassiveHookEffects , null , effect ) ;
2225
- if ( hasCaughtError ( ) ) {
2226
- invariant ( effect !== null , 'Should be working on an effect.' ) ;
2227
- const error = clearCaughtError ( ) ;
2228
- captureCommitPhaseError ( effect , error ) ;
2264
+ // Second pass: Create new passive effects.
2265
+ let mountEffects = pendingPassiveHookEffectsMount ;
2266
+ pendingPassiveHookEffectsMount = [ ] ;
2267
+ for ( let i = 0 ; i < mountEffects . length ; i += 2 ) {
2268
+ const effect = ( ( mountEffects [ i ] : any ) : HookEffect ) ;
2269
+ const fiber = ( ( mountEffects [ i + 1 ] : any ) : Fiber ) ;
2270
+ if ( __DEV__ ) {
2271
+ setCurrentDebugFiberInDEV ( fiber ) ;
2272
+ invokeGuardedCallback ( null , invokePassiveEffectCreate , null , effect ) ;
2273
+ if ( hasCaughtError ( ) ) {
2274
+ invariant ( fiber !== null , 'Should be working on an effect.' ) ;
2275
+ const error = clearCaughtError ( ) ;
2276
+ captureCommitPhaseError ( fiber , error ) ;
2277
+ }
2278
+ resetCurrentDebugFiberInDEV ( ) ;
2279
+ } else {
2280
+ try {
2281
+ const create = effect . create ;
2282
+ effect . destroy = create ( ) ;
2283
+ } catch ( error ) {
2284
+ invariant ( fiber !== null , 'Should be working on an effect.' ) ;
2285
+ captureCommitPhaseError ( fiber , error ) ;
2286
+ }
2229
2287
}
2230
- resetCurrentDebugFiberInDEV ( ) ;
2231
- } else {
2232
- try {
2233
- commitPassiveHookEffects ( effect ) ;
2234
- } catch ( error ) {
2235
- invariant ( effect !== null , 'Should be working on an effect.' ) ;
2236
- captureCommitPhaseError ( effect , error ) ;
2288
+ }
2289
+ } else {
2290
+ // Note: This currently assumes there are no passive effects on the root fiber
2291
+ // because the root is not part of its own effect list.
2292
+ // This could change in the future.
2293
+ let effect = root . current . firstEffect ;
2294
+ while ( effect !== null ) {
2295
+ if ( __DEV__ ) {
2296
+ setCurrentDebugFiberInDEV ( effect ) ;
2297
+ invokeGuardedCallback ( null , commitPassiveHookEffects , null , effect ) ;
2298
+ if ( hasCaughtError ( ) ) {
2299
+ invariant ( effect !== null , 'Should be working on an effect.' ) ;
2300
+ const error = clearCaughtError ( ) ;
2301
+ captureCommitPhaseError ( effect , error ) ;
2302
+ }
2303
+ resetCurrentDebugFiberInDEV ( ) ;
2304
+ } else {
2305
+ try {
2306
+ commitPassiveHookEffects ( effect ) ;
2307
+ } catch ( error ) {
2308
+ invariant ( effect !== null , 'Should be working on an effect.' ) ;
2309
+ captureCommitPhaseError ( effect , error ) ;
2310
+ }
2237
2311
}
2312
+ const nextNextEffect = effect . nextEffect ;
2313
+ // Remove nextEffect pointer to assist GC
2314
+ effect . nextEffect = null ;
2315
+ effect = nextNextEffect ;
2238
2316
}
2239
- const nextNextEffect = effect . nextEffect ;
2240
- // Remove nextEffect pointer to assist GC
2241
- effect . nextEffect = null ;
2242
- effect = nextNextEffect ;
2243
2317
}
2244
2318
2245
2319
if ( enableSchedulerTracing ) {
0 commit comments