14
import type {Interaction} from 'scheduler/src/Tracing';
15
import type {SuspenseConfig} from './ReactFiberSuspenseConfig';
16
import type {SuspenseState} from './ReactFiberSuspenseComponent';
17
+import type {Effect as HookEffect} from './ReactFiberHooks';
18
19
import {
20
warnAboutDeprecatedLifecycles,
258
let rootWithPendingPassiveEffects: FiberRoot | null = null;
259
let pendingPassiveEffectsRenderPriority: ReactPriorityLevel = NoPriority;
260
let pendingPassiveEffectsExpirationTime: ExpirationTime = NoWork;
260
-let pendingUnmountedPassiveEffectDestroyFunctions: Array<() => void> = [];
261
+let pendingPassiveHookEffectsMount: Array<HookEffect | Fiber> = [];
262
+let pendingPassiveHookEffectsUnmount: Array<HookEffect | Fiber> = [];
263
264
let rootsWithPendingDiscreteUpdates: Map<
265
FiberRoot,
2170
}
2171
}
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,
2192
): void {
2193
if (deferPassiveEffectCleanupDuringUnmount) {
2175
- pendingUnmountedPassiveEffectDestroyFunctions.push(destroy);
2194
+ pendingPassiveHookEffectsUnmount.push(effect, fiber);
2195
if (!rootDoesHavePassiveEffects) {
2196
rootDoesHavePassiveEffects = true;
2197
scheduleCallback(NormalPriority, () => {
2202
}
2203
}
2204
2205
+function invokePassiveEffectCreate(effect: HookEffect): void {
2206
+ const create = effect.create;
2207
+ effect.destroy = create();
2208
+}
2209
+
2210
function flushPassiveEffectsImpl() {
2211
if (rootWithPendingPassiveEffects === null) {
2212
return false;
2225
const prevInteractions = pushInteractions(root);
2226
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
+ }
2262
}
2214
- pendingUnmountedPassiveEffectDestroyFunctions.length = 0;
2215
- }
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
+ }
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
+ }
2311
}
2312
+ const nextNextEffect = effect.nextEffect;
2313
+ // Remove nextEffect pointer to assist GC
2314
+ effect.nextEffect = null;
2315
+ effect = nextNextEffect;
2316
}
2239
- const nextNextEffect = effect.nextEffect;
2240
- // Remove nextEffect pointer to assist GC
2241
- effect.nextEffect = null;
2242
- effect = nextNextEffect;
2317
}
2318
2319
if (enableSchedulerTracing) {