@@ -14,6 +14,7 @@ import type {ReactPriorityLevel} from './SchedulerWithReactIntegration';
1414import type { Interaction } from 'scheduler/src/Tracing' ;
1515import type { SuspenseConfig } from './ReactFiberSuspenseConfig' ;
1616import type { SuspenseState } from './ReactFiberSuspenseComponent' ;
17+ import type { Effect as HookEffect } from './ReactFiberHooks' ;
1718
1819import {
1920 warnAboutDeprecatedLifecycles ,
@@ -257,7 +258,8 @@ let rootDoesHavePassiveEffects: boolean = false;
257258let rootWithPendingPassiveEffects : FiberRoot | null = null ;
258259let pendingPassiveEffectsRenderPriority : ReactPriorityLevel = NoPriority ;
259260let pendingPassiveEffectsExpirationTime : ExpirationTime = NoWork ;
260- let pendingUnmountedPassiveEffectDestroyFunctions : Array < ( ) => void > = [ ] ;
261+ let pendingPassiveHookEffectsMount : Array < HookEffect | Fiber > = [ ] ;
262+ let pendingPassiveHookEffectsUnmount : Array < HookEffect | Fiber > = [ ] ;
261263
262264let rootsWithPendingDiscreteUpdates : Map <
263265 FiberRoot ,
@@ -2168,11 +2170,28 @@ export function flushPassiveEffects() {
21682170 }
21692171}
21702172
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 ,
21732192) : void {
21742193 if ( deferPassiveEffectCleanupDuringUnmount ) {
2175- pendingUnmountedPassiveEffectDestroyFunctions . push ( destroy ) ;
2194+ pendingPassiveHookEffectsUnmount . push ( effect , fiber ) ;
21762195 if ( ! rootDoesHavePassiveEffects ) {
21772196 rootDoesHavePassiveEffects = true ;
21782197 scheduleCallback ( NormalPriority , ( ) => {
@@ -2183,6 +2202,11 @@ export function enqueuePendingPassiveEffectDestroyFn(
21832202 }
21842203}
21852204
2205+ function invokePassiveEffectCreate ( effect : HookEffect ) : void {
2206+ const create = effect . create ;
2207+ effect . destroy = create ( ) ;
2208+ }
2209+
21862210function flushPassiveEffectsImpl ( ) {
21872211 if ( rootWithPendingPassiveEffects === null ) {
21882212 return false ;
@@ -2201,45 +2225,95 @@ function flushPassiveEffectsImpl() {
22012225 const prevInteractions = pushInteractions ( root ) ;
22022226
22032227 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+ }
22132262 }
2214- pendingUnmountedPassiveEffectDestroyFunctions . length = 0 ;
2215- }
22162263
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+ }
22292287 }
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+ }
22372311 }
2312+ const nextNextEffect = effect . nextEffect ;
2313+ // Remove nextEffect pointer to assist GC
2314+ effect . nextEffect = null ;
2315+ effect = nextNextEffect ;
22382316 }
2239- const nextNextEffect = effect . nextEffect ;
2240- // Remove nextEffect pointer to assist GC
2241- effect . nextEffect = null ;
2242- effect = nextNextEffect ;
22432317 }
22442318
22452319 if ( enableSchedulerTracing ) {
0 commit comments