-
Notifications
You must be signed in to change notification settings - Fork 49.8k
Flush all passive destroy fns before calling create fns #17947
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
This change offers a small advantage over the way we did things previous: it continues invoking destroy functions even after a previous one errored.
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,6 +14,7 @@ import type {ReactPriorityLevel} from './SchedulerWithReactIntegration'; | |
| import type {Interaction} from 'scheduler/src/Tracing'; | ||
| import type {SuspenseConfig} from './ReactFiberSuspenseConfig'; | ||
| import type {SuspenseState} from './ReactFiberSuspenseComponent'; | ||
| import type {Effect as HookEffect} from './ReactFiberHooks'; | ||
|
|
||
| import { | ||
| warnAboutDeprecatedLifecycles, | ||
|
|
@@ -132,8 +133,6 @@ import { | |
| commitBeforeMutationLifeCycles as commitBeforeMutationEffectOnFiber, | ||
| commitLifeCycles as commitLayoutEffectOnFiber, | ||
| commitPassiveHookEffects, | ||
| commitPassiveHookUnmountEffects, | ||
| commitPassiveHookMountEffects, | ||
| commitPlacement, | ||
| commitWork, | ||
| commitDeletion, | ||
|
|
@@ -259,7 +258,8 @@ let rootDoesHavePassiveEffects: boolean = false; | |
| let rootWithPendingPassiveEffects: FiberRoot | null = null; | ||
| let pendingPassiveEffectsRenderPriority: ReactPriorityLevel = NoPriority; | ||
| let pendingPassiveEffectsExpirationTime: ExpirationTime = NoWork; | ||
| let pendingUnmountedPassiveEffectDestroyFunctions: Array<() => void> = []; | ||
| let pendingPassiveHookEffectsMount: Array<HookEffect | Fiber> = []; | ||
| let pendingPassiveHookEffectsUnmount: Array<HookEffect | Fiber> = []; | ||
|
|
||
| let rootsWithPendingDiscreteUpdates: Map< | ||
| FiberRoot, | ||
|
|
@@ -2170,11 +2170,28 @@ export function flushPassiveEffects() { | |
| } | ||
| } | ||
|
|
||
| export function enqueuePendingPassiveEffectDestroyFn( | ||
| destroy: () => void, | ||
| export function enqueuePendingPassiveHookEffectMount( | ||
| fiber: Fiber, | ||
| effect: HookEffect, | ||
| ): void { | ||
| if (deferPassiveEffectCleanupDuringUnmount) { | ||
| pendingPassiveHookEffectsMount.push(effect, fiber); | ||
| if (!rootDoesHavePassiveEffects) { | ||
| rootDoesHavePassiveEffects = true; | ||
| scheduleCallback(NormalPriority, () => { | ||
| flushPassiveEffects(); | ||
| return null; | ||
| }); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| export function enqueuePendingPassiveHookEffectUnmount( | ||
| fiber: Fiber, | ||
| effect: HookEffect, | ||
| ): void { | ||
| if (deferPassiveEffectCleanupDuringUnmount) { | ||
| pendingUnmountedPassiveEffectDestroyFunctions.push(destroy); | ||
| pendingPassiveHookEffectsUnmount.push(effect, fiber); | ||
| if (!rootDoesHavePassiveEffects) { | ||
| rootDoesHavePassiveEffects = true; | ||
| scheduleCallback(NormalPriority, () => { | ||
|
|
@@ -2185,6 +2202,11 @@ export function enqueuePendingPassiveEffectDestroyFn( | |
| } | ||
| } | ||
|
|
||
| function invokePassiveEffectCreate(effect: HookEffect): void { | ||
| const create = effect.create; | ||
| effect.destroy = create(); | ||
| } | ||
|
|
||
| function flushPassiveEffectsImpl() { | ||
| if (rootWithPendingPassiveEffects === null) { | ||
| return false; | ||
|
|
@@ -2203,18 +2225,6 @@ function flushPassiveEffectsImpl() { | |
| const prevInteractions = pushInteractions(root); | ||
|
|
||
| if (deferPassiveEffectCleanupDuringUnmount) { | ||
| // Flush any pending passive effect destroy functions that belong to | ||
| // components that were unmounted during the most recent commit. | ||
| for ( | ||
| let i = 0; | ||
| i < pendingUnmountedPassiveEffectDestroyFunctions.length; | ||
| i++ | ||
| ) { | ||
| const destroy = pendingUnmountedPassiveEffectDestroyFunctions[i]; | ||
| invokeGuardedCallback(null, destroy, null); | ||
| } | ||
| pendingUnmountedPassiveEffectDestroyFunctions.length = 0; | ||
|
|
||
| // It's important that ALL pending passive effect destroy functions are called | ||
| // before ANY passive effect create functions are called. | ||
| // Otherwise effects in sibling components might interfere with each other. | ||
|
|
@@ -2223,70 +2233,58 @@ function flushPassiveEffectsImpl() { | |
| // Layout effects have the same constraint. | ||
|
|
||
| // First pass: Destroy stale passive effects. | ||
| // | ||
| // Note: This currently assumes there are no passive effects on the root fiber | ||
| // because the root is not part of its own effect list. | ||
| // This could change in the future. | ||
| let effect = root.current.firstEffect; | ||
| while (effect !== null) { | ||
| if (__DEV__) { | ||
| setCurrentDebugFiberInDEV(effect); | ||
| invokeGuardedCallback( | ||
| null, | ||
| commitPassiveHookUnmountEffects, | ||
| null, | ||
| effect, | ||
| ); | ||
| if (hasCaughtError()) { | ||
| invariant(effect !== null, 'Should be working on an effect.'); | ||
| const error = clearCaughtError(); | ||
| captureCommitPhaseError(effect, error); | ||
| } | ||
| resetCurrentDebugFiberInDEV(); | ||
| } else { | ||
| try { | ||
| commitPassiveHookUnmountEffects(effect); | ||
| } catch (error) { | ||
| invariant(effect !== null, 'Should be working on an effect.'); | ||
| captureCommitPhaseError(effect, error); | ||
| let unmountEffects = pendingPassiveHookEffectsUnmount; | ||
| pendingPassiveHookEffectsUnmount = []; | ||
| for (let i = 0; i < unmountEffects.length; i += 2) { | ||
| const effect = ((unmountEffects[i]: any): HookEffect); | ||
| const fiber = ((unmountEffects[i + 1]: any): Fiber); | ||
| const destroy = effect.destroy; | ||
| effect.destroy = undefined; | ||
| if (typeof destroy === 'function') { | ||
| if (__DEV__) { | ||
| setCurrentDebugFiberInDEV(fiber); | ||
| invokeGuardedCallback(null, destroy, null); | ||
| if (hasCaughtError()) { | ||
| invariant(fiber !== null, 'Should be working on an effect.'); | ||
| const error = clearCaughtError(); | ||
| captureCommitPhaseError(fiber, error); | ||
| } | ||
| resetCurrentDebugFiberInDEV(); | ||
| } else { | ||
| try { | ||
| destroy(); | ||
| } catch (error) { | ||
| invariant(fiber !== null, 'Should be working on an effect.'); | ||
| captureCommitPhaseError(fiber, error); | ||
| } | ||
| } | ||
| } | ||
| effect = effect.nextEffect; | ||
| } | ||
|
|
||
| // Second pass: Create new passive effects. | ||
| // | ||
| // Note: This currently assumes there are no passive effects on the root fiber | ||
| // because the root is not part of its own effect list. | ||
| // This could change in the future. | ||
| effect = root.current.firstEffect; | ||
| while (effect !== null) { | ||
| let mountEffects = pendingPassiveHookEffectsMount; | ||
| pendingPassiveHookEffectsMount = []; | ||
| for (let i = 0; i < mountEffects.length; i += 2) { | ||
| const effect = ((mountEffects[i]: any): HookEffect); | ||
| const fiber = ((mountEffects[i + 1]: any): Fiber); | ||
| if (__DEV__) { | ||
| setCurrentDebugFiberInDEV(effect); | ||
| invokeGuardedCallback( | ||
| null, | ||
| commitPassiveHookMountEffects, | ||
| null, | ||
| effect, | ||
| ); | ||
| setCurrentDebugFiberInDEV(fiber); | ||
| invokeGuardedCallback(null, invokePassiveEffectCreate, null, effect); | ||
| if (hasCaughtError()) { | ||
| invariant(effect !== null, 'Should be working on an effect.'); | ||
| invariant(fiber !== null, 'Should be working on an effect.'); | ||
| const error = clearCaughtError(); | ||
| captureCommitPhaseError(effect, error); | ||
| captureCommitPhaseError(fiber, error); | ||
| } | ||
| resetCurrentDebugFiberInDEV(); | ||
| } else { | ||
| try { | ||
| commitPassiveHookMountEffects(effect); | ||
| const create = effect.create; | ||
| effect.destroy = create(); | ||
| } catch (error) { | ||
| invariant(effect !== null, 'Should be working on an effect.'); | ||
| captureCommitPhaseError(effect, error); | ||
| invariant(fiber !== null, 'Should be working on an effect.'); | ||
| captureCommitPhaseError(fiber, error); | ||
| } | ||
| } | ||
| const nextNextEffect = effect.nextEffect; | ||
| // Remove nextEffect pointer to assist GC | ||
| effect.nextEffect = null; | ||
| effect = nextNextEffect; | ||
| } | ||
| } else { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the old code path. If we flip the |
||
| // Note: This currently assumes there are no passive effects on the root fiber | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1753,6 +1753,7 @@ describe('ReactHooksWithNoopRenderer', () => { | |
| // not block the subsequent create functions from being run. | ||
| expect(Scheduler).toHaveYielded([ | ||
| 'Oops!', | ||
| 'Unmount B [0]', | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the main positive change wrt 41e0752. |
||
| 'Mount A [1]', | ||
| 'Mount B [1]', | ||
| ]); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Retaining both the effect and Fiber is kind of gross, particularly in the same array. I think we'd need to track them both somewhere though because of how we track and report errors.