@@ -26,6 +26,7 @@ import type {ReactPriorityLevel} from './SchedulerWithReactIntegration';
26
26
27
27
import { unstable_wrap as Schedule_tracing_wrap } from 'scheduler/tracing' ;
28
28
import {
29
+ deferPassiveEffectCleanupDuringUnmount ,
29
30
enableSchedulerTracing ,
30
31
enableProfilerTimer ,
31
32
enableSuspenseServerRenderer ,
@@ -109,16 +110,13 @@ import {
109
110
captureCommitPhaseError ,
110
111
resolveRetryThenable ,
111
112
markCommitTimeOfFallback ,
113
+ enqueuePendingPassiveEffectDestroyFn ,
112
114
} from './ReactFiberWorkLoop' ;
113
115
import {
114
116
NoEffect as NoHookEffect ,
115
- UnmountSnapshot ,
116
- UnmountMutation ,
117
- MountMutation ,
118
- UnmountLayout ,
119
- MountLayout ,
120
- UnmountPassive ,
121
- MountPassive ,
117
+ HasEffect as HookHasEffect ,
118
+ Layout as HookLayout ,
119
+ Passive as HookPassive ,
122
120
} from './ReactHookEffectTags' ;
123
121
import { didWarnAboutReassigningProps } from './ReactFiberBeginWork' ;
124
122
import { runWithPriority , NormalPriority } from './SchedulerWithReactIntegration' ;
@@ -250,7 +248,6 @@ function commitBeforeMutationLifeCycles(
250
248
case ForwardRef :
251
249
case SimpleMemoComponent :
252
250
case Chunk : {
253
- commitHookEffectList ( UnmountSnapshot , NoHookEffect , finishedWork ) ;
254
251
return ;
255
252
}
256
253
case ClassComponent : {
@@ -328,26 +325,34 @@ function commitBeforeMutationLifeCycles(
328
325
) ;
329
326
}
330
327
331
- function commitHookEffectList (
332
- unmountTag : number ,
333
- mountTag : number ,
334
- finishedWork : Fiber ,
335
- ) {
328
+ function commitHookEffectListUnmount ( tag : number , finishedWork : Fiber ) {
336
329
const updateQueue : FunctionComponentUpdateQueue | null = ( finishedWork . updateQueue : any ) ;
337
330
let lastEffect = updateQueue !== null ? updateQueue . lastEffect : null ;
338
331
if ( lastEffect !== null ) {
339
332
const firstEffect = lastEffect . next ;
340
333
let effect = firstEffect ;
341
334
do {
342
- if ( ( effect . tag & unmountTag ) !== NoHookEffect ) {
335
+ if ( ( effect . tag & tag ) === tag ) {
343
336
// Unmount
344
337
const destroy = effect . destroy ;
345
338
effect . destroy = undefined ;
346
339
if ( destroy !== undefined ) {
347
340
destroy ( ) ;
348
341
}
349
342
}
350
- if ( ( effect . tag & mountTag ) !== NoHookEffect ) {
343
+ effect = effect . next ;
344
+ } while ( effect !== firstEffect ) ;
345
+ }
346
+ }
347
+
348
+ function commitHookEffectListMount ( tag : number , finishedWork : Fiber ) {
349
+ const updateQueue : FunctionComponentUpdateQueue | null = ( finishedWork . updateQueue : any ) ;
350
+ let lastEffect = updateQueue !== null ? updateQueue . lastEffect : null ;
351
+ if ( lastEffect !== null ) {
352
+ const firstEffect = lastEffect . next ;
353
+ let effect = firstEffect ;
354
+ do {
355
+ if ( ( effect . tag & tag ) === tag ) {
351
356
// Mount
352
357
const create = effect . create ;
353
358
effect . destroy = create ( ) ;
@@ -398,8 +403,11 @@ export function commitPassiveHookEffects(finishedWork: Fiber): void {
398
403
case ForwardRef :
399
404
case SimpleMemoComponent :
400
405
case Chunk : {
401
- commitHookEffectList ( UnmountPassive , NoHookEffect , finishedWork ) ;
402
- commitHookEffectList ( NoHookEffect , MountPassive , finishedWork ) ;
406
+ // TODO (#17945) We should call all passive destroy functions (for all fibers)
407
+ // before calling any create functions. The current approach only serializes
408
+ // these for a single fiber.
409
+ commitHookEffectListUnmount ( HookPassive | HookHasEffect , finishedWork ) ;
410
+ commitHookEffectListMount ( HookPassive | HookHasEffect , finishedWork ) ;
403
411
break ;
404
412
}
405
413
default :
@@ -419,7 +427,11 @@ function commitLifeCycles(
419
427
case ForwardRef :
420
428
case SimpleMemoComponent :
421
429
case Chunk : {
422
- commitHookEffectList ( UnmountLayout , MountLayout , finishedWork ) ;
430
+ // At this point layout effects have already been destroyed (during mutation phase).
431
+ // This is done to prevent sibling component effects from interfering with each other,
432
+ // e.g. a destroy function in one component should never override a ref set
433
+ // by a create function in another component during the same commit.
434
+ commitHookEffectListMount ( HookLayout | HookHasEffect , finishedWork ) ;
423
435
return ;
424
436
}
425
437
case ClassComponent : {
@@ -756,32 +768,47 @@ function commitUnmount(
756
768
if ( lastEffect !== null ) {
757
769
const firstEffect = lastEffect . next ;
758
770
759
- // When the owner fiber is deleted, the destroy function of a passive
760
- // effect hook is called during the synchronous commit phase. This is
761
- // a concession to implementation complexity. Calling it in the
762
- // passive effect phase (like they usually are, when dependencies
763
- // change during an update) would require either traversing the
764
- // children of the deleted fiber again, or including unmount effects
765
- // as part of the fiber effect list.
766
- //
767
- // Because this is during the sync commit phase, we need to change
768
- // the priority.
769
- //
770
- // TODO: Reconsider this implementation trade off.
771
- const priorityLevel =
772
- renderPriorityLevel > NormalPriority
773
- ? NormalPriority
774
- : renderPriorityLevel ;
775
- runWithPriority ( priorityLevel , ( ) => {
771
+ if ( deferPassiveEffectCleanupDuringUnmount ) {
776
772
let effect = firstEffect ;
777
773
do {
778
- const destroy = effect . destroy ;
774
+ const { destroy, tag } = effect ;
779
775
if ( destroy !== undefined ) {
780
- safelyCallDestroy ( current , destroy ) ;
776
+ if ( ( tag & HookPassive ) !== NoHookEffect ) {
777
+ enqueuePendingPassiveEffectDestroyFn ( destroy ) ;
778
+ } else {
779
+ safelyCallDestroy ( current , destroy ) ;
780
+ }
781
781
}
782
782
effect = effect . next ;
783
783
} while ( effect !== firstEffect ) ;
784
- } ) ;
784
+ } else {
785
+ // When the owner fiber is deleted, the destroy function of a passive
786
+ // effect hook is called during the synchronous commit phase. This is
787
+ // a concession to implementation complexity. Calling it in the
788
+ // passive effect phase (like they usually are, when dependencies
789
+ // change during an update) would require either traversing the
790
+ // children of the deleted fiber again, or including unmount effects
791
+ // as part of the fiber effect list.
792
+ //
793
+ // Because this is during the sync commit phase, we need to change
794
+ // the priority.
795
+ //
796
+ // TODO: Reconsider this implementation trade off.
797
+ const priorityLevel =
798
+ renderPriorityLevel > NormalPriority
799
+ ? NormalPriority
800
+ : renderPriorityLevel ;
801
+ runWithPriority ( priorityLevel , ( ) => {
802
+ let effect = firstEffect ;
803
+ do {
804
+ const destroy = effect . destroy ;
805
+ if ( destroy !== undefined ) {
806
+ safelyCallDestroy ( current , destroy ) ;
807
+ }
808
+ effect = effect . next ;
809
+ } while ( effect !== firstEffect ) ;
810
+ } ) ;
811
+ }
785
812
}
786
813
}
787
814
return ;
@@ -1285,9 +1312,12 @@ function commitWork(current: Fiber | null, finishedWork: Fiber): void {
1285
1312
case MemoComponent :
1286
1313
case SimpleMemoComponent :
1287
1314
case Chunk : {
1288
- // Note: We currently never use MountMutation, but useLayout uses
1289
- // UnmountMutation.
1290
- commitHookEffectList ( UnmountMutation , MountMutation , finishedWork ) ;
1315
+ // Layout effects are destroyed during the mutation phase so that all
1316
+ // destroy functions for all fibers are called before any create functions.
1317
+ // This prevents sibling component effects from interfering with each other,
1318
+ // e.g. a destroy function in one component should never override a ref set
1319
+ // by a create function in another component during the same commit.
1320
+ commitHookEffectListUnmount ( HookLayout | HookHasEffect , finishedWork ) ;
1291
1321
return ;
1292
1322
}
1293
1323
case Profiler : {
@@ -1325,9 +1355,12 @@ function commitWork(current: Fiber | null, finishedWork: Fiber): void {
1325
1355
case MemoComponent :
1326
1356
case SimpleMemoComponent :
1327
1357
case Chunk : {
1328
- // Note: We currently never use MountMutation, but useLayout uses
1329
- // UnmountMutation.
1330
- commitHookEffectList ( UnmountMutation , MountMutation , finishedWork ) ;
1358
+ // Layout effects are destroyed during the mutation phase so that all
1359
+ // destroy functions for all fibers are called before any create functions.
1360
+ // This prevents sibling component effects from interfering with each other,
1361
+ // e.g. a destroy function in one component should never override a ref set
1362
+ // by a create function in another component during the same commit.
1363
+ commitHookEffectListUnmount ( HookLayout | HookHasEffect , finishedWork ) ;
1331
1364
return ;
1332
1365
}
1333
1366
case ClassComponent : {
0 commit comments