@samitouri / QOS-React-2 / commits / 765e89b908

Reset new fork to old fork (#20254)

* Fix typo This typo was fixed in the new fork but not the old. * Reset new fork to old fork Something in the new fork is causing a topline metrics regression. We're not sure what it is, so we're going to split it into steps and bisect. As a first step, this resets the new fork back to the contents of the old fork. We will land this to confirm that the fork infra itself is not causing a regression. * Fix tests: Add `dfsEffectsRefactor` flag Some of the tests that gated on the effects refactor used the `new` flag. In order to bisect, we'll need to decompose the new fork changes into multiple steps. So I added a hardcoded test flag called `dfsEffectsRefactor` and set it to false. Will turn back on when we switch back to traversing the finished tree using DFS and `subtreeTag`.

Andrew Clark committed Nov 13, 2020 at 13:54 UTC 765e89b908206fe62feb10240604db224f38de7d
19 files changed +1380 -2552
packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js
+1 -1
@@ -367,7 +367,7 @@ describe('ReactDOMServerPartialHydration', () => {
367 // This is a new node.
368 expect(span).not.toBe(span2);
369
370 - if (gate(flags => flags.new)) {
370 + if (gate(flags => flags.dfsEffectsRefactor)) {
371 // The effects list refactor causes this to be null because the Suspense Offscreen's child
372 // is null. However, since we can't hydrate Suspense in legacy this change in behavior is ok
373 expect(ref.current).toBe(null);
packages/react-dom/src/__tests__/ReactWrongReturnPointer-test.js
+1
@@ -16,6 +16,7 @@ beforeEach(() => {
16 });
17
18 // Don't feel too guilty if you have to delete this test.
19 +// @gate dfsEffectsRefactor
20 // @gate new
21 // @gate __DEV__
22 test('warns in DEV if return pointer is inconsistent', async () => {
packages/react-reconciler/src/ReactChildFiber.new.js
+13 -6
@@ -13,7 +13,7 @@ import type {Fiber} from './ReactInternalTypes';
13 import type {Lanes} from './ReactFiberLane';
14
15 import getComponentName from 'shared/getComponentName';
16 -import {Deletion, Placement} from './ReactFiberFlags';
16 +import {Placement, Deletion} from './ReactFiberFlags';
17 import {
18 getIteratorFn,
19 REACT_ELEMENT_TYPE,
@@ -256,13 +256,20 @@ function ChildReconciler(shouldTrackSideEffects) {
256 // Noop.
257 return;
258 }
259 - const deletions = returnFiber.deletions;
260 - if (deletions === null) {
261 - returnFiber.deletions = [childToDelete];
262 - returnFiber.flags |= Deletion;
259 + // Deletions are added in reversed order so we add it to the front.
260 + // At this point, the return fiber's effect list is empty except for
261 + // deletions, so we can just append the deletion to the list. The remaining
262 + // effects aren't added until the complete phase. Once we implement
263 + // resuming, this may not be true.
264 + const last = returnFiber.lastEffect;
265 + if (last !== null) {
266 + last.nextEffect = childToDelete;
267 + returnFiber.lastEffect = childToDelete;
268 } else {
264 - deletions.push(childToDelete);
269 + returnFiber.firstEffect = returnFiber.lastEffect = childToDelete;
270 }
271 + childToDelete.nextEffect = null;
272 + childToDelete.flags = Deletion;
273 }
274
275 function deleteRemainingChildren(
packages/react-reconciler/src/ReactFiber.new.js
+13 -4
@@ -28,7 +28,7 @@ import {
28 enableFundamentalAPI,
29 enableScopeAPI,
30 } from 'shared/ReactFeatureFlags';
31 -import {NoFlags, Placement, StaticMask} from './ReactFiberFlags';
31 +import {NoFlags, Placement} from './ReactFiberFlags';
32 import {ConcurrentRoot, BlockingRoot} from './ReactRootTags';
33 import {
34 IndeterminateComponent,
@@ -279,6 +279,13 @@ export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
279 workInProgress.type = current.type;
280
281 // We already have an alternate.
282 + // Reset the effect tag.
283 + workInProgress.flags = NoFlags;
284 +
285 + // The effect list is no longer valid.
286 + workInProgress.nextEffect = null;
287 + workInProgress.firstEffect = null;
288 + workInProgress.lastEffect = null;
289 workInProgress.subtreeFlags = NoFlags;
290 workInProgress.deletions = null;
291
@@ -292,9 +299,6 @@ export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
299 }
300 }
301
295 - // Reset all effects except static ones.
296 - // Static effects are not specific to a render.
297 - workInProgress.flags = current.flags & StaticMask;
302 workInProgress.childLanes = current.childLanes;
303 workInProgress.lanes = current.lanes;
304
@@ -360,6 +364,11 @@ export function resetWorkInProgress(workInProgress: Fiber, renderLanes: Lanes) {
364 // that child fiber is setting, not the reconciliation.
365 workInProgress.flags &= Placement;
366
367 + // The effect list is no longer valid.
368 + workInProgress.nextEffect = null;
369 + workInProgress.firstEffect = null;
370 + workInProgress.lastEffect = null;
371 +
372 const current = workInProgress.alternate;
373 if (current === null) {
374 // Reset to createFiber's initial values.
packages/react-reconciler/src/ReactFiberBeginWork.new.js
+42 -26
@@ -58,10 +58,10 @@ import {
58 Hydrating,
59 ContentReset,
60 DidCapture,
61 + Update,
62 Ref,
63 Deletion,
64 ForceUpdateForLegacySuspense,
64 - StaticMask,
65 } from './ReactFiberFlags';
66 import ReactSharedInternals from 'shared/ReactSharedInternals';
67 import {
@@ -671,6 +671,8 @@ function updateProfiler(
671 renderLanes: Lanes,
672 ) {
673 if (enableProfilerTimer) {
674 + workInProgress.flags |= Update;
675 +
676 // Reset effect durations for the next eventual effect phase.
677 // These are reset during render to allow the DevTools commit hook a chance to read them,
678 const stateNode = workInProgress.stateNode;
@@ -1077,9 +1079,6 @@ function updateHostComponent(
1079 workInProgress.flags |= ContentReset;
1080 }
1081
1080 - // React DevTools reads this flag.
1081 - workInProgress.flags |= PerformedWork;
1082 -
1082 markRef(current, workInProgress);
1083 reconcileChildren(current, workInProgress, nextChildren, renderLanes);
1084 return workInProgress.child;
@@ -2005,14 +2004,9 @@ function updateSuspensePrimaryChildren(
2004 primaryChildFragment.sibling = null;
2005 if (currentFallbackChildFragment !== null) {
2006 // Delete the fallback child fragment
2008 - const deletions = workInProgress.deletions;
2009 - if (deletions === null) {
2010 - workInProgress.deletions = [currentFallbackChildFragment];
2011 - // TODO (effects) Rename this to better reflect its new usage (e.g. ChildDeletions)
2012 - workInProgress.flags |= Deletion;
2013 - } else {
2014 - deletions.push(currentFallbackChildFragment);
2015 - }
2007 + currentFallbackChildFragment.nextEffect = null;
2008 + currentFallbackChildFragment.flags = Deletion;
2009 + workInProgress.firstEffect = workInProgress.lastEffect = currentFallbackChildFragment;
2010 }
2011
2012 workInProgress.child = primaryChildFragment;
@@ -2069,19 +2063,24 @@ function updateSuspenseFallbackChildren(
2063
2064 // The fallback fiber was added as a deletion effect during the first pass.
2065 // However, since we're going to remain on the fallback, we no longer want
2072 - // to delete it.
2073 - workInProgress.deletions = null;
2066 + // to delete it. So we need to remove it from the list. Deletions are stored
2067 + // on the same list as effects. We want to keep the effects from the primary
2068 + // tree. So we copy the primary child fragment's effect list, which does not
2069 + // include the fallback deletion effect.
2070 + const progressedLastEffect = primaryChildFragment.lastEffect;
2071 + if (progressedLastEffect !== null) {
2072 + workInProgress.firstEffect = primaryChildFragment.firstEffect;
2073 + workInProgress.lastEffect = progressedLastEffect;
2074 + progressedLastEffect.nextEffect = null;
2075 + } else {
2076 + // TODO: Reset this somewhere else? Lol legacy mode is so weird.
2077 + workInProgress.firstEffect = workInProgress.lastEffect = null;
2078 + }
2079 } else {
2080 primaryChildFragment = createWorkInProgressOffscreenFiber(
2081 currentPrimaryChildFragment,
2082 primaryChildProps,
2083 );
2079 -
2080 - // Since we're reusing a current tree, we need to reuse the flags, too.
2081 - // (We don't do this in legacy mode, because in legacy mode we don't re-use
2082 - // the current tree; see previous branch.)
2083 - primaryChildFragment.subtreeFlags =
2084 - currentPrimaryChildFragment.subtreeFlags & StaticMask;
2084 }
2085 let fallbackChildFragment;
2086 if (currentFallbackChildFragment !== null) {
@@ -2566,6 +2565,7 @@ function initSuspenseListRenderState(
2565 tail: null | Fiber,
2566 lastContentRow: null | Fiber,
2567 tailMode: SuspenseListTailMode,
2568 + lastEffectBeforeRendering: null | Fiber,
2569 ): void {
2570 const renderState: null | SuspenseListRenderState =
2571 workInProgress.memoizedState;
@@ -2577,6 +2577,7 @@ function initSuspenseListRenderState(
2577 last: lastContentRow,
2578 tail: tail,
2579 tailMode: tailMode,
2580 + lastEffect: lastEffectBeforeRendering,
2581 }: SuspenseListRenderState);
2582 } else {
2583 // We can reuse the existing object from previous renders.
@@ -2586,6 +2587,7 @@ function initSuspenseListRenderState(
2587 renderState.last = lastContentRow;
2588 renderState.tail = tail;
2589 renderState.tailMode = tailMode;
2590 + renderState.lastEffect = lastEffectBeforeRendering;
2591 }
2592 }
2593
@@ -2667,6 +2669,7 @@ function updateSuspenseListComponent(
2669 tail,
2670 lastContentRow,
2671 tailMode,
2672 + workInProgress.lastEffect,
2673 );
2674 break;
2675 }
@@ -2698,6 +2701,7 @@ function updateSuspenseListComponent(
2701 tail,
2702 null, // last
2703 tailMode,
2704 + workInProgress.lastEffect,
2705 );
2706 break;
2707 }
@@ -2708,6 +2712,7 @@ function updateSuspenseListComponent(
2712 null, // tail
2713 null, // last
2714 undefined,
2715 + workInProgress.lastEffect,
2716 );
2717 break;
2718 }
@@ -2967,14 +2972,15 @@ function remountFiber(
2972
2973 // Delete the old fiber and place the new one.
2974 // Since the old fiber is disconnected, we have to schedule it manually.
2970 - const deletions = returnFiber.deletions;
2971 - if (deletions === null) {
2972 - returnFiber.deletions = [current];
2973 - // TODO (effects) Rename this to better reflect its new usage (e.g. ChildDeletions)
2974 - returnFiber.flags |= Deletion;
2975 + const last = returnFiber.lastEffect;
2976 + if (last !== null) {
2977 + last.nextEffect = current;
2978 + returnFiber.lastEffect = current;
2979 } else {
2976 - deletions.push(current);
2980 + returnFiber.firstEffect = returnFiber.lastEffect = current;
2981 }
2982 + current.nextEffect = null;
2983 + current.flags = Deletion;
2984
2985 newWorkInProgress.flags |= Placement;
2986
@@ -3059,6 +3065,15 @@ function beginWork(
3065 }
3066 case Profiler:
3067 if (enableProfilerTimer) {
3068 + // Profiler should only call onRender when one of its descendants actually rendered.
3069 + const hasChildWork = includesSomeLane(
3070 + renderLanes,
3071 + workInProgress.childLanes,
3072 + );
3073 + if (hasChildWork) {
3074 + workInProgress.flags |= Update;
3075 + }
3076 +
3077 // Reset effect durations for the next eventual effect phase.
3078 // These are reset during render to allow the DevTools commit hook a chance to read them,
3079 const stateNode = workInProgress.stateNode;
@@ -3165,6 +3180,7 @@ function beginWork(
3180 // update in the past but didn't complete it.
3181 renderState.rendering = null;
3182 renderState.tail = null;
3183 + renderState.lastEffect = null;
3184 }
3185 pushSuspenseContext(workInProgress, suspenseStackCursor.current);
3186
packages/react-reconciler/src/ReactFiberClassComponent.new.js
+6 -46
@@ -12,14 +12,13 @@ import type {Lanes} from './ReactFiberLane';
12 import type {UpdateQueue} from './ReactUpdateQueue.new';
13
14 import * as React from 'react';
15 -import {Update, Snapshot, MountLayoutDev} from './ReactFiberFlags';
15 +import {Update, Snapshot} from './ReactFiberFlags';
16 import {
17 debugRenderPhaseSideEffectsForStrictMode,
18 disableLegacyContext,
19 enableDebugTracing,
20 enableSchedulingProfiler,
21 warnAboutDeprecatedLifecycles,
22 - enableDoubleInvokingEffects,
22 } from 'shared/ReactFeatureFlags';
23 import ReactStrictModeWarnings from './ReactStrictModeWarnings.new';
24 import {isMounted} from './ReactFiberTreeReflection';
@@ -30,13 +29,7 @@ import invariant from 'shared/invariant';
29 import {REACT_CONTEXT_TYPE, REACT_PROVIDER_TYPE} from 'shared/ReactSymbols';
30
31 import {resolveDefaultProps} from './ReactFiberLazyComponent.new';
33 -import {
34 - BlockingMode,
35 - ConcurrentMode,
36 - DebugTracingMode,
37 - NoMode,
38 - StrictMode,
39 -} from './ReactTypeOfMode';
32 +import {DebugTracingMode, StrictMode} from './ReactTypeOfMode';
33
34 import {
35 enqueueUpdate,
@@ -897,16 +890,7 @@ function mountClassInstance(
890 }
891
892 if (typeof instance.componentDidMount === 'function') {
900 - if (
901 - __DEV__ &&
902 - enableDoubleInvokingEffects &&
903 - (workInProgress.mode & (BlockingMode | ConcurrentMode)) !== NoMode
904 - ) {
905 - // Never double-invoke effects for legacy roots.
906 - workInProgress.flags |= MountLayoutDev | Update;
907 - } else {
908 - workInProgress.flags |= Update;
909 - }
893 + workInProgress.flags |= Update;
894 }
895 }
896
@@ -976,15 +960,7 @@ function resumeMountClassInstance(
960 // If an update was already in progress, we should schedule an Update
961 // effect even though we're bailing out, so that cWU/cDU are called.
962 if (typeof instance.componentDidMount === 'function') {
979 - if (
980 - __DEV__ &&
981 - enableDoubleInvokingEffects &&
982 - (workInProgress.mode & (BlockingMode | ConcurrentMode)) !== NoMode
983 - ) {
984 - workInProgress.flags |= MountLayoutDev | Update;
985 - } else {
986 - workInProgress.flags |= Update;
987 - }
963 + workInProgress.flags |= Update;
964 }
965 return false;
966 }
@@ -1027,29 +1003,13 @@ function resumeMountClassInstance(
1003 }
1004 }
1005 if (typeof instance.componentDidMount === 'function') {
1030 - if (
1031 - __DEV__ &&
1032 - enableDoubleInvokingEffects &&
1033 - (workInProgress.mode & (BlockingMode | ConcurrentMode)) !== NoMode
1034 - ) {
1035 - workInProgress.flags |= MountLayoutDev | Update;
1036 - } else {
1037 - workInProgress.flags |= Update;
1038 - }
1006 + workInProgress.flags |= Update;
1007 }
1008 } else {
1009 // If an update was already in progress, we should schedule an Update
1010 // effect even though we're bailing out, so that cWU/cDU are called.
1011 if (typeof instance.componentDidMount === 'function') {
1044 - if (
1045 - __DEV__ &&
1046 - enableDoubleInvokingEffects &&
1047 - (workInProgress.mode & (BlockingMode | ConcurrentMode)) !== NoMode
1048 - ) {
1049 - workInProgress.flags |= MountLayoutDev | Update;
1050 - } else {
1051 - workInProgress.flags |= Update;
1052 - }
1012 + workInProgress.flags |= Update;
1013 }
1014
1015 // If shouldComponentUpdate returned false, we should still update the
packages/react-reconciler/src/ReactFiberCommitWork.new.js
+536 -1921
@@ -17,14 +17,13 @@ import type {
17 } from './ReactFiberHostConfig';
18 import type {Fiber} from './ReactInternalTypes';
19 import type {FiberRoot} from './ReactInternalTypes';
20 +import type {Lanes} from './ReactFiberLane';
21 import type {SuspenseState} from './ReactFiberSuspenseComponent.new';
22 import type {UpdateQueue} from './ReactUpdateQueue.new';
23 import type {FunctionComponentUpdateQueue} from './ReactFiberHooks.new';
24 import type {Wakeable} from 'shared/ReactTypes';
25 import type {ReactPriorityLevel} from './ReactInternalTypes';
26 import type {OffscreenState} from './ReactFiberOffscreenComponent';
26 -import type {HookFlags} from './ReactHookEffectTags';
27 -import type {Flags} from './ReactFiberFlags';
27
28 import {unstable_wrap as Schedule_tracing_wrap} from 'scheduler/tracing';
29 import {
@@ -36,8 +35,6 @@ import {
35 enableFundamentalAPI,
36 enableSuspenseCallback,
37 enableScopeAPI,
39 - enableDoubleInvokingEffects,
40 - enableRecursiveCommitTraversal,
38 } from 'shared/ReactFeatureFlags';
39 import {
40 FunctionComponent,
@@ -69,29 +66,11 @@ import {
66 ContentReset,
67 Placement,
68 Snapshot,
72 - Visibility,
69 Update,
74 - Callback,
75 - Ref,
76 - PlacementAndUpdate,
77 - Hydrating,
78 - HydratingAndUpdate,
79 - Passive,
80 - PassiveStatic,
81 - BeforeMutationMask,
82 - MutationMask,
83 - LayoutMask,
84 - PassiveMask,
85 - MountLayoutDev,
86 - MountPassiveDev,
70 } from './ReactFiberFlags';
71 import getComponentName from 'shared/getComponentName';
72 import invariant from 'shared/invariant';
90 -import {
91 - current as currentDebugFiberInDEV,
92 - resetCurrentFiber as resetCurrentDebugFiberInDEV,
93 - setCurrentFiber as setCurrentDebugFiberInDEV,
94 -} from './ReactCurrentFiber';
73 +
74 import {onCommitUnmount} from './ReactFiberDevToolsHook.new';
75 import {resolveDefaultProps} from './ReactFiberLazyComponent.new';
76 import {
@@ -99,23 +78,14 @@ import {
78 getCommitTime,
79 recordLayoutEffectDuration,
80 startLayoutEffectTimer,
102 - recordPassiveEffectDuration,
103 - startPassiveEffectTimer,
81 } from './ReactProfilerTimer.new';
105 -import {
106 - NoMode,
107 - BlockingMode,
108 - ConcurrentMode,
109 - ProfileMode,
110 -} from './ReactTypeOfMode';
82 +import {ProfileMode} from './ReactTypeOfMode';
83 import {commitUpdateQueue} from './ReactUpdateQueue.new';
84 import {
85 getPublicInstance,
86 supportsMutation,
87 supportsPersistence,
88 supportsHydration,
117 - prepareForCommit,
118 - beforeActiveInstanceBlur,
89 commitMount,
90 commitUpdate,
91 resetTextContent,
@@ -145,6 +115,9 @@ import {
115 captureCommitPhaseError,
116 resolveRetryWakeable,
117 markCommitTimeOfFallback,
118 + enqueuePendingPassiveHookEffectMount,
119 + enqueuePendingPassiveHookEffectUnmount,
120 + enqueuePendingPassiveProfilerEffect,
121 } from './ReactFiberWorkLoop.new';
122 import {
123 NoFlags as NoHookEffect,
@@ -153,12 +126,6 @@ import {
126 Passive as HookPassive,
127 } from './ReactHookEffectTags';
128 import {didWarnAboutReassigningProps} from './ReactFiberBeginWork.new';
156 -import {doesFiberContain} from './ReactFiberTreeReflection';
157 -
158 -let nextEffect: Fiber | null = null;
159 -
160 -// Used to avoid traversing the return path to find the nearest Profiler ancestor during commit.
161 -let nearestProfilerOnStack: Fiber | null = null;
129
130 let didWarnAboutUndefinedSnapshotBeforeUpdate: Set<mixed> | null = null;
131 if (__DEV__) {
@@ -187,7 +154,7 @@ const callComponentWillUnmountWithTimer = function(current, instance) {
154 };
155
156 // Capture errors so they don't interrupt unmounting.
190 -function safelyCallComponentWillUnmount(current, instance) {
157 +function safelyCallComponentWillUnmount(current: Fiber, instance: any) {
158 if (__DEV__) {
159 invokeGuardedCallback(
160 null,
@@ -256,7 +223,7 @@ function safelyDetachRef(current: Fiber) {
223 }
224 }
225
259 -export function safelyCallDestroy(current: Fiber, destroy: () => void) {
226 +function safelyCallDestroy(current: Fiber, destroy: () => void) {
227 if (__DEV__) {
228 invokeGuardedCallback(null, destroy, null);
229 if (hasCaughtError()) {
@@ -272,19 +239,110 @@ export function safelyCallDestroy(current: Fiber, destroy: () => void) {
239 }
240 }
241
275 -function commitHookEffectListUnmount(flags: HookFlags, finishedWork: Fiber) {
242 +function commitBeforeMutationLifeCycles(
243 + current: Fiber | null,
244 + finishedWork: Fiber,
245 +): void {
246 + switch (finishedWork.tag) {
247 + case FunctionComponent:
248 + case ForwardRef:
249 + case SimpleMemoComponent: {
250 + return;
251 + }
252 + case ClassComponent: {
253 + if (finishedWork.flags & Snapshot) {
254 + if (current !== null) {
255 + const prevProps = current.memoizedProps;
256 + const prevState = current.memoizedState;
257 + const instance = finishedWork.stateNode;
258 + // We could update instance props and state here,
259 + // but instead we rely on them being set during last render.
260 + // TODO: revisit this when we implement resuming.
261 + if (__DEV__) {
262 + if (
263 + finishedWork.type === finishedWork.elementType &&
264 + !didWarnAboutReassigningProps
265 + ) {
266 + if (instance.props !== finishedWork.memoizedProps) {
267 + console.error(
268 + 'Expected %s props to match memoized props before ' +
269 + 'getSnapshotBeforeUpdate. ' +
270 + 'This might either be because of a bug in React, or because ' +
271 + 'a component reassigns its own `this.props`. ' +
272 + 'Please file an issue.',
273 + getComponentName(finishedWork.type) || 'instance',
274 + );
275 + }
276 + if (instance.state !== finishedWork.memoizedState) {
277 + console.error(
278 + 'Expected %s state to match memoized state before ' +
279 + 'getSnapshotBeforeUpdate. ' +
280 + 'This might either be because of a bug in React, or because ' +
281 + 'a component reassigns its own `this.state`. ' +
282 + 'Please file an issue.',
283 + getComponentName(finishedWork.type) || 'instance',
284 + );
285 + }
286 + }
287 + }
288 + const snapshot = instance.getSnapshotBeforeUpdate(
289 + finishedWork.elementType === finishedWork.type
290 + ? prevProps
291 + : resolveDefaultProps(finishedWork.type, prevProps),
292 + prevState,
293 + );
294 + if (__DEV__) {
295 + const didWarnSet = ((didWarnAboutUndefinedSnapshotBeforeUpdate: any): Set<mixed>);
296 + if (snapshot === undefined && !didWarnSet.has(finishedWork.type)) {
297 + didWarnSet.add(finishedWork.type);
298 + console.error(
299 + '%s.getSnapshotBeforeUpdate(): A snapshot value (or null) ' +
300 + 'must be returned. You have returned undefined.',
301 + getComponentName(finishedWork.type),
302 + );
303 + }
304 + }
305 + instance.__reactInternalSnapshotBeforeUpdate = snapshot;
306 + }
307 + }
308 + return;
309 + }
310 + case HostRoot: {
311 + if (supportsMutation) {
312 + if (finishedWork.flags & Snapshot) {
313 + const root = finishedWork.stateNode;
314 + clearContainer(root.containerInfo);
315 + }
316 + }
317 + return;
318 + }
319 + case HostComponent:
320 + case HostText:
321 + case HostPortal:
322 + case IncompleteClassComponent:
323 + // Nothing to do for these component types
324 + return;
325 + }
326 + invariant(
327 + false,
328 + 'This unit of work tag should not have side-effects. This error is ' +
329 + 'likely caused by a bug in React. Please file an issue.',
330 + );
331 +}
332 +
333 +function commitHookEffectListUnmount(tag: number, finishedWork: Fiber) {
334 const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
335 const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
336 if (lastEffect !== null) {
337 const firstEffect = lastEffect.next;
338 let effect = firstEffect;
339 do {
282 - if ((effect.tag & flags) === flags) {
340 + if ((effect.tag & tag) === tag) {
341 // Unmount
342 const destroy = effect.destroy;
343 effect.destroy = undefined;
344 if (destroy !== undefined) {
287 - safelyCallDestroy(finishedWork, destroy);
345 + destroy();
346 }
347 }
348 effect = effect.next;
@@ -292,14 +350,14 @@ function commitHookEffectListUnmount(flags: HookFlags, finishedWork: Fiber) {
350 }
351 }
352
295 -function commitHookEffectListMount(flags: HookFlags, finishedWork: Fiber) {
353 +function commitHookEffectListMount(tag: number, finishedWork: Fiber) {
354 const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
355 const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
356 if (lastEffect !== null) {
357 const firstEffect = lastEffect.next;
358 let effect = firstEffect;
359 do {
302 - if ((effect.tag & flags) === flags) {
360 + if ((effect.tag & tag) === tag) {
361 // Mount
362 const create = effect.create;
363 effect.destroy = create();
@@ -342,1657 +400,450 @@ function commitHookEffectListMount(flags: HookFlags, finishedWork: Fiber) {
400 }
401 }
402
345 -function commitProfilerPassiveEffect(
403 +function schedulePassiveEffects(finishedWork: Fiber) {
404 + const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
405 + const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
406 + if (lastEffect !== null) {
407 + const firstEffect = lastEffect.next;
408 + let effect = firstEffect;
409 + do {
410 + const {next, tag} = effect;
411 + if (
412 + (tag & HookPassive) !== NoHookEffect &&
413 + (tag & HookHasEffect) !== NoHookEffect
414 + ) {
415 + enqueuePendingPassiveHookEffectUnmount(finishedWork, effect);
416 + enqueuePendingPassiveHookEffectMount(finishedWork, effect);
417 + }
418 + effect = next;
419 + } while (effect !== firstEffect);
420 + }
421 +}
422 +
423 +export function commitPassiveEffectDurations(
424 finishedRoot: FiberRoot,
425 finishedWork: Fiber,
426 ): void {
427 if (enableProfilerTimer && enableProfilerCommitHooks) {
350 - switch (finishedWork.tag) {
351 - case Profiler: {
352 - const {passiveEffectDuration} = finishedWork.stateNode;
353 - const {id, onPostCommit} = finishedWork.memoizedProps;
354 -
355 - // This value will still reflect the previous commit phase.
356 - // It does not get reset until the start of the next commit phase.
357 - const commitTime = getCommitTime();
428 + // Only Profilers with work in their subtree will have an Update effect scheduled.
429 + if ((finishedWork.flags & Update) !== NoFlags) {
430 + switch (finishedWork.tag) {
431 + case Profiler: {
432 + const {passiveEffectDuration} = finishedWork.stateNode;
433 + const {id, onPostCommit} = finishedWork.memoizedProps;
434 +
435 + // This value will still reflect the previous commit phase.
436 + // It does not get reset until the start of the next commit phase.
437 + const commitTime = getCommitTime();
438 +
439 + let phase = finishedWork.alternate === null ? 'mount' : 'update';
440 + if (enableProfilerNestedUpdatePhase) {
441 + if (isCurrentUpdateNested()) {
442 + phase = 'nested-update';
443 + }
444 + }
445
359 - let phase = finishedWork.alternate === null ? 'mount' : 'update';
360 - if (enableProfilerNestedUpdatePhase) {
361 - if (isCurrentUpdateNested()) {
362 - phase = 'nested-update';
446 + if (typeof onPostCommit === 'function') {
447 + if (enableSchedulerTracing) {
448 + onPostCommit(
449 + id,
450 + phase,
451 + passiveEffectDuration,
452 + commitTime,
453 + finishedRoot.memoizedInteractions,
454 + );
455 + } else {
456 + onPostCommit(id, phase, passiveEffectDuration, commitTime);
457 + }
458 }
364 - }
459
366 - if (typeof onPostCommit === 'function') {
367 - if (enableSchedulerTracing) {
368 - onPostCommit(
369 - id,
370 - phase,
371 - passiveEffectDuration,
372 - commitTime,
373 - finishedRoot.memoizedInteractions,
374 - );
375 - } else {
376 - onPostCommit(id, phase, passiveEffectDuration, commitTime);
460 + // Bubble times to the next nearest ancestor Profiler.
461 + // After we process that Profiler, we'll bubble further up.
462 + let parentFiber = finishedWork.return;
463 + while (parentFiber !== null) {
464 + if (parentFiber.tag === Profiler) {
465 + const parentStateNode = parentFiber.stateNode;
466 + parentStateNode.passiveEffectDuration += passiveEffectDuration;
467 + break;
468 + }
469 + parentFiber = parentFiber.return;
470 }
471 + break;
472 }
379 - break;
380 - }
381 - default:
382 - break;
383 - }
384 - }
385 -}
386 -
387 -let focusedInstanceHandle: null | Fiber = null;
388 -let shouldFireAfterActiveInstanceBlur: boolean = false;
389 -
390 -export function commitBeforeMutationEffects(
391 - root: FiberRoot,
392 - firstChild: Fiber,
393 -) {
394 - focusedInstanceHandle = prepareForCommit(root.containerInfo);
395 -
396 - if (enableRecursiveCommitTraversal) {
397 - recursivelyCommitBeforeMutationEffects(firstChild);
398 - } else {
399 - nextEffect = firstChild;
400 - iterativelyCommitBeforeMutationEffects_begin();
401 - }
402 -
403 - // We no longer need to track the active instance fiber
404 - const shouldFire = shouldFireAfterActiveInstanceBlur;
405 - shouldFireAfterActiveInstanceBlur = false;
406 - focusedInstanceHandle = null;
407 -
408 - return shouldFire;
409 -}
410 -
411 -function recursivelyCommitBeforeMutationEffects(firstChild: Fiber) {
412 - let fiber = firstChild;
413 - while (fiber !== null) {
414 - // TODO: Should wrap this in flags check, too, as optimization
415 - if (fiber.deletions !== null) {
416 - commitBeforeMutationEffectsDeletions(fiber.deletions);
417 - }
418 -
419 - const child = fiber.child;
420 - if (fiber.subtreeFlags & BeforeMutationMask && child !== null) {
421 - recursivelyCommitBeforeMutationEffects(child);
422 - }
423 -
424 - if (__DEV__) {
425 - setCurrentDebugFiberInDEV(fiber);
426 - invokeGuardedCallback(
427 - null,
428 - commitBeforeMutationEffectsOnFiber,
429 - null,
430 - fiber,
431 - );
432 - if (hasCaughtError()) {
433 - const error = clearCaughtError();
434 - captureCommitPhaseError(fiber, error);
435 - }
436 - resetCurrentDebugFiberInDEV();
437 - } else {
438 - try {
439 - commitBeforeMutationEffectsOnFiber(fiber);
440 - } catch (error) {
441 - captureCommitPhaseError(fiber, error);
473 + default:
474 + break;
475 }
476 }
444 - fiber = fiber.sibling;
445 - }
446 -}
447 -
448 -function iterativelyCommitBeforeMutationEffects_begin() {
449 - while (nextEffect !== null) {
450 - const fiber = nextEffect;
451 -
452 - // TODO: Should wrap this in flags check, too, as optimization
453 - const deletions = fiber.deletions;
454 - if (deletions !== null) {
455 - commitBeforeMutationEffectsDeletions(deletions);
456 - }
457 -
458 - const child = fiber.child;
459 - if (
460 - (fiber.subtreeFlags & BeforeMutationMask) !== NoFlags &&
461 - child !== null
462 - ) {
463 - warnIfWrongReturnPointer(fiber, child);
464 - nextEffect = child;
465 - } else {
466 - iterativelyCommitBeforeMutationEffects_complete();
467 - }
477 }
478 }
479
471 -function iterativelyCommitBeforeMutationEffects_complete() {
472 - while (nextEffect !== null) {
473 - const fiber = nextEffect;
474 - if (__DEV__) {
475 - setCurrentDebugFiberInDEV(fiber);
476 - invokeGuardedCallback(
477 - null,
478 - commitBeforeMutationEffectsOnFiber,
479 - null,
480 - fiber,
481 - );
482 - if (hasCaughtError()) {
483 - const error = clearCaughtError();
484 - captureCommitPhaseError(fiber, error);
485 - }
486 - resetCurrentDebugFiberInDEV();
487 - } else {
488 - try {
489 - commitBeforeMutationEffectsOnFiber(fiber);
490 - } catch (error) {
491 - captureCommitPhaseError(fiber, error);
480 +function commitLifeCycles(
481 + finishedRoot: FiberRoot,
482 + current: Fiber | null,
483 + finishedWork: Fiber,
484 + committedLanes: Lanes,
485 +): void {
486 + switch (finishedWork.tag) {
487 + case FunctionComponent:
488 + case ForwardRef:
489 + case SimpleMemoComponent: {
490 + // At this point layout effects have already been destroyed (during mutation phase).
491 + // This is done to prevent sibling component effects from interfering with each other,
492 + // e.g. a destroy function in one component should never override a ref set
493 + // by a create function in another component during the same commit.
494 + if (
495 + enableProfilerTimer &&
496 + enableProfilerCommitHooks &&
497 + finishedWork.mode & ProfileMode
498 + ) {
499 + try {
500 + startLayoutEffectTimer();
501 + commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);
502 + } finally {
503 + recordLayoutEffectDuration(finishedWork);
504 + }
505 + } else {
506 + commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);
507 }
493 - }
508
495 - const sibling = fiber.sibling;
496 - if (sibling !== null) {
497 - warnIfWrongReturnPointer(fiber.return, sibling);
498 - nextEffect = sibling;
509 + schedulePassiveEffects(finishedWork);
510 return;
511 }
501 -
502 - nextEffect = fiber.return;
503 - }
504 -}
505 -
506 -function commitBeforeMutationEffectsOnFiber(finishedWork: Fiber) {
507 - const current = finishedWork.alternate;
508 - const flags = finishedWork.flags;
509 -
510 - if (!shouldFireAfterActiveInstanceBlur && focusedInstanceHandle !== null) {
511 - // Check to see if the focused element was inside of a hidden (Suspense) subtree.
512 - if (
513 - // TODO: Can optimize this further with separate Hide and Show flags. We
514 - // only care about Hide here.
515 - (flags & Visibility) !== NoFlags &&
516 - finishedWork.tag === SuspenseComponent &&
517 - isSuspenseBoundaryBeingHidden(current, finishedWork) &&
518 - doesFiberContain(finishedWork, focusedInstanceHandle)
519 - ) {
520 - shouldFireAfterActiveInstanceBlur = true;
521 - beforeActiveInstanceBlur(finishedWork);
522 - }
523 - }
524 -
525 - if ((flags & Snapshot) !== NoFlags) {
526 - setCurrentDebugFiberInDEV(finishedWork);
527 - switch (finishedWork.tag) {
528 - case FunctionComponent:
529 - case ForwardRef:
530 - case SimpleMemoComponent: {
531 - break;
532 - }
533 - case ClassComponent: {
534 - if (finishedWork.flags & Snapshot) {
535 - if (current !== null) {
536 - const prevProps = current.memoizedProps;
537 - const prevState = current.memoizedState;
538 - const instance = finishedWork.stateNode;
539 - // We could update instance props and state here,
540 - // but instead we rely on them being set during last render.
541 - // TODO: revisit this when we implement resuming.
542 - if (__DEV__) {
543 - if (
544 - finishedWork.type === finishedWork.elementType &&
545 - !didWarnAboutReassigningProps
546 - ) {
547 - if (instance.props !== finishedWork.memoizedProps) {
548 - console.error(
549 - 'Expected %s props to match memoized props before ' +
550 - 'getSnapshotBeforeUpdate. ' +
551 - 'This might either be because of a bug in React, or because ' +
552 - 'a component reassigns its own `this.props`. ' +
553 - 'Please file an issue.',
554 - getComponentName(finishedWork.type) || 'instance',
555 - );
556 - }
557 - if (instance.state !== finishedWork.memoizedState) {
558 - console.error(
559 - 'Expected %s state to match memoized state before ' +
560 - 'getSnapshotBeforeUpdate. ' +
561 - 'This might either be because of a bug in React, or because ' +
562 - 'a component reassigns its own `this.state`. ' +
563 - 'Please file an issue.',
564 - getComponentName(finishedWork.type) || 'instance',
565 - );
566 - }
512 + case ClassComponent: {
513 + const instance = finishedWork.stateNode;
514 + if (finishedWork.flags & Update) {
515 + if (current === null) {
516 + // We could update instance props and state here,
517 + // but instead we rely on them being set during last render.
518 + // TODO: revisit this when we implement resuming.
519 + if (__DEV__) {
520 + if (
521 + finishedWork.type === finishedWork.elementType &&
522 + !didWarnAboutReassigningProps
523 + ) {
524 + if (instance.props !== finishedWork.memoizedProps) {
525 + console.error(
526 + 'Expected %s props to match memoized props before ' +
527 + 'componentDidMount. ' +
528 + 'This might either be because of a bug in React, or because ' +
529 + 'a component reassigns its own `this.props`. ' +
530 + 'Please file an issue.',
531 + getComponentName(finishedWork.type) || 'instance',
532 + );
533 + }
534 + if (instance.state !== finishedWork.memoizedState) {
535 + console.error(
536 + 'Expected %s state to match memoized state before ' +
537 + 'componentDidMount. ' +
538 + 'This might either be because of a bug in React, or because ' +
539 + 'a component reassigns its own `this.state`. ' +
540 + 'Please file an issue.',
541 + getComponentName(finishedWork.type) || 'instance',
542 + );
543 }
544 }
569 - const snapshot = instance.getSnapshotBeforeUpdate(
570 - finishedWork.elementType === finishedWork.type
571 - ? prevProps
572 - : resolveDefaultProps(finishedWork.type, prevProps),
573 - prevState,
574 - );
575 - if (__DEV__) {
576 - const didWarnSet = ((didWarnAboutUndefinedSnapshotBeforeUpdate: any): Set<mixed>);
577 - if (
578 - snapshot === undefined &&
579 - !didWarnSet.has(finishedWork.type)
580 - ) {
581 - didWarnSet.add(finishedWork.type);
545 + }
546 + if (
547 + enableProfilerTimer &&
548 + enableProfilerCommitHooks &&
549 + finishedWork.mode & ProfileMode
550 + ) {
551 + try {
552 + startLayoutEffectTimer();
553 + instance.componentDidMount();
554 + } finally {
555 + recordLayoutEffectDuration(finishedWork);
556 + }
557 + } else {
558 + instance.componentDidMount();
559 + }
560 + } else {
561 + const prevProps =
562 + finishedWork.elementType === finishedWork.type
563 + ? current.memoizedProps
564 + : resolveDefaultProps(finishedWork.type, current.memoizedProps);
565 + const prevState = current.memoizedState;
566 + // We could update instance props and state here,
567 + // but instead we rely on them being set during last render.
568 + // TODO: revisit this when we implement resuming.
569 + if (__DEV__) {
570 + if (
571 + finishedWork.type === finishedWork.elementType &&
572 + !didWarnAboutReassigningProps
573 + ) {
574 + if (instance.props !== finishedWork.memoizedProps) {
575 + console.error(
576 + 'Expected %s props to match memoized props before ' +
577 + 'componentDidUpdate. ' +
578 + 'This might either be because of a bug in React, or because ' +
579 + 'a component reassigns its own `this.props`. ' +
580 + 'Please file an issue.',
581 + getComponentName(finishedWork.type) || 'instance',
582 + );
583 + }
584 + if (instance.state !== finishedWork.memoizedState) {
585 console.error(
583 - '%s.getSnapshotBeforeUpdate(): A snapshot value (or null) ' +
584 - 'must be returned. You have returned undefined.',
585 - getComponentName(finishedWork.type),
586 + 'Expected %s state to match memoized state before ' +
587 + 'componentDidUpdate. ' +
588 + 'This might either be because of a bug in React, or because ' +
589 + 'a component reassigns its own `this.state`. ' +
590 + 'Please file an issue.',
591 + getComponentName(finishedWork.type) || 'instance',
592 );
593 }
594 }
589 - instance.__reactInternalSnapshotBeforeUpdate = snapshot;
595 + }
596 + if (
597 + enableProfilerTimer &&
598 + enableProfilerCommitHooks &&
599 + finishedWork.mode & ProfileMode
600 + ) {
601 + try {
602 + startLayoutEffectTimer();
603 + instance.componentDidUpdate(
604 + prevProps,
605 + prevState,
606 + instance.__reactInternalSnapshotBeforeUpdate,
607 + );
608 + } finally {
609 + recordLayoutEffectDuration(finishedWork);
610 + }
611 + } else {
612 + instance.componentDidUpdate(
613 + prevProps,
614 + prevState,
615 + instance.__reactInternalSnapshotBeforeUpdate,
616 + );
617 }
618 }
592 - break;
619 }
594 - case HostRoot: {
595 - if (supportsMutation) {
596 - if (finishedWork.flags & Snapshot) {
597 - const root = finishedWork.stateNode;
598 - clearContainer(root.containerInfo);
620 +
621 + // TODO: I think this is now always non-null by the time it reaches the
622 + // commit phase. Consider removing the type check.
623 + const updateQueue: UpdateQueue<
624 + *,
625 + > | null = (finishedWork.updateQueue: any);
626 + if (updateQueue !== null) {
627 + if (__DEV__) {
628 + if (
629 + finishedWork.type === finishedWork.elementType &&
630 + !didWarnAboutReassigningProps
631 + ) {
632 + if (instance.props !== finishedWork.memoizedProps) {
633 + console.error(
634 + 'Expected %s props to match memoized props before ' +
635 + 'processing the update queue. ' +
636 + 'This might either be because of a bug in React, or because ' +
637 + 'a component reassigns its own `this.props`. ' +
638 + 'Please file an issue.',
639 + getComponentName(finishedWork.type) || 'instance',
640 + );
641 + }
642 + if (instance.state !== finishedWork.memoizedState) {
643 + console.error(
644 + 'Expected %s state to match memoized state before ' +
645 + 'processing the update queue. ' +
646 + 'This might either be because of a bug in React, or because ' +
647 + 'a component reassigns its own `this.state`. ' +
648 + 'Please file an issue.',
649 + getComponentName(finishedWork.type) || 'instance',
650 + );
651 + }
652 }
653 }
601 - break;
654 + // We could update instance props and state here,
655 + // but instead we rely on them being set during last render.
656 + // TODO: revisit this when we implement resuming.
657 + commitUpdateQueue(finishedWork, updateQueue, instance);
658 }
603 - case HostComponent:
604 - case HostText:
605 - case HostPortal:
606 - case IncompleteClassComponent:
607 - // Nothing to do for these component types
608 - break;
609 - default:
610 - invariant(
611 - false,
612 - 'This unit of work tag should not have side-effects. This error is ' +
613 - 'likely caused by a bug in React. Please file an issue.',
614 - );
615 - }
616 - resetCurrentDebugFiberInDEV();
617 - }
618 -}
619 -
620 -function commitBeforeMutationEffectsDeletions(deletions: Array<Fiber>) {
621 - for (let i = 0; i < deletions.length; i++) {
622 - const fiber = deletions[i];
623 -
624 - // TODO (effects) It would be nice to avoid calling doesFiberContain()
625 - // Maybe we can repurpose one of the subtreeFlags positions for this instead?
626 - // Use it to store which part of the tree the focused instance is in?
627 - // This assumes we can safely determine that instance during the "render" phase.
628 -
629 - if (doesFiberContain(fiber, ((focusedInstanceHandle: any): Fiber))) {
630 - shouldFireAfterActiveInstanceBlur = true;
631 - beforeActiveInstanceBlur(fiber);
659 + return;
660 }
633 - }
634 -}
635 -
636 -export function commitMutationEffects(
637 - firstChild: Fiber,
638 - root: FiberRoot,
639 - renderPriorityLevel: ReactPriorityLevel,
640 -) {
641 - if (enableRecursiveCommitTraversal) {
642 - recursivelyCommitMutationEffects(firstChild, root, renderPriorityLevel);
643 - } else {
644 - nextEffect = firstChild;
645 - iterativelyCommitMutationEffects_begin(root, renderPriorityLevel);
646 - }
647 -}
648 -
649 -function recursivelyCommitMutationEffects(
650 - firstChild: Fiber,
651 - root: FiberRoot,
652 - renderPriorityLevel: ReactPriorityLevel,
653 -) {
654 - let fiber = firstChild;
655 - while (fiber !== null) {
656 - const deletions = fiber.deletions;
657 - if (deletions !== null) {
658 - commitMutationEffectsDeletions(deletions, root, renderPriorityLevel);
659 - }
660 -
661 - if (fiber.child !== null) {
662 - const mutationFlags = fiber.subtreeFlags & MutationMask;
663 - if (mutationFlags !== NoFlags) {
664 - recursivelyCommitMutationEffects(
665 - fiber.child,
666 - root,
667 - renderPriorityLevel,
668 - );
661 + case HostRoot: {
662 + // TODO: I think this is now always non-null by the time it reaches the
663 + // commit phase. Consider removing the type check.
664 + const updateQueue: UpdateQueue<
665 + *,
666 + > | null = (finishedWork.updateQueue: any);
667 + if (updateQueue !== null) {
668 + let instance = null;
669 + if (finishedWork.child !== null) {
670 + switch (finishedWork.child.tag) {
671 + case HostComponent:
672 + instance = getPublicInstance(finishedWork.child.stateNode);
673 + break;
674 + case ClassComponent:
675 + instance = finishedWork.child.stateNode;
676 + break;
677 + }
678 + }
679 + commitUpdateQueue(finishedWork, updateQueue, instance);
680 }
681 + return;
682 }
683 + case HostComponent: {
684 + const instance: Instance = finishedWork.stateNode;
685
672 - if (__DEV__) {
673 - setCurrentDebugFiberInDEV(fiber);
674 - invokeGuardedCallback(
675 - null,
676 - commitMutationEffectsOnFiber,
677 - null,
678 - fiber,
679 - root,
680 - renderPriorityLevel,
681 - );
682 - if (hasCaughtError()) {
683 - const error = clearCaughtError();
684 - captureCommitPhaseError(fiber, error);
686 + // Renderers may schedule work to be done after host components are mounted
687 + // (eg DOM renderer may schedule auto-focus for inputs and form controls).
688 + // These effects should only be committed when components are first mounted,
689 + // aka when there is no current/alternate.
690 + if (current === null && finishedWork.flags & Update) {
691 + const type = finishedWork.type;
692 + const props = finishedWork.memoizedProps;
693 + commitMount(instance, type, props, finishedWork);
694 }
686 - resetCurrentDebugFiberInDEV();
687 - } else {
688 - try {
689 - commitMutationEffectsOnFiber(fiber, root, renderPriorityLevel);
690 - } catch (error) {
691 - captureCommitPhaseError(fiber, error);
692 - }
693 - }
694 - fiber = fiber.sibling;
695 - }
696 -}
697 -
698 -function iterativelyCommitMutationEffects_begin(
699 - root: FiberRoot,
700 - renderPriorityLevel: ReactPriorityLevel,
701 -) {
702 - while (nextEffect !== null) {
703 - const fiber = nextEffect;
704 -
705 - // TODO: Should wrap this in flags check, too, as optimization
706 - const deletions = fiber.deletions;
707 - if (deletions !== null) {
708 - commitMutationEffectsDeletions(deletions, root, renderPriorityLevel);
709 - }
710 -
711 - const child = fiber.child;
712 - if ((fiber.subtreeFlags & MutationMask) !== NoFlags && child !== null) {
713 - warnIfWrongReturnPointer(fiber, child);
714 - nextEffect = child;
715 - } else {
716 - iterativelyCommitMutationEffects_complete(root, renderPriorityLevel);
717 - }
718 - }
719 -}
720 -
721 -function iterativelyCommitMutationEffects_complete(
722 - root: FiberRoot,
723 - renderPriorityLevel: ReactPriorityLevel,
724 -) {
725 - while (nextEffect !== null) {
726 - const fiber = nextEffect;
727 - if (__DEV__) {
728 - setCurrentDebugFiberInDEV(fiber);
729 - invokeGuardedCallback(
730 - null,
731 - commitMutationEffectsOnFiber,
732 - null,
733 - fiber,
734 - root,
735 - renderPriorityLevel,
736 - );
737 - if (hasCaughtError()) {
738 - const error = clearCaughtError();
739 - captureCommitPhaseError(fiber, error);
740 - }
741 - resetCurrentDebugFiberInDEV();
742 - } else {
743 - try {
744 - commitMutationEffectsOnFiber(fiber, root, renderPriorityLevel);
745 - } catch (error) {
746 - captureCommitPhaseError(fiber, error);
747 - }
748 - }
749 -
750 - const sibling = fiber.sibling;
751 - if (sibling !== null) {
752 - warnIfWrongReturnPointer(fiber.return, sibling);
753 - nextEffect = sibling;
754 - return;
755 - }
756 -
757 - nextEffect = fiber.return;
758 - }
759 -}
760 -
761 -function commitMutationEffectsOnFiber(
762 - fiber: Fiber,
763 - root: FiberRoot,
764 - renderPriorityLevel,
765 -) {
766 - const flags = fiber.flags;
767 - if (flags & ContentReset) {
768 - commitResetTextContent(fiber);
769 - }
770 -
771 - if (flags & Ref) {
772 - const current = fiber.alternate;
773 - if (current !== null) {
774 - commitDetachRef(current);
775 - }
776 - if (enableScopeAPI) {
777 - // TODO: This is a temporary solution that allowed us to transition away from React Flare on www.
778 - if (fiber.tag === ScopeComponent) {
779 - commitAttachRef(fiber);
780 - }
781 - }
782 - }
783 -
784 - // The following switch statement is only concerned about placement,
785 - // updates, and deletions. To avoid needing to add a case for every possible
786 - // bitmap value, we remove the secondary effects from the effect tag and
787 - // switch on that value.
788 - const primaryFlags = flags & (Placement | Update | Hydrating);
789 - switch (primaryFlags) {
790 - case Placement: {
791 - commitPlacement(fiber);
792 - // Clear the "placement" from effect tag so that we know that this is
793 - // inserted, before any life-cycles like componentDidMount gets called.
794 - // TODO: findDOMNode doesn't rely on this any more but isMounted does
795 - // and isMounted is deprecated anyway so we should be able to kill this.
796 - fiber.flags &= ~Placement;
797 - break;
798 - }
799 - case PlacementAndUpdate: {
800 - // Placement
801 - commitPlacement(fiber);
802 - // Clear the "placement" from effect tag so that we know that this is
803 - // inserted, before any life-cycles like componentDidMount gets called.
804 - fiber.flags &= ~Placement;
805 -
806 - // Update
807 - const current = fiber.alternate;
808 - commitWork(current, fiber);
809 - break;
810 - }
811 - case Hydrating: {
812 - fiber.flags &= ~Hydrating;
813 - break;
814 - }
815 - case HydratingAndUpdate: {
816 - fiber.flags &= ~Hydrating;
817 -
818 - // Update
819 - const current = fiber.alternate;
820 - commitWork(current, fiber);
821 - break;
822 - }
823 - case Update: {
824 - const current = fiber.alternate;
825 - commitWork(current, fiber);
826 - break;
827 - }
828 - }
829 -}
830 -
831 -function commitMutationEffectsDeletions(
832 - deletions: Array<Fiber>,
833 - root: FiberRoot,
834 - renderPriorityLevel,
835 -) {
836 - for (let i = 0; i < deletions.length; i++) {
837 - const childToDelete = deletions[i];
838 - if (__DEV__) {
839 - invokeGuardedCallback(
840 - null,
841 - commitDeletion,
842 - null,
843 - root,
844 - childToDelete,
845 - renderPriorityLevel,
846 - );
847 - if (hasCaughtError()) {
848 - const error = clearCaughtError();
849 - captureCommitPhaseError(childToDelete, error);
850 - }
851 - } else {
852 - try {
853 - commitDeletion(root, childToDelete, renderPriorityLevel);
854 - } catch (error) {
855 - captureCommitPhaseError(childToDelete, error);
856 - }
857 - }
858 - }
859 -}
860 -
861 -export function commitLayoutEffects(
862 - finishedWork: Fiber,
863 - finishedRoot: FiberRoot,
864 -) {
865 - if (enableRecursiveCommitTraversal) {
866 - if (__DEV__) {
867 - setCurrentDebugFiberInDEV(finishedWork);
868 - invokeGuardedCallback(
869 - null,
870 - recursivelyCommitLayoutEffects,
871 - null,
872 - finishedWork,
873 - finishedRoot,
874 - );
875 - if (hasCaughtError()) {
876 - const error = clearCaughtError();
877 - captureCommitPhaseError(finishedWork, error);
878 - }
879 - resetCurrentDebugFiberInDEV();
880 - } else {
881 - try {
882 - recursivelyCommitLayoutEffects(finishedWork, finishedRoot);
883 - } catch (error) {
884 - captureCommitPhaseError(finishedWork, error);
885 - }
886 - }
887 - } else {
888 - nextEffect = finishedWork;
889 - iterativelyCommitLayoutEffects_begin(finishedWork, finishedRoot);
890 - }
891 -}
892 -
893 -function recursivelyCommitLayoutEffects(
894 - finishedWork: Fiber,
895 - finishedRoot: FiberRoot,
896 -) {
897 - const {flags, tag} = finishedWork;
898 - switch (tag) {
899 - case Profiler: {
900 - let prevProfilerOnStack = null;
901 - if (enableProfilerTimer && enableProfilerCommitHooks) {
902 - prevProfilerOnStack = nearestProfilerOnStack;
903 - nearestProfilerOnStack = finishedWork;
904 - }
905 -
906 - let child = finishedWork.child;
907 - while (child !== null) {
908 - const primarySubtreeFlags = finishedWork.subtreeFlags & LayoutMask;
909 - if (primarySubtreeFlags !== NoFlags) {
910 - if (__DEV__) {
911 - const prevCurrentFiberInDEV = currentDebugFiberInDEV;
912 - setCurrentDebugFiberInDEV(child);
913 - invokeGuardedCallback(
914 - null,
915 - recursivelyCommitLayoutEffects,
916 - null,
917 - child,
918 - finishedRoot,
919 - );
920 - if (hasCaughtError()) {
921 - const error = clearCaughtError();
922 - captureCommitPhaseError(child, error);
923 - }
924 - if (prevCurrentFiberInDEV !== null) {
925 - setCurrentDebugFiberInDEV(prevCurrentFiberInDEV);
926 - } else {
927 - resetCurrentDebugFiberInDEV();
928 - }
929 - } else {
930 - try {
931 - recursivelyCommitLayoutEffects(child, finishedRoot);
932 - } catch (error) {
933 - captureCommitPhaseError(child, error);
934 - }
935 - }
936 - }
937 - child = child.sibling;
938 - }
939 -
940 - const primaryFlags = flags & (Update | Callback);
941 - if (primaryFlags !== NoFlags) {
942 - if (enableProfilerTimer) {
943 - if (__DEV__) {
944 - const prevCurrentFiberInDEV = currentDebugFiberInDEV;
945 - setCurrentDebugFiberInDEV(finishedWork);
946 - invokeGuardedCallback(
947 - null,
948 - commitLayoutEffectsForProfiler,
949 - null,
950 - finishedWork,
951 - finishedRoot,
952 - );
953 - if (hasCaughtError()) {
954 - const error = clearCaughtError();
955 - captureCommitPhaseError(finishedWork, error);
956 - }
957 - if (prevCurrentFiberInDEV !== null) {
958 - setCurrentDebugFiberInDEV(prevCurrentFiberInDEV);
959 - } else {
960 - resetCurrentDebugFiberInDEV();
961 - }
962 - } else {
963 - try {
964 - commitLayoutEffectsForProfiler(finishedWork, finishedRoot);
965 - } catch (error) {
966 - captureCommitPhaseError(finishedWork, error);
967 - }
968 - }
969 - }
970 - }
971 -
972 - if (enableProfilerTimer && enableProfilerCommitHooks) {
973 - // Propagate layout effect durations to the next nearest Profiler ancestor.
974 - // Do not reset these values until the next render so DevTools has a chance to read them first.
975 - if (prevProfilerOnStack !== null) {
976 - prevProfilerOnStack.stateNode.effectDuration +=
977 - finishedWork.stateNode.effectDuration;
978 - }
979 -
980 - nearestProfilerOnStack = prevProfilerOnStack;
981 - }
982 - break;
983 - }
984 -
985 - // case Offscreen: {
986 - // TODO: Fast path to invoke all nested layout effects when Offscren goes from hidden to visible.
987 - // break;
988 - // }
989 -
990 - default: {
991 - let child = finishedWork.child;
992 - while (child !== null) {
993 - const primarySubtreeFlags = finishedWork.subtreeFlags & LayoutMask;
994 - if (primarySubtreeFlags !== NoFlags) {
995 - if (__DEV__) {
996 - const prevCurrentFiberInDEV = currentDebugFiberInDEV;
997 - setCurrentDebugFiberInDEV(child);
998 - invokeGuardedCallback(
999 - null,
1000 - recursivelyCommitLayoutEffects,
1001 - null,
1002 - child,
1003 - finishedRoot,
1004 - );
1005 - if (hasCaughtError()) {
1006 - const error = clearCaughtError();
1007 - captureCommitPhaseError(child, error);
1008 - }
1009 - if (prevCurrentFiberInDEV !== null) {
1010 - setCurrentDebugFiberInDEV(prevCurrentFiberInDEV);
1011 - } else {
1012 - resetCurrentDebugFiberInDEV();
1013 - }
1014 - } else {
1015 - try {
1016 - recursivelyCommitLayoutEffects(child, finishedRoot);
1017 - } catch (error) {
1018 - captureCommitPhaseError(child, error);
1019 - }
1020 - }
1021 - }
1022 - child = child.sibling;
1023 - }
1024 -
1025 - const primaryFlags = flags & (Update | Callback);
1026 - if (primaryFlags !== NoFlags) {
1027 - switch (tag) {
1028 - case FunctionComponent:
1029 - case ForwardRef:
1030 - case SimpleMemoComponent: {
1031 - if (
1032 - enableProfilerTimer &&
1033 - enableProfilerCommitHooks &&
1034 - finishedWork.mode & ProfileMode
1035 - ) {
1036 - try {
1037 - startLayoutEffectTimer();
1038 - commitHookEffectListMount(
1039 - HookLayout | HookHasEffect,
1040 - finishedWork,
1041 - );
1042 - } finally {
1043 - recordLayoutEffectDuration(finishedWork);
1044 - }
1045 - } else {
1046 - commitHookEffectListMount(
1047 - HookLayout | HookHasEffect,
1048 - finishedWork,
1049 - );
1050 - }
1051 - break;
1052 - }
1053 - case ClassComponent: {
1054 - // NOTE: Layout effect durations are measured within this function.
1055 - commitLayoutEffectsForClassComponent(finishedWork);
1056 - break;
1057 - }
1058 - case HostRoot: {
1059 - commitLayoutEffectsForHostRoot(finishedWork);
1060 - break;
1061 - }
1062 - case HostComponent: {
1063 - commitLayoutEffectsForHostComponent(finishedWork);
1064 - break;
1065 - }
1066 - case SuspenseComponent: {
1067 - commitSuspenseHydrationCallbacks(finishedRoot, finishedWork);
1068 - break;
1069 - }
1070 - case FundamentalComponent:
1071 - case HostPortal:
1072 - case HostText:
1073 - case IncompleteClassComponent:
1074 - case LegacyHiddenComponent:
1075 - case OffscreenComponent:
1076 - case ScopeComponent:
1077 - case SuspenseListComponent: {
1078 - // We have no life-cycles associated with these component types.
1079 - break;
1080 - }
1081 - default: {
1082 - invariant(
1083 - false,
1084 - 'This unit of work tag should not have side-effects. This error is ' +
1085 - 'likely caused by a bug in React. Please file an issue.',
1086 - );
1087 - }
1088 - }
1089 - }
1090 -
1091 - if (enableScopeAPI) {
1092 - // TODO: This is a temporary solution that allowed us to transition away from React Flare on www.
1093 - if (flags & Ref && tag !== ScopeComponent) {
1094 - commitAttachRef(finishedWork);
1095 - }
1096 - } else {
1097 - if (flags & Ref) {
1098 - commitAttachRef(finishedWork);
1099 - }
1100 - }
1101 - break;
1102 - }
1103 - }
1104 -}
1105 -
1106 -function iterativelyCommitLayoutEffects_begin(
1107 - subtreeRoot: Fiber,
1108 - finishedRoot: FiberRoot,
1109 -) {
1110 - while (nextEffect !== null) {
1111 - const finishedWork: Fiber = nextEffect;
1112 - const firstChild = finishedWork.child;
1113 -
1114 - if (
1115 - (finishedWork.subtreeFlags & LayoutMask) !== NoFlags &&
1116 - firstChild !== null
1117 - ) {
1118 - if (
1119 - enableProfilerTimer &&
1120 - enableProfilerCommitHooks &&
1121 - finishedWork.tag === Profiler
1122 - ) {
1123 - const prevProfilerOnStack = nearestProfilerOnStack;
1124 - nearestProfilerOnStack = finishedWork;
1125 -
1126 - let child = firstChild;
1127 - while (child !== null) {
1128 - nextEffect = child;
1129 - iterativelyCommitLayoutEffects_begin(child, finishedRoot);
1130 - child = child.sibling;
1131 - }
1132 - nextEffect = finishedWork;
1133 -
1134 - if ((finishedWork.flags & LayoutMask) !== NoFlags) {
1135 - if (__DEV__) {
1136 - setCurrentDebugFiberInDEV(finishedWork);
1137 - invokeGuardedCallback(
1138 - null,
1139 - commitLayoutEffectsForProfiler,
1140 - null,
1141 - finishedWork,
1142 - finishedRoot,
1143 - );
1144 - if (hasCaughtError()) {
1145 - const error = clearCaughtError();
1146 - captureCommitPhaseError(finishedWork, error);
1147 - }
1148 - resetCurrentDebugFiberInDEV();
1149 - } else {
1150 - try {
1151 - commitLayoutEffectsForProfiler(finishedWork, finishedRoot);
1152 - } catch (error) {
1153 - captureCommitPhaseError(finishedWork, error);
1154 - }
1155 - }
1156 - }
1157 -
1158 - // Propagate layout effect durations to the next nearest Profiler ancestor.
1159 - // Do not reset these values until the next render so DevTools has a chance to read them first.
1160 - if (prevProfilerOnStack !== null) {
1161 - prevProfilerOnStack.stateNode.effectDuration +=
1162 - finishedWork.stateNode.effectDuration;
1163 - }
1164 - nearestProfilerOnStack = prevProfilerOnStack;
1165 -
1166 - if (finishedWork === subtreeRoot) {
1167 - nextEffect = null;
1168 - return;
1169 - }
1170 - const sibling = finishedWork.sibling;
1171 - if (sibling !== null) {
1172 - warnIfWrongReturnPointer(finishedWork.return, sibling);
1173 - nextEffect = sibling;
1174 - } else {
1175 - nextEffect = finishedWork.return;
1176 - iterativelyCommitLayoutEffects_complete(subtreeRoot, finishedRoot);
1177 - }
1178 - } else {
1179 - warnIfWrongReturnPointer(finishedWork, firstChild);
1180 - nextEffect = firstChild;
1181 - }
1182 - } else {
1183 - iterativelyCommitLayoutEffects_complete(subtreeRoot, finishedRoot);
1184 - }
1185 - }
1186 -}
1187 -
1188 -function iterativelyCommitLayoutEffects_complete(
1189 - subtreeRoot: Fiber,
1190 - finishedRoot: FiberRoot,
1191 -) {
1192 - while (nextEffect !== null) {
1193 - const fiber = nextEffect;
1194 -
1195 - if ((fiber.flags & LayoutMask) !== NoFlags) {
1196 - if (__DEV__) {
1197 - setCurrentDebugFiberInDEV(fiber);
1198 - invokeGuardedCallback(
1199 - null,
1200 - commitLayoutEffectsOnFiber,
1201 - null,
1202 - finishedRoot,
1203 - fiber,
1204 - );
1205 - if (hasCaughtError()) {
1206 - const error = clearCaughtError();
1207 - captureCommitPhaseError(fiber, error);
1208 - }
1209 - resetCurrentDebugFiberInDEV();
1210 - } else {
1211 - try {
1212 - commitLayoutEffectsOnFiber(finishedRoot, fiber);
1213 - } catch (error) {
1214 - captureCommitPhaseError(fiber, error);
1215 - }
1216 - }
1217 - }
1218 -
1219 - if (fiber === subtreeRoot) {
1220 - nextEffect = null;
1221 - return;
1222 - }
1223 -
1224 - const sibling = fiber.sibling;
1225 - if (sibling !== null) {
1226 - warnIfWrongReturnPointer(fiber.return, sibling);
1227 - nextEffect = sibling;
1228 - return;
1229 - }
1230 -
1231 - nextEffect = nextEffect.return;
1232 - }
1233 -}
1234 -
1235 -function commitLayoutEffectsOnFiber(
1236 - finishedRoot: FiberRoot,
1237 - finishedWork: Fiber,
1238 -) {
1239 - const tag = finishedWork.tag;
1240 - const flags = finishedWork.flags;
1241 - if ((flags & (Update | Callback)) !== NoFlags) {
1242 - switch (tag) {
1243 - case FunctionComponent:
1244 - case ForwardRef:
1245 - case SimpleMemoComponent: {
1246 - if (
1247 - enableProfilerTimer &&
1248 - enableProfilerCommitHooks &&
1249 - finishedWork.mode & ProfileMode
1250 - ) {
1251 - try {
1252 - startLayoutEffectTimer();
1253 - commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);
1254 - } finally {
1255 - recordLayoutEffectDuration(finishedWork);
1256 - }
1257 - } else {
1258 - commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);
1259 - }
1260 - break;
1261 - }
1262 - case ClassComponent: {
1263 - // NOTE: Layout effect durations are measured within this function.
1264 - commitLayoutEffectsForClassComponent(finishedWork);
1265 - break;
1266 - }
1267 - case HostRoot: {
1268 - commitLayoutEffectsForHostRoot(finishedWork);
1269 - break;
1270 - }
1271 - case HostComponent: {
1272 - commitLayoutEffectsForHostComponent(finishedWork);
1273 - break;
1274 - }
1275 - case Profiler: {
1276 - commitLayoutEffectsForProfiler(finishedWork, finishedRoot);
1277 - break;
1278 - }
1279 - case SuspenseComponent: {
1280 - commitSuspenseHydrationCallbacks(finishedRoot, finishedWork);
1281 - break;
1282 - }
1283 - case FundamentalComponent:
1284 - case HostPortal:
1285 - case HostText:
1286 - case IncompleteClassComponent:
1287 - case LegacyHiddenComponent:
1288 - case OffscreenComponent:
1289 - case ScopeComponent:
1290 - case SuspenseListComponent: {
1291 - // We have no life-cycles associated with these component types.
1292 - break;
1293 - }
1294 - default: {
1295 - invariant(
1296 - false,
1297 - 'This unit of work tag should not have side-effects. This error is ' +
1298 - 'likely caused by a bug in React. Please file an issue.',
1299 - );
1300 - }
1301 - }
1302 - }
1303 -
1304 - if (enableScopeAPI) {
1305 - // TODO: This is a temporary solution that allowed us to transition away from React Flare on www.
1306 - if (flags & Ref && tag !== ScopeComponent) {
1307 - commitAttachRef(finishedWork);
1308 - }
1309 - } else {
1310 - if (flags & Ref) {
1311 - commitAttachRef(finishedWork);
1312 - }
1313 - }
1314 -}
1315 -
1316 -function commitLayoutEffectsForProfiler(
1317 - finishedWork: Fiber,
1318 - finishedRoot: FiberRoot,
1319 -) {
1320 - if (enableProfilerTimer) {
1321 - const flags = finishedWork.flags;
1322 - const current = finishedWork.alternate;
1323 -
1324 - const {onCommit, onRender} = finishedWork.memoizedProps;
1325 - const {effectDuration} = finishedWork.stateNode;
1326 -
1327 - const commitTime = getCommitTime();
1328 -
1329 - const OnRenderFlag = Update;
1330 - const OnCommitFlag = Callback;
1331 -
1332 - let phase = current === null ? 'mount' : 'update';
1333 - if (enableProfilerNestedUpdatePhase) {
1334 - if (isCurrentUpdateNested()) {
1335 - phase = 'nested-update';
1336 - }
1337 - }
1338 -
1339 - if ((flags & OnRenderFlag) !== NoFlags && typeof onRender === 'function') {
1340 - if (enableSchedulerTracing) {
1341 - onRender(
1342 - finishedWork.memoizedProps.id,
1343 - phase,
1344 - finishedWork.actualDuration,
1345 - finishedWork.treeBaseDuration,
1346 - finishedWork.actualStartTime,
1347 - commitTime,
1348 - finishedRoot.memoizedInteractions,
1349 - );
1350 - } else {
1351 - onRender(
1352 - finishedWork.memoizedProps.id,
1353 - phase,
1354 - finishedWork.actualDuration,
1355 - finishedWork.treeBaseDuration,
1356 - finishedWork.actualStartTime,
1357 - commitTime,
1358 - );
1359 - }
1360 - }
1361 -
1362 - if (enableProfilerCommitHooks) {
1363 - if (
1364 - (flags & OnCommitFlag) !== NoFlags &&
1365 - typeof onCommit === 'function'
1366 - ) {
1367 - if (enableSchedulerTracing) {
1368 - onCommit(
1369 - finishedWork.memoizedProps.id,
1370 - phase,
1371 - effectDuration,
1372 - commitTime,
1373 - finishedRoot.memoizedInteractions,
1374 - );
1375 - } else {
1376 - onCommit(
1377 - finishedWork.memoizedProps.id,
1378 - phase,
1379 - effectDuration,
1380 - commitTime,
1381 - );
1382 - }
1383 - }
1384 - }
1385 - }
1386 -}
1387 -
1388 -function commitLayoutEffectsForClassComponent(finishedWork: Fiber) {
1389 - const instance = finishedWork.stateNode;
1390 - const current = finishedWork.alternate;
1391 - if (finishedWork.flags & Update) {
1392 - if (current === null) {
1393 - // We could update instance props and state here,
1394 - // but instead we rely on them being set during last render.
1395 - // TODO: revisit this when we implement resuming.
1396 - if (__DEV__) {
1397 - if (
1398 - finishedWork.type === finishedWork.elementType &&
1399 - !didWarnAboutReassigningProps
1400 - ) {
1401 - if (instance.props !== finishedWork.memoizedProps) {
1402 - console.error(
1403 - 'Expected %s props to match memoized props before ' +
1404 - 'componentDidMount. ' +
1405 - 'This might either be because of a bug in React, or because ' +
1406 - 'a component reassigns its own `this.props`. ' +
1407 - 'Please file an issue.',
1408 - getComponentName(finishedWork.type) || 'instance',
1409 - );
1410 - }
1411 - if (instance.state !== finishedWork.memoizedState) {
1412 - console.error(
1413 - 'Expected %s state to match memoized state before ' +
1414 - 'componentDidMount. ' +
1415 - 'This might either be because of a bug in React, or because ' +
1416 - 'a component reassigns its own `this.state`. ' +
1417 - 'Please file an issue.',
1418 - getComponentName(finishedWork.type) || 'instance',
1419 - );
1420 - }
1421 - }
1422 - }
1423 - if (
1424 - enableProfilerTimer &&
1425 - enableProfilerCommitHooks &&
1426 - finishedWork.mode & ProfileMode
1427 - ) {
1428 - try {
1429 - startLayoutEffectTimer();
1430 - instance.componentDidMount();
1431 - } finally {
1432 - recordLayoutEffectDuration(finishedWork);
1433 - }
1434 - } else {
1435 - instance.componentDidMount();
1436 - }
1437 - } else {
1438 - const prevProps =
1439 - finishedWork.elementType === finishedWork.type
1440 - ? current.memoizedProps
1441 - : resolveDefaultProps(finishedWork.type, current.memoizedProps);
1442 - const prevState = current.memoizedState;
1443 - // We could update instance props and state here,
1444 - // but instead we rely on them being set during last render.
1445 - // TODO: revisit this when we implement resuming.
1446 - if (__DEV__) {
1447 - if (
1448 - finishedWork.type === finishedWork.elementType &&
1449 - !didWarnAboutReassigningProps
1450 - ) {
1451 - if (instance.props !== finishedWork.memoizedProps) {
1452 - console.error(
1453 - 'Expected %s props to match memoized props before ' +
1454 - 'componentDidUpdate. ' +
1455 - 'This might either be because of a bug in React, or because ' +
1456 - 'a component reassigns its own `this.props`. ' +
1457 - 'Please file an issue.',
1458 - getComponentName(finishedWork.type) || 'instance',
1459 - );
1460 - }
1461 - if (instance.state !== finishedWork.memoizedState) {
1462 - console.error(
1463 - 'Expected %s state to match memoized state before ' +
1464 - 'componentDidUpdate. ' +
1465 - 'This might either be because of a bug in React, or because ' +
1466 - 'a component reassigns its own `this.state`. ' +
1467 - 'Please file an issue.',
1468 - getComponentName(finishedWork.type) || 'instance',
1469 - );
1470 - }
1471 - }
1472 - }
1473 - if (
1474 - enableProfilerTimer &&
1475 - enableProfilerCommitHooks &&
1476 - finishedWork.mode & ProfileMode
1477 - ) {
1478 - try {
1479 - startLayoutEffectTimer();
1480 - instance.componentDidUpdate(
1481 - prevProps,
1482 - prevState,
1483 - instance.__reactInternalSnapshotBeforeUpdate,
1484 - );
1485 - } finally {
1486 - recordLayoutEffectDuration(finishedWork);
1487 - }
1488 - } else {
1489 - instance.componentDidUpdate(
1490 - prevProps,
1491 - prevState,
1492 - instance.__reactInternalSnapshotBeforeUpdate,
1493 - );
1494 - }
1495 - }
1496 - }
1497 -
1498 - // TODO: I think this is now always non-null by the time it reaches the
1499 - // commit phase. Consider removing the type check.
1500 - const updateQueue: UpdateQueue<*> | null = (finishedWork.updateQueue: any);
1501 - if (updateQueue !== null) {
1502 - if (__DEV__) {
1503 - if (
1504 - finishedWork.type === finishedWork.elementType &&
1505 - !didWarnAboutReassigningProps
1506 - ) {
1507 - if (instance.props !== finishedWork.memoizedProps) {
1508 - console.error(
1509 - 'Expected %s props to match memoized props before ' +
1510 - 'processing the update queue. ' +
1511 - 'This might either be because of a bug in React, or because ' +
1512 - 'a component reassigns its own `this.props`. ' +
1513 - 'Please file an issue.',
1514 - getComponentName(finishedWork.type) || 'instance',
1515 - );
1516 - }
1517 - if (instance.state !== finishedWork.memoizedState) {
1518 - console.error(
1519 - 'Expected %s state to match memoized state before ' +
1520 - 'processing the update queue. ' +
1521 - 'This might either be because of a bug in React, or because ' +
1522 - 'a component reassigns its own `this.state`. ' +
1523 - 'Please file an issue.',
1524 - getComponentName(finishedWork.type) || 'instance',
1525 - );
1526 - }
1527 - }
1528 - }
1529 - // We could update instance props and state here,
1530 - // but instead we rely on them being set during last render.
1531 - // TODO: revisit this when we implement resuming.
1532 - commitUpdateQueue(finishedWork, updateQueue, instance);
1533 - }
1534 -}
1535 -
1536 -function commitLayoutEffectsForHostRoot(finishedWork: Fiber) {
1537 - // TODO: I think this is now always non-null by the time it reaches the
1538 - // commit phase. Consider removing the type check.
1539 - const updateQueue: UpdateQueue<*> | null = (finishedWork.updateQueue: any);
1540 - if (updateQueue !== null) {
1541 - let instance = null;
1542 - if (finishedWork.child !== null) {
1543 - switch (finishedWork.child.tag) {
1544 - case HostComponent:
1545 - instance = getPublicInstance(finishedWork.child.stateNode);
1546 - break;
1547 - case ClassComponent:
1548 - instance = finishedWork.child.stateNode;
1549 - break;
1550 - }
1551 - }
1552 - commitUpdateQueue(finishedWork, updateQueue, instance);
1553 - }
1554 -}
1555 -
1556 -function commitLayoutEffectsForHostComponent(finishedWork: Fiber) {
1557 - const instance: Instance = finishedWork.stateNode;
1558 - const current = finishedWork.alternate;
1559 -
1560 - // Renderers may schedule work to be done after host components are mounted
1561 - // (eg DOM renderer may schedule auto-focus for inputs and form controls).
1562 - // These effects should only be committed when components are first mounted,
1563 - // aka when there is no current/alternate.
1564 - if (current === null && finishedWork.flags & Update) {
1565 - const type = finishedWork.type;
1566 - const props = finishedWork.memoizedProps;
1567 - commitMount(instance, type, props, finishedWork);
1568 - }
1569 -}
1570 -
1571 -function hideOrUnhideAllChildren(finishedWork, isHidden) {
1572 - if (supportsMutation) {
1573 - // We only have the top Fiber that was inserted but we need to recurse down its
1574 - // children to find all the terminal nodes.
1575 - let node: Fiber = finishedWork;
1576 - while (true) {
1577 - if (node.tag === HostComponent) {
1578 - const instance = node.stateNode;
1579 - if (isHidden) {
1580 - hideInstance(instance);
1581 - } else {
1582 - unhideInstance(node.stateNode, node.memoizedProps);
1583 - }
1584 - } else if (node.tag === HostText) {
1585 - const instance = node.stateNode;
1586 - if (isHidden) {
1587 - hideTextInstance(instance);
1588 - } else {
1589 - unhideTextInstance(instance, node.memoizedProps);
1590 - }
1591 - } else if (
1592 - (node.tag === OffscreenComponent ||
1593 - node.tag === LegacyHiddenComponent) &&
1594 - (node.memoizedState: OffscreenState) !== null &&
1595 - node !== finishedWork
1596 - ) {
1597 - // Found a nested Offscreen component that is hidden. Don't search
1598 - // any deeper. This tree should remain hidden.
1599 - } else if (node.child !== null) {
1600 - node.child.return = node;
1601 - node = node.child;
1602 - continue;
1603 - }
1604 - if (node === finishedWork) {
1605 - return;
1606 - }
1607 - while (node.sibling === null) {
1608 - if (node.return === null || node.return === finishedWork) {
1609 - return;
1610 - }
1611 - node = node.return;
1612 - }
1613 - node.sibling.return = node.return;
1614 - node = node.sibling;
1615 - }
1616 - }
1617 -}
1618 -
1619 -export function commitPassiveMountEffects(
1620 - root: FiberRoot,
1621 - firstChild: Fiber,
1622 -): void {
1623 - if (enableRecursiveCommitTraversal) {
1624 - recursivelyCommitPassiveMountEffects(root, firstChild);
1625 - } else {
1626 - nextEffect = firstChild;
1627 - iterativelyCommitPassiveMountEffects_begin(firstChild, root);
1628 - }
1629 -}
1630 -
1631 -function recursivelyCommitPassiveMountEffects(
1632 - root: FiberRoot,
1633 - firstChild: Fiber,
1634 -): void {
1635 - let fiber = firstChild;
1636 - while (fiber !== null) {
1637 - let prevProfilerOnStack = null;
1638 - if (enableProfilerTimer && enableProfilerCommitHooks) {
1639 - if (fiber.tag === Profiler) {
1640 - prevProfilerOnStack = nearestProfilerOnStack;
1641 - nearestProfilerOnStack = fiber;
1642 - }
1643 - }
1644 -
1645 - const primarySubtreeFlags = fiber.subtreeFlags & PassiveMask;
1646 -
1647 - if (fiber.child !== null && primarySubtreeFlags !== NoFlags) {
1648 - recursivelyCommitPassiveMountEffects(root, fiber.child);
1649 - }
1650 -
1651 - if ((fiber.flags & Passive) !== NoFlags) {
1652 - if (__DEV__) {
1653 - setCurrentDebugFiberInDEV(fiber);
1654 - invokeGuardedCallback(
1655 - null,
1656 - commitPassiveMountOnFiber,
1657 - null,
1658 - root,
1659 - fiber,
1660 - );
1661 - if (hasCaughtError()) {
1662 - const error = clearCaughtError();
1663 - captureCommitPhaseError(fiber, error);
1664 - }
1665 - resetCurrentDebugFiberInDEV();
1666 - } else {
1667 - try {
1668 - commitPassiveMountOnFiber(root, fiber);
1669 - } catch (error) {
1670 - captureCommitPhaseError(fiber, error);
1671 - }
1672 - }
1673 - }
1674 -
1675 - if (enableProfilerTimer && enableProfilerCommitHooks) {
1676 - if (fiber.tag === Profiler) {
1677 - // Bubble times to the next nearest ancestor Profiler.
1678 - // After we process that Profiler, we'll bubble further up.
1679 - if (prevProfilerOnStack !== null) {
1680 - prevProfilerOnStack.stateNode.passiveEffectDuration +=
1681 - fiber.stateNode.passiveEffectDuration;
1682 - }
1683 -
1684 - nearestProfilerOnStack = prevProfilerOnStack;
1685 - }
1686 - }
1687 -
1688 - fiber = fiber.sibling;
1689 - }
1690 -}
1691 -
1692 -function iterativelyCommitPassiveMountEffects_begin(
1693 - subtreeRoot: Fiber,
1694 - root: FiberRoot,
1695 -) {
1696 - while (nextEffect !== null) {
1697 - const fiber = nextEffect;
1698 - const firstChild = fiber.child;
1699 - if ((fiber.subtreeFlags & PassiveMask) !== NoFlags && firstChild !== null) {
1700 - if (
1701 - enableProfilerTimer &&
1702 - enableProfilerCommitHooks &&
1703 - fiber.tag === Profiler
1704 - ) {
1705 - const prevProfilerOnStack = nearestProfilerOnStack;
1706 - nearestProfilerOnStack = fiber;
1707 -
1708 - let child = firstChild;
1709 - while (child !== null) {
1710 - nextEffect = child;
1711 - iterativelyCommitPassiveMountEffects_begin(child, root);
1712 - child = child.sibling;
1713 - }
1714 - nextEffect = fiber;
1715 -
1716 - if ((fiber.flags & PassiveMask) !== NoFlags) {
1717 - if (__DEV__) {
1718 - setCurrentDebugFiberInDEV(fiber);
1719 - invokeGuardedCallback(
1720 - null,
1721 - commitProfilerPassiveEffect,
1722 - null,
1723 - root,
1724 - fiber,
1725 - );
1726 - if (hasCaughtError()) {
1727 - const error = clearCaughtError();
1728 - captureCommitPhaseError(fiber, error);
1729 - }
1730 - resetCurrentDebugFiberInDEV();
1731 - } else {
1732 - try {
1733 - commitProfilerPassiveEffect(root, fiber);
1734 - } catch (error) {
1735 - captureCommitPhaseError(fiber, error);
1736 - }
1737 - }
1738 - }
1739 -
1740 - // Bubble times to the next nearest ancestor Profiler.
1741 - // After we process that Profiler, we'll bubble further up.
1742 - if (prevProfilerOnStack !== null) {
1743 - prevProfilerOnStack.stateNode.passiveEffectDuration +=
1744 - fiber.stateNode.passiveEffectDuration;
1745 - }
1746 -
1747 - nearestProfilerOnStack = prevProfilerOnStack;
1748 -
1749 - if (fiber === subtreeRoot) {
1750 - nextEffect = null;
1751 - return;
1752 - }
1753 - const sibling = fiber.sibling;
1754 - if (sibling !== null) {
1755 - warnIfWrongReturnPointer(fiber.return, sibling);
1756 - nextEffect = sibling;
1757 - } else {
1758 - nextEffect = fiber.return;
1759 - iterativelyCommitPassiveMountEffects_complete(subtreeRoot, root);
1760 - }
1761 - } else {
1762 - warnIfWrongReturnPointer(fiber, firstChild);
1763 - nextEffect = firstChild;
1764 - }
1765 - } else {
1766 - iterativelyCommitPassiveMountEffects_complete(subtreeRoot, root);
1767 - }
1768 - }
1769 -}
1770 -
1771 -function iterativelyCommitPassiveMountEffects_complete(
1772 - subtreeRoot: Fiber,
1773 - root: FiberRoot,
1774 -) {
1775 - while (nextEffect !== null) {
1776 - const fiber = nextEffect;
1777 - if ((fiber.flags & Passive) !== NoFlags) {
1778 - if (__DEV__) {
1779 - setCurrentDebugFiberInDEV(fiber);
1780 - invokeGuardedCallback(
1781 - null,
1782 - commitPassiveMountOnFiber,
1783 - null,
1784 - root,
1785 - fiber,
1786 - );
1787 - if (hasCaughtError()) {
1788 - const error = clearCaughtError();
1789 - captureCommitPhaseError(fiber, error);
1790 - }
1791 - resetCurrentDebugFiberInDEV();
1792 - } else {
1793 - try {
1794 - commitPassiveMountOnFiber(root, fiber);
1795 - } catch (error) {
1796 - captureCommitPhaseError(fiber, error);
1797 - }
1798 - }
1799 - }
695
1801 - if (fiber === subtreeRoot) {
1802 - nextEffect = null;
696 return;
697 }
1805 -
1806 - const sibling = fiber.sibling;
1807 - if (sibling !== null) {
1808 - warnIfWrongReturnPointer(fiber.return, sibling);
1809 - nextEffect = sibling;
698 + case HostText: {
699 + // We have no life-cycles associated with text.
700 return;
701 }
1812 -
1813 - nextEffect = fiber.return;
1814 - }
1815 -}
1816 -
1817 -export function commitPassiveUnmountEffects(firstChild: Fiber): void {
1818 - if (enableRecursiveCommitTraversal) {
1819 - recursivelyCommitPassiveUnmountEffects(firstChild);
1820 - } else {
1821 - nextEffect = firstChild;
1822 - iterativelyCommitPassiveUnmountEffects_begin();
1823 - }
1824 -}
1825 -
1826 -function recursivelyCommitPassiveUnmountEffects(firstChild: Fiber): void {
1827 - let fiber = firstChild;
1828 - while (fiber !== null) {
1829 - const deletions = fiber.deletions;
1830 - if (deletions !== null) {
1831 - for (let i = 0; i < deletions.length; i++) {
1832 - const fiberToDelete = deletions[i];
1833 - recursivelyCommitPassiveUnmountEffectsInsideOfDeletedTree(
1834 - fiberToDelete,
1835 - );
1836 -
1837 - // Now that passive effects have been processed, it's safe to detach lingering pointers.
1838 - detachFiberAfterEffects(fiberToDelete);
1839 - }
1840 - }
1841 -
1842 - const child = fiber.child;
1843 - if (child !== null) {
1844 - // If any children have passive effects then traverse the subtree.
1845 - // Note that this requires checking subtreeFlags of the current Fiber,
1846 - // rather than the subtreeFlags/effectsTag of the first child,
1847 - // since that would not cover passive effects in siblings.
1848 - const passiveFlags = fiber.subtreeFlags & PassiveMask;
1849 - if (passiveFlags !== NoFlags) {
1850 - recursivelyCommitPassiveUnmountEffects(child);
1851 - }
1852 - }
1853 -
1854 - const primaryFlags = fiber.flags & Passive;
1855 - if (primaryFlags !== NoFlags) {
1856 - setCurrentDebugFiberInDEV(fiber);
1857 - commitPassiveUnmountOnFiber(fiber);
1858 - resetCurrentDebugFiberInDEV();
1859 - }
1860 -
1861 - fiber = fiber.sibling;
1862 - }
1863 -}
1864 -
1865 -function iterativelyCommitPassiveUnmountEffects_begin() {
1866 - while (nextEffect !== null) {
1867 - const fiber = nextEffect;
1868 - const child = fiber.child;
1869 -
1870 - // TODO: Should wrap this in flags check, too, as optimization
1871 - const deletions = fiber.deletions;
1872 - if (deletions !== null) {
1873 - for (let i = 0; i < deletions.length; i++) {
1874 - const fiberToDelete = deletions[i];
1875 - nextEffect = fiberToDelete;
1876 - iterativelyCommitPassiveUnmountEffectsInsideOfDeletedTree_begin(
1877 - fiberToDelete,
1878 - );
1879 -
1880 - // Now that passive effects have been processed, it's safe to detach lingering pointers.
1881 - detachFiberAfterEffects(fiberToDelete);
1882 - }
1883 - nextEffect = fiber;
1884 - }
1885 -
1886 - if ((fiber.subtreeFlags & PassiveMask) !== NoFlags && child !== null) {
1887 - warnIfWrongReturnPointer(fiber, child);
1888 - nextEffect = child;
1889 - } else {
1890 - iterativelyCommitPassiveUnmountEffects_complete();
1891 - }
1892 - }
1893 -}
1894 -
1895 -function iterativelyCommitPassiveUnmountEffects_complete() {
1896 - while (nextEffect !== null) {
1897 - const fiber = nextEffect;
1898 - if ((fiber.flags & Passive) !== NoFlags) {
1899 - setCurrentDebugFiberInDEV(fiber);
1900 - commitPassiveUnmountOnFiber(fiber);
1901 - resetCurrentDebugFiberInDEV();
1902 - }
1903 -
1904 - const sibling = fiber.sibling;
1905 - if (sibling !== null) {
1906 - warnIfWrongReturnPointer(fiber.return, sibling);
1907 - nextEffect = sibling;
702 + case HostPortal: {
703 + // We have no life-cycles associated with portals.
704 return;
705 }
706 + case Profiler: {
707 + if (enableProfilerTimer) {
708 + const {onCommit, onRender} = finishedWork.memoizedProps;
709 + const {effectDuration} = finishedWork.stateNode;
710
1911 - nextEffect = fiber.return;
1912 - }
1913 -}
1914 -
1915 -function recursivelyCommitPassiveUnmountEffectsInsideOfDeletedTree(
1916 - fiberToDelete: Fiber,
1917 -): void {
1918 - if ((fiberToDelete.subtreeFlags & PassiveStatic) !== NoFlags) {
1919 - // If any children have passive effects then traverse the subtree.
1920 - // Note that this requires checking subtreeFlags of the current Fiber,
1921 - // rather than the subtreeFlags/effectsTag of the first child,
1922 - // since that would not cover passive effects in siblings.
1923 - let child = fiberToDelete.child;
1924 - while (child !== null) {
1925 - recursivelyCommitPassiveUnmountEffectsInsideOfDeletedTree(child);
1926 - child = child.sibling;
1927 - }
1928 - }
1929 -
1930 - if ((fiberToDelete.flags & PassiveStatic) !== NoFlags) {
1931 - setCurrentDebugFiberInDEV(fiberToDelete);
1932 - commitPassiveUnmountInsideDeletedTreeOnFiber(fiberToDelete);
1933 - resetCurrentDebugFiberInDEV();
1934 - }
1935 -}
711 + const commitTime = getCommitTime();
712
1937 -function iterativelyCommitPassiveUnmountEffectsInsideOfDeletedTree_begin(
1938 - deletedSubtreeRoot: Fiber,
1939 -) {
1940 - while (nextEffect !== null) {
1941 - const fiber = nextEffect;
1942 - const child = fiber.child;
1943 - if ((fiber.subtreeFlags & PassiveStatic) !== NoFlags && child !== null) {
1944 - warnIfWrongReturnPointer(fiber, child);
1945 - nextEffect = child;
1946 - } else {
1947 - iterativelyCommitPassiveUnmountEffectsInsideOfDeletedTree_complete(
1948 - deletedSubtreeRoot,
1949 - );
1950 - }
1951 - }
1952 -}
713 + let phase = current === null ? 'mount' : 'update';
714 + if (enableProfilerNestedUpdatePhase) {
715 + if (isCurrentUpdateNested()) {
716 + phase = 'nested-update';
717 + }
718 + }
719
1954 -function iterativelyCommitPassiveUnmountEffectsInsideOfDeletedTree_complete(
1955 - deletedSubtreeRoot: Fiber,
1956 -) {
1957 - while (nextEffect !== null) {
1958 - const fiber = nextEffect;
1959 - if ((fiber.flags & PassiveStatic) !== NoFlags) {
1960 - setCurrentDebugFiberInDEV(fiber);
1961 - commitPassiveUnmountInsideDeletedTreeOnFiber(fiber);
1962 - resetCurrentDebugFiberInDEV();
1963 - }
720 + if (typeof onRender === 'function') {
721 + if (enableSchedulerTracing) {
722 + onRender(
723 + finishedWork.memoizedProps.id,
724 + phase,
725 + finishedWork.actualDuration,
726 + finishedWork.treeBaseDuration,
727 + finishedWork.actualStartTime,
728 + commitTime,
729 + finishedRoot.memoizedInteractions,
730 + );
731 + } else {
732 + onRender(
733 + finishedWork.memoizedProps.id,
734 + phase,
735 + finishedWork.actualDuration,
736 + finishedWork.treeBaseDuration,
737 + finishedWork.actualStartTime,
738 + commitTime,
739 + );
740 + }
741 + }
742 +
743 + if (enableProfilerCommitHooks) {
744 + if (typeof onCommit === 'function') {
745 + if (enableSchedulerTracing) {
746 + onCommit(
747 + finishedWork.memoizedProps.id,
748 + phase,
749 + effectDuration,
750 + commitTime,
751 + finishedRoot.memoizedInteractions,
752 + );
753 + } else {
754 + onCommit(
755 + finishedWork.memoizedProps.id,
756 + phase,
757 + effectDuration,
758 + commitTime,
759 + );
760 + }
761 + }
762
1965 - if (fiber === deletedSubtreeRoot) {
1966 - nextEffect = null;
763 + // Schedule a passive effect for this Profiler to call onPostCommit hooks.
764 + // This effect should be scheduled even if there is no onPostCommit callback for this Profiler,
765 + // because the effect is also where times bubble to parent Profilers.
766 + enqueuePendingPassiveProfilerEffect(finishedWork);
767 +
768 + // Propagate layout effect durations to the next nearest Profiler ancestor.
769 + // Do not reset these values until the next render so DevTools has a chance to read them first.
770 + let parentFiber = finishedWork.return;
771 + while (parentFiber !== null) {
772 + if (parentFiber.tag === Profiler) {
773 + const parentStateNode = parentFiber.stateNode;
774 + parentStateNode.effectDuration += effectDuration;
775 + break;
776 + }
777 + parentFiber = parentFiber.return;
778 + }
779 + }
780 + }
781 return;
782 }
1969 -
1970 - const sibling = fiber.sibling;
1971 - if (sibling !== null) {
1972 - warnIfWrongReturnPointer(fiber.return, sibling);
1973 - nextEffect = sibling;
783 + case SuspenseComponent: {
784 + commitSuspenseHydrationCallbacks(finishedRoot, finishedWork);
785 return;
786 }
1976 -
1977 - nextEffect = fiber.return;
787 + case SuspenseListComponent:
788 + case IncompleteClassComponent:
789 + case FundamentalComponent:
790 + case ScopeComponent:
791 + case OffscreenComponent:
792 + case LegacyHiddenComponent:
793 + return;
794 }
795 + invariant(
796 + false,
797 + 'This unit of work tag should not have side-effects. This error is ' +
798 + 'likely caused by a bug in React. Please file an issue.',
799 + );
800 }
801
1981 -function detachFiberAfterEffects(fiber: Fiber): void {
1982 - // Null out fields to improve GC for references that may be lingering (e.g. DevTools).
1983 - // Note that we already cleared the return pointer in detachFiberMutation().
1984 - fiber.child = null;
1985 - fiber.deletions = null;
1986 - fiber.dependencies = null;
1987 - fiber.memoizedProps = null;
1988 - fiber.memoizedState = null;
1989 - fiber.pendingProps = null;
1990 - fiber.sibling = null;
1991 - fiber.stateNode = null;
1992 - fiber.updateQueue = null;
1993 -
1994 - if (__DEV__) {
1995 - fiber._debugOwner = null;
802 +function hideOrUnhideAllChildren(finishedWork, isHidden) {
803 + if (supportsMutation) {
804 + // We only have the top Fiber that was inserted but we need to recurse down its
805 + // children to find all the terminal nodes.
806 + let node: Fiber = finishedWork;
807 + while (true) {
808 + if (node.tag === HostComponent) {
809 + const instance = node.stateNode;
810 + if (isHidden) {
811 + hideInstance(instance);
812 + } else {
813 + unhideInstance(node.stateNode, node.memoizedProps);
814 + }
815 + } else if (node.tag === HostText) {
816 + const instance = node.stateNode;
817 + if (isHidden) {
818 + hideTextInstance(instance);
819 + } else {
820 + unhideTextInstance(instance, node.memoizedProps);
821 + }
822 + } else if (
823 + (node.tag === OffscreenComponent ||
824 + node.tag === LegacyHiddenComponent) &&
825 + (node.memoizedState: OffscreenState) !== null &&
826 + node !== finishedWork
827 + ) {
828 + // Found a nested Offscreen component that is hidden. Don't search
829 + // any deeper. This tree should remain hidden.
830 + } else if (node.child !== null) {
831 + node.child.return = node;
832 + node = node.child;
833 + continue;
834 + }
835 + if (node === finishedWork) {
836 + return;
837 + }
838 + while (node.sibling === null) {
839 + if (node.return === null || node.return === finishedWork) {
840 + return;
841 + }
842 + node = node.return;
843 + }
844 + node.sibling.return = node.return;
845 + node = node.sibling;
846 + }
847 }
848 }
849
@@ -2092,7 +943,9 @@ function commitUnmount(
943 do {
944 const {destroy, tag} = effect;
945 if (destroy !== undefined) {
2095 - if ((tag & HookLayout) !== NoHookEffect) {
946 + if ((tag & HookPassive) !== NoHookEffect) {
947 + enqueuePendingPassiveHookEffectUnmount(current, effect);
948 + } else {
949 if (
950 enableProfilerTimer &&
951 enableProfilerCommitHooks &&
@@ -2206,24 +1059,32 @@ function commitNestedUnmounts(
1059 }
1060
1061 function detachFiberMutation(fiber: Fiber) {
2209 - // Cut off the return pointer to disconnect it from the tree.
2210 - // This enables us to detect and warn against state updates on an unmounted component.
2211 - // It also prevents events from bubbling from within disconnected components.
2212 - //
2213 - // Ideally, we should also clear the child pointer of the parent alternate to let this
1062 + // Cut off the return pointers to disconnect it from the tree. Ideally, we
1063 + // should clear the child pointer of the parent alternate to let this
1064 // get GC:ed but we don't know which for sure which parent is the current
2215 - // one so we'll settle for GC:ing the subtree of this child.
2216 - // This child itself will be GC:ed when the parent updates the next time.
1065 + // one so we'll settle for GC:ing the subtree of this child. This child
1066 + // itself will be GC:ed when the parent updates the next time.
1067 + // Note: we cannot null out sibling here, otherwise it can cause issues
1068 + // with findDOMNode and how it requires the sibling field to carry out
1069 + // traversal in a later effect. See PR #16820. We now clear the sibling
1070 + // field after effects, see: detachFiberAfterEffects.
1071 //
2218 - // Note that we can't clear child or sibling pointers yet.
2219 - // They're needed for passive effects and for findDOMNode.
2220 - // We defer those fields, and all other cleanup, to the passive phase (see detachFiberAfterEffects).
2221 - const alternate = fiber.alternate;
2222 - if (alternate !== null) {
2223 - alternate.return = null;
2224 - fiber.alternate = null;
2225 - }
1072 + // Don't disconnect stateNode now; it will be detached in detachFiberAfterEffects.
1073 + // It may be required if the current component is an error boundary,
1074 + // and one of its descendants throws while unmounting a passive effect.
1075 + fiber.alternate = null;
1076 + fiber.child = null;
1077 + fiber.dependencies = null;
1078 + fiber.firstEffect = null;
1079 + fiber.lastEffect = null;
1080 + fiber.memoizedProps = null;
1081 + fiber.memoizedState = null;
1082 + fiber.pendingProps = null;
1083 fiber.return = null;
1084 + fiber.updateQueue = null;
1085 + if (__DEV__) {
1086 + fiber._debugOwner = null;
1087 + }
1088 }
1089
1090 function emptyPortalContainer(current: Fiber) {
@@ -2459,9 +1320,9 @@ function insertOrAppendPlacementNode(
1320 }
1321
1322 function unmountHostComponents(
2462 - finishedRoot,
2463 - current,
2464 - renderPriorityLevel,
1323 + finishedRoot: FiberRoot,
1324 + current: Fiber,
1325 + renderPriorityLevel: ReactPriorityLevel,
1326 ): void {
1327 // We only have the top Fiber that was deleted but we need to recurse down its
1328 // children to find all the terminal nodes.
@@ -2915,7 +1776,7 @@ function attachSuspenseRetryListeners(finishedWork: Fiber) {
1776 // This function detects when a Suspense boundary goes from visible to hidden.
1777 // It returns false if the boundary is already hidden.
1778 // TODO: Use an effect tag.
2918 -function isSuspenseBoundaryBeingHidden(
1779 +export function isSuspenseBoundaryBeingHidden(
1780 current: Fiber | null,
1781 finishedWork: Fiber,
1782 ): boolean {
@@ -2929,266 +1790,20 @@ function isSuspenseBoundaryBeingHidden(
1790 return false;
1791 }
1792
2932 -function commitResetTextContent(current: Fiber): void {
1793 +function commitResetTextContent(current: Fiber) {
1794 if (!supportsMutation) {
1795 return;
1796 }
1797 resetTextContent(current.stateNode);
1798 }
1799
2939 -function commitPassiveUnmountOnFiber(finishedWork: Fiber): void {
2940 - switch (finishedWork.tag) {
2941 - case FunctionComponent:
2942 - case ForwardRef:
2943 - case SimpleMemoComponent: {
2944 - if (
2945 - enableProfilerTimer &&
2946 - enableProfilerCommitHooks &&
2947 - finishedWork.mode & ProfileMode
2948 - ) {
2949 - startPassiveEffectTimer();
2950 - commitHookEffectListUnmount(HookPassive | HookHasEffect, finishedWork);
2951 - recordPassiveEffectDuration(finishedWork);
2952 - } else {
2953 - commitHookEffectListUnmount(HookPassive | HookHasEffect, finishedWork);
2954 - }
2955 - break;
2956 - }
2957 - }
2958 -}
2959 -
2960 -function commitPassiveUnmountInsideDeletedTreeOnFiber(current: Fiber): void {
2961 - switch (current.tag) {
2962 - case FunctionComponent:
2963 - case ForwardRef:
2964 - case SimpleMemoComponent: {
2965 - if (
2966 - enableProfilerTimer &&
2967 - enableProfilerCommitHooks &&
2968 - current.mode & ProfileMode
2969 - ) {
2970 - startPassiveEffectTimer();
2971 - commitHookEffectListUnmount(HookPassive, current);
2972 - recordPassiveEffectDuration(current);
2973 - } else {
2974 - commitHookEffectListUnmount(HookPassive, current);
2975 - }
2976 - break;
2977 - }
2978 - }
2979 -}
2980 -
2981 -function commitPassiveMountOnFiber(
2982 - finishedRoot: FiberRoot,
2983 - finishedWork: Fiber,
2984 -): void {
2985 - switch (finishedWork.tag) {
2986 - case FunctionComponent:
2987 - case ForwardRef:
2988 - case SimpleMemoComponent: {
2989 - if (
2990 - enableProfilerTimer &&
2991 - enableProfilerCommitHooks &&
2992 - finishedWork.mode & ProfileMode
2993 - ) {
2994 - startPassiveEffectTimer();
2995 - try {
2996 - commitHookEffectListMount(HookPassive | HookHasEffect, finishedWork);
2997 - } finally {
2998 - recordPassiveEffectDuration(finishedWork);
2999 - }
3000 - } else {
3001 - commitHookEffectListMount(HookPassive | HookHasEffect, finishedWork);
3002 - }
3003 - break;
3004 - }
3005 - case Profiler: {
3006 - commitProfilerPassiveEffect(finishedRoot, finishedWork);
3007 - break;
3008 - }
3009 - }
3010 -}
3011 -
3012 -function invokeLayoutEffectMountInDEV(fiber: Fiber): void {
3013 - if (__DEV__ && enableDoubleInvokingEffects) {
3014 - // We don't need to re-check for legacy roots here.
3015 - // This function will not be called within legacy roots.
3016 - switch (fiber.tag) {
3017 - case FunctionComponent:
3018 - case ForwardRef:
3019 - case SimpleMemoComponent: {
3020 - invokeGuardedCallback(
3021 - null,
3022 - commitHookEffectListMount,
3023 - null,
3024 - HookLayout | HookHasEffect,
3025 - fiber,
3026 - );
3027 - if (hasCaughtError()) {
3028 - const mountError = clearCaughtError();
3029 - captureCommitPhaseError(fiber, mountError);
3030 - }
3031 - break;
3032 - }
3033 - case ClassComponent: {
3034 - const instance = fiber.stateNode;
3035 - invokeGuardedCallback(null, instance.componentDidMount, instance);
3036 - if (hasCaughtError()) {
3037 - const mountError = clearCaughtError();
3038 - captureCommitPhaseError(fiber, mountError);
3039 - }
3040 - break;
3041 - }
3042 - }
3043 - }
3044 -}
3045 -
3046 -function invokePassiveEffectMountInDEV(fiber: Fiber): void {
3047 - if (__DEV__ && enableDoubleInvokingEffects) {
3048 - // We don't need to re-check for legacy roots here.
3049 - // This function will not be called within legacy roots.
3050 - switch (fiber.tag) {
3051 - case FunctionComponent:
3052 - case ForwardRef:
3053 - case SimpleMemoComponent: {
3054 - invokeGuardedCallback(
3055 - null,
3056 - commitHookEffectListMount,
3057 - null,
3058 - HookPassive | HookHasEffect,
3059 - fiber,
3060 - );
3061 - if (hasCaughtError()) {
3062 - const mountError = clearCaughtError();
3063 - captureCommitPhaseError(fiber, mountError);
3064 - }
3065 - break;
3066 - }
3067 - }
3068 - }
3069 -}
3070 -
3071 -function invokeLayoutEffectUnmountInDEV(fiber: Fiber): void {
3072 - if (__DEV__ && enableDoubleInvokingEffects) {
3073 - // We don't need to re-check for legacy roots here.
3074 - // This function will not be called within legacy roots.
3075 - switch (fiber.tag) {
3076 - case FunctionComponent:
3077 - case ForwardRef:
3078 - case SimpleMemoComponent: {
3079 - invokeGuardedCallback(
3080 - null,
3081 - commitHookEffectListUnmount,
3082 - null,
3083 - HookLayout | HookHasEffect,
3084 - fiber,
3085 - );
3086 - if (hasCaughtError()) {
3087 - const unmountError = clearCaughtError();
3088 - captureCommitPhaseError(fiber, unmountError);
3089 - }
3090 - break;
3091 - }
3092 - case ClassComponent: {
3093 - const instance = fiber.stateNode;
3094 - if (typeof instance.componentWillUnmount === 'function') {
3095 - safelyCallComponentWillUnmount(fiber, instance);
3096 - }
3097 - break;
3098 - }
3099 - }
3100 - }
3101 -}
3102 -
3103 -function invokePassiveEffectUnmountInDEV(fiber: Fiber): void {
3104 - if (__DEV__ && enableDoubleInvokingEffects) {
3105 - // We don't need to re-check for legacy roots here.
3106 - // This function will not be called within legacy roots.
3107 - switch (fiber.tag) {
3108 - case FunctionComponent:
3109 - case ForwardRef:
3110 - case SimpleMemoComponent: {
3111 - invokeGuardedCallback(
3112 - null,
3113 - commitHookEffectListUnmount,
3114 - null,
3115 - HookPassive | HookHasEffect,
3116 - fiber,
3117 - );
3118 - if (hasCaughtError()) {
3119 - const unmountError = clearCaughtError();
3120 - captureCommitPhaseError(fiber, unmountError);
3121 - }
3122 - break;
3123 - }
3124 - }
3125 - }
3126 -}
3127 -
3128 -// TODO: Convert this to iteration instead of recursion, too. Leaving this for
3129 -// a follow up because the flag is off.
3130 -export function commitDoubleInvokeEffectsInDEV(
3131 - fiber: Fiber,
3132 - hasPassiveEffects: boolean,
3133 -) {
3134 - if (__DEV__ && enableDoubleInvokingEffects) {
3135 - // Never double-invoke effects for legacy roots.
3136 - if ((fiber.mode & (BlockingMode | ConcurrentMode)) === NoMode) {
3137 - return;
3138 - }
3139 -
3140 - setCurrentDebugFiberInDEV(fiber);
3141 - invokeEffectsInDev(fiber, MountLayoutDev, invokeLayoutEffectUnmountInDEV);
3142 - if (hasPassiveEffects) {
3143 - invokeEffectsInDev(
3144 - fiber,
3145 - MountPassiveDev,
3146 - invokePassiveEffectUnmountInDEV,
3147 - );
3148 - }
3149 -
3150 - invokeEffectsInDev(fiber, MountLayoutDev, invokeLayoutEffectMountInDEV);
3151 - if (hasPassiveEffects) {
3152 - invokeEffectsInDev(fiber, MountPassiveDev, invokePassiveEffectMountInDEV);
3153 - }
3154 - resetCurrentDebugFiberInDEV();
3155 - }
3156 -}
3157 -
3158 -function invokeEffectsInDev(
3159 - firstChild: Fiber,
3160 - fiberFlags: Flags,
3161 - invokeEffectFn: (fiber: Fiber) => void,
3162 -): void {
3163 - if (__DEV__ && enableDoubleInvokingEffects) {
3164 - // We don't need to re-check for legacy roots here.
3165 - // This function will not be called within legacy roots.
3166 - let fiber = firstChild;
3167 - while (fiber !== null) {
3168 - if (fiber.child !== null) {
3169 - const primarySubtreeFlag = fiber.subtreeFlags & fiberFlags;
3170 - if (primarySubtreeFlag !== NoFlags) {
3171 - invokeEffectsInDev(fiber.child, fiberFlags, invokeEffectFn);
3172 - }
3173 - }
3174 -
3175 - if ((fiber.flags & fiberFlags) !== NoFlags) {
3176 - invokeEffectFn(fiber);
3177 - }
3178 - fiber = fiber.sibling;
3179 - }
3180 - }
3181 -}
3182 -
3183 -let didWarnWrongReturnPointer = false;
3184 -function warnIfWrongReturnPointer(returnFiber, child) {
3185 - if (__DEV__) {
3186 - if (!didWarnWrongReturnPointer && child.return !== returnFiber) {
3187 - didWarnWrongReturnPointer = true;
3188 - console.error(
3189 - 'Internal React error: Return pointer is inconsistent ' +
3190 - 'with parent.',
3191 - );
3192 - }
3193 - }
3194 -}
1800 +export {
1801 + commitBeforeMutationLifeCycles,
1802 + commitResetTextContent,
1803 + commitPlacement,
1804 + commitDeletion,
1805 + commitWork,
1806 + commitLifeCycles,
1807 + commitAttachRef,
1808 + commitDetachRef,
1809 +};
packages/react-reconciler/src/ReactFiberCompleteWork.new.js
+40 -316
@@ -8,7 +8,7 @@
8 */
9
10 import type {Fiber} from './ReactInternalTypes';
11 -import type {Lanes, Lane} from './ReactFiberLane';
11 +import type {Lanes} from './ReactFiberLane';
12 import type {
13 ReactFundamentalComponentInstance,
14 ReactScopeInstance,
@@ -57,28 +57,8 @@ import {
57 OffscreenComponent,
58 LegacyHiddenComponent,
59 } from './ReactWorkTags';
60 -import {
61 - NoMode,
62 - BlockingMode,
63 - ConcurrentMode,
64 - ProfileMode,
65 -} from './ReactTypeOfMode';
66 -import {
67 - Ref,
68 - Update,
69 - Callback,
70 - Passive,
71 - Deletion,
72 - NoFlags,
73 - DidCapture,
74 - Snapshot,
75 - Visibility,
76 - MutationMask,
77 - LayoutMask,
78 - PassiveMask,
79 - StaticMask,
80 - PerformedWork,
81 -} from './ReactFiberFlags';
60 +import {NoMode, BlockingMode, ProfileMode} from './ReactTypeOfMode';
61 +import {Ref, Update, NoFlags, DidCapture, Snapshot} from './ReactFiberFlags';
62 import invariant from 'shared/invariant';
63
64 import {
@@ -148,16 +128,9 @@ import {
128 renderHasNotSuspendedYet,
129 popRenderLanes,
130 getRenderTargetTime,
151 - subtreeRenderLanes,
131 } from './ReactFiberWorkLoop.new';
132 import {createFundamentalStateInstance} from './ReactFiberFundamental.new';
154 -import {
155 - OffscreenLane,
156 - SomeRetryLane,
157 - NoLanes,
158 - includesSomeLane,
159 - mergeLanes,
160 -} from './ReactFiberLane';
133 +import {OffscreenLane, SomeRetryLane} from './ReactFiberLane';
134 import {resetChildFibers} from './ReactChildFiber.new';
135 import {createScopeInstance} from './ReactFiberScope.new';
136 import {transferActualDuration} from './ReactProfilerTimer.new';
@@ -172,31 +145,6 @@ function markRef(workInProgress: Fiber) {
145 workInProgress.flags |= Ref;
146 }
147
175 -function hadNoMutationsEffects(current: null | Fiber, completedWork: Fiber) {
176 - const didBailout = current !== null && current.child === completedWork.child;
177 - if (didBailout) {
178 - return true;
179 - }
180 -
181 - if ((completedWork.flags & Deletion) !== NoFlags) {
182 - return false;
183 - }
184 -
185 - // TODO: If we move the `hadNoMutationsEffects` call after `bubbleProperties`
186 - // then we only have to check the `completedWork.subtreeFlags`.
187 - let child = completedWork.child;
188 - while (child !== null) {
189 - if ((child.flags & (MutationMask | Deletion)) !== NoFlags) {
190 - return false;
191 - }
192 - if ((child.subtreeFlags & (MutationMask | Deletion)) !== NoFlags) {
193 - return false;
194 - }
195 - child = child.sibling;
196 - }
197 - return true;
198 -}
199 -
148 let appendAllChildren;
149 let updateHostContainer;
150 let updateHostComponent;
@@ -241,7 +189,7 @@ if (supportsMutation) {
189 }
190 };
191
244 - updateHostContainer = function(current: null | Fiber, workInProgress: Fiber) {
192 + updateHostContainer = function(workInProgress: Fiber) {
193 // Noop
194 };
195 updateHostComponent = function(
@@ -485,13 +433,13 @@ if (supportsMutation) {
433 node = node.sibling;
434 }
435 };
488 - updateHostContainer = function(current: null | Fiber, workInProgress: Fiber) {
436 + updateHostContainer = function(workInProgress: Fiber) {
437 const portalOrRoot: {
438 containerInfo: Container,
439 pendingChildren: ChildSet,
440 ...
441 } = workInProgress.stateNode;
494 - const childrenUnchanged = hadNoMutationsEffects(current, workInProgress);
442 + const childrenUnchanged = workInProgress.firstEffect === null;
443 if (childrenUnchanged) {
444 // No changes, just reuse the existing instance.
445 } else {
@@ -516,7 +464,7 @@ if (supportsMutation) {
464 const oldProps = current.memoizedProps;
465 // If there are no effects associated with this node, then none of our children had any updates.
466 // This guarantees that we can reuse all of them.
519 - const childrenUnchanged = hadNoMutationsEffects(current, workInProgress);
467 + const childrenUnchanged = workInProgress.firstEffect === null;
468 if (childrenUnchanged && oldProps === newProps) {
469 // No changes, just reuse the existing instance.
470 // Note that this might release a previous clone.
@@ -599,7 +547,7 @@ if (supportsMutation) {
547 };
548 } else {
549 // No host operations
602 - updateHostContainer = function(current: null | Fiber, workInProgress: Fiber) {
550 + updateHostContainer = function(workInProgress: Fiber) {
551 // Noop
552 };
553 updateHostComponent = function(
@@ -692,126 +640,6 @@ function cutOffTailIfNeeded(
640 }
641 }
642
695 -function bubbleProperties(completedWork: Fiber) {
696 - const didBailout =
697 - completedWork.alternate !== null &&
698 - completedWork.alternate.child === completedWork.child;
699 -
700 - let newChildLanes = NoLanes;
701 - let subtreeFlags = NoFlags;
702 -
703 - if (!didBailout) {
704 - // Bubble up the earliest expiration time.
705 - if (enableProfilerTimer && (completedWork.mode & ProfileMode) !== NoMode) {
706 - // In profiling mode, resetChildExpirationTime is also used to reset
707 - // profiler durations.
708 - let actualDuration = completedWork.actualDuration;
709 - let treeBaseDuration = ((completedWork.selfBaseDuration: any): number);
710 -
711 - let child = completedWork.child;
712 - while (child !== null) {
713 - newChildLanes = mergeLanes(
714 - newChildLanes,
715 - mergeLanes(child.lanes, child.childLanes),
716 - );
717 -
718 - subtreeFlags |= child.subtreeFlags;
719 - subtreeFlags |= child.flags;
720 -
721 - // When a fiber is cloned, its actualDuration is reset to 0. This value will
722 - // only be updated if work is done on the fiber (i.e. it doesn't bailout).
723 - // When work is done, it should bubble to the parent's actualDuration. If
724 - // the fiber has not been cloned though, (meaning no work was done), then
725 - // this value will reflect the amount of time spent working on a previous
726 - // render. In that case it should not bubble. We determine whether it was
727 - // cloned by comparing the child pointer.
728 - actualDuration += child.actualDuration;
729 -
730 - treeBaseDuration += child.treeBaseDuration;
731 - child = child.sibling;
732 - }
733 -
734 - completedWork.actualDuration = actualDuration;
735 - completedWork.treeBaseDuration = treeBaseDuration;
736 - } else {
737 - let child = completedWork.child;
738 - while (child !== null) {
739 - newChildLanes = mergeLanes(
740 - newChildLanes,
741 - mergeLanes(child.lanes, child.childLanes),
742 - );
743 -
744 - subtreeFlags |= child.subtreeFlags;
745 - subtreeFlags |= child.flags;
746 -
747 - // Update the return pointer so the tree is consistent. This is a code
748 - // smell because it assumes the commit phase is never concurrent with
749 - // the render phase. Will address during refactor to alternate model.
750 - child.return = completedWork;
751 -
752 - child = child.sibling;
753 - }
754 - }
755 -
756 - completedWork.subtreeFlags |= subtreeFlags;
757 - } else {
758 - // Bubble up the earliest expiration time.
759 - if (enableProfilerTimer && (completedWork.mode & ProfileMode) !== NoMode) {
760 - // In profiling mode, resetChildExpirationTime is also used to reset
761 - // profiler durations.
762 - let treeBaseDuration = ((completedWork.selfBaseDuration: any): number);
763 -
764 - let child = completedWork.child;
765 - while (child !== null) {
766 - newChildLanes = mergeLanes(
767 - newChildLanes,
768 - mergeLanes(child.lanes, child.childLanes),
769 - );
770 -
771 - // "Static" flags share the lifetime of the fiber/hook they belong to,
772 - // so we should bubble those up even during a bailout. All the other
773 - // flags have a lifetime only of a single render + commit, so we should
774 - // ignore them.
775 - subtreeFlags |= child.subtreeFlags & StaticMask;
776 - subtreeFlags |= child.flags & StaticMask;
777 -
778 - treeBaseDuration += child.treeBaseDuration;
779 - child = child.sibling;
780 - }
781 -
782 - completedWork.treeBaseDuration = treeBaseDuration;
783 - } else {
784 - let child = completedWork.child;
785 - while (child !== null) {
786 - newChildLanes = mergeLanes(
787 - newChildLanes,
788 - mergeLanes(child.lanes, child.childLanes),
789 - );
790 -
791 - // "Static" flags share the lifetime of the fiber/hook they belong to,
792 - // so we should bubble those up even during a bailout. All the other
793 - // flags have a lifetime only of a single render + commit, so we should
794 - // ignore them.
795 - subtreeFlags |= child.subtreeFlags & StaticMask;
796 - subtreeFlags |= child.flags & StaticMask;
797 -
798 - // Update the return pointer so the tree is consistent. This is a code
799 - // smell because it assumes the commit phase is never concurrent with
800 - // the render phase. Will address during refactor to alternate model.
801 - child.return = completedWork;
802 -
803 - child = child.sibling;
804 - }
805 - }
806 -
807 - completedWork.subtreeFlags |= subtreeFlags;
808 - }
809 -
810 - completedWork.childLanes = newChildLanes;
811 -
812 - return didBailout;
813 -}
814 -
643 function completeWork(
644 current: Fiber | null,
645 workInProgress: Fiber,
@@ -827,16 +655,15 @@ function completeWork(
655 case ForwardRef:
656 case Fragment:
657 case Mode:
658 + case Profiler:
659 case ContextConsumer:
660 case MemoComponent:
832 - bubbleProperties(workInProgress);
661 return null;
662 case ClassComponent: {
663 const Component = workInProgress.type;
664 if (isLegacyContextProvider(Component)) {
665 popLegacyContext(workInProgress);
666 }
839 - bubbleProperties(workInProgress);
667 return null;
668 }
669 case HostRoot: {
@@ -864,8 +691,7 @@ function completeWork(
691 workInProgress.flags |= Snapshot;
692 }
693 }
867 - updateHostContainer(current, workInProgress);
868 - bubbleProperties(workInProgress);
694 + updateHostContainer(workInProgress);
695 return null;
696 }
697 case HostComponent: {
@@ -892,7 +718,6 @@ function completeWork(
718 'caused by a bug in React. Please file an issue.',
719 );
720 // This can happen when we abort work.
895 - bubbleProperties(workInProgress);
721 return null;
722 }
723
@@ -950,7 +775,6 @@ function completeWork(
775 markRef(workInProgress);
776 }
777 }
953 - bubbleProperties(workInProgress);
778 return null;
779 }
780 case HostText: {
@@ -985,58 +809,6 @@ function completeWork(
809 );
810 }
811 }
988 - bubbleProperties(workInProgress);
989 - return null;
990 - }
991 - case Profiler: {
992 - const didBailout = bubbleProperties(workInProgress);
993 - if (!didBailout) {
994 - // Use subtreeFlags to determine which commit callbacks should fire.
995 - // TODO: Move this logic to the commit phase, since we already check if
996 - // a fiber's subtree contains effects. Refactor the commit phase's
997 - // depth-first traversal so that we can put work tag-specific logic
998 - // before or after committing a subtree's effects.
999 - const OnRenderFlag = Update;
1000 - const OnCommitFlag = Callback;
1001 - const OnPostCommitFlag = Passive;
1002 - const subtreeFlags = workInProgress.subtreeFlags;
1003 - const flags = workInProgress.flags;
1004 - let newFlags = flags;
1005 -
1006 - // Call onRender any time this fiber or its subtree are worked on.
1007 - if (
1008 - (flags & PerformedWork) !== NoFlags ||
1009 - (subtreeFlags & PerformedWork) !== NoFlags
1010 - ) {
1011 - newFlags |= OnRenderFlag;
1012 - }
1013 -
1014 - // Call onCommit only if the subtree contains layout work, or if it
1015 - // contains deletions, since those might result in unmount work, which
1016 - // we include in the same measure.
1017 - // TODO: Can optimize by using a static flag to track whether a tree
1018 - // contains layout effects, like we do for passive effects.
1019 - if (
1020 - (flags & (LayoutMask | Deletion)) !== NoFlags ||
1021 - (subtreeFlags & (LayoutMask | Deletion)) !== NoFlags
1022 - ) {
1023 - newFlags |= OnCommitFlag;
1024 - }
1025 -
1026 - // Call onPostCommit only if the subtree contains passive work.
1027 - // Don't have to check for deletions, because Deletion is already
1028 - // a passive flag.
1029 - if (
1030 - (flags & PassiveMask) !== NoFlags ||
1031 - (subtreeFlags & PassiveMask) !== NoFlags
1032 - ) {
1033 - newFlags |= OnPostCommitFlag;
1034 - }
1035 - workInProgress.flags = newFlags;
1036 - } else {
1037 - // This fiber and its subtree bailed out, so don't fire any callbacks.
1038 - }
1039 -
812 return null;
813 }
814 case SuspenseComponent: {
@@ -1056,20 +828,6 @@ function completeWork(
828 if (enableSchedulerTracing) {
829 markSpawnedWork(OffscreenLane);
830 }
1059 - bubbleProperties(workInProgress);
1060 - if (enableProfilerTimer) {
1061 - if ((workInProgress.mode & ProfileMode) !== NoMode) {
1062 - const isTimedOutSuspense = nextState !== null;
1063 - if (isTimedOutSuspense) {
1064 - // Don't count time spent in a timed out Suspense subtree as part of the base duration.
1065 - const primaryChildFragment = workInProgress.child;
1066 - if (primaryChildFragment !== null) {
1067 - // $FlowFixMe Flow doens't support type casting in combiation with the -= operator
1068 - workInProgress.treeBaseDuration -= ((primaryChildFragment.treeBaseDuration: any): number);
1069 - }
1070 - }
1071 - }
1072 - }
831 return null;
832 } else {
833 // We should never have been in a hydration state if we didn't have a current.
@@ -1086,20 +844,6 @@ function completeWork(
844 // If something suspended, schedule an effect to attach retry listeners.
845 // So we might as well always mark this.
846 workInProgress.flags |= Update;
1089 - bubbleProperties(workInProgress);
1090 - if (enableProfilerTimer) {
1091 - if ((workInProgress.mode & ProfileMode) !== NoMode) {
1092 - const isTimedOutSuspense = nextState !== null;
1093 - if (isTimedOutSuspense) {
1094 - // Don't count time spent in a timed out Suspense subtree as part of the base duration.
1095 - const primaryChildFragment = workInProgress.child;
1096 - if (primaryChildFragment !== null) {
1097 - // $FlowFixMe Flow doens't support type casting in combiation with the -= operator
1098 - workInProgress.treeBaseDuration -= ((primaryChildFragment.treeBaseDuration: any): number);
1099 - }
1100 - }
1101 - }
1102 - }
847 return null;
848 }
849 }
@@ -1115,7 +859,6 @@ function completeWork(
859 ) {
860 transferActualDuration(workInProgress);
861 }
1118 - // Don't bubble properties in this case.
862 return workInProgress;
863 }
864
@@ -1169,8 +912,8 @@ function completeWork(
912 // TODO: Only schedule updates if not prevDidTimeout.
913 if (nextDidTimeout) {
914 // If this boundary just timed out, schedule an effect to attach a
1172 - // retry listener to the promise.
1173 - // TODO: Move to passive phase
915 + // retry listener to the promise. This flag is also used to hide the
916 + // primary children.
917 workInProgress.flags |= Update;
918 }
919 }
@@ -1182,7 +925,7 @@ function completeWork(
925 // primary children. In mutation mode, we also need the flag to
926 // *unhide* children that were previously hidden, so check if this
927 // is currently timed out, too.
1185 - workInProgress.flags |= Update | Visibility;
928 + workInProgress.flags |= Update;
929 }
930 }
931 if (
@@ -1191,36 +934,20 @@ function completeWork(
934 workInProgress.memoizedProps.suspenseCallback != null
935 ) {
936 // Always notify the callback
1194 - // TODO: Move to passive phase
937 workInProgress.flags |= Update;
938 }
1197 - bubbleProperties(workInProgress);
1198 - if (enableProfilerTimer) {
1199 - if ((workInProgress.mode & ProfileMode) !== NoMode) {
1200 - if (nextDidTimeout) {
1201 - // Don't count time spent in a timed out Suspense subtree as part of the base duration.
1202 - const primaryChildFragment = workInProgress.child;
1203 - if (primaryChildFragment !== null) {
1204 - // $FlowFixMe Flow doens't support type casting in combiation with the -= operator
1205 - workInProgress.treeBaseDuration -= ((primaryChildFragment.treeBaseDuration: any): number);
1206 - }
1207 - }
1208 - }
1209 - }
939 return null;
940 }
941 case HostPortal:
942 popHostContainer(workInProgress);
1214 - updateHostContainer(current, workInProgress);
943 + updateHostContainer(workInProgress);
944 if (current === null) {
945 preparePortalMount(workInProgress.stateNode.containerInfo);
946 }
1218 - bubbleProperties(workInProgress);
947 return null;
948 case ContextProvider:
949 // Pop provider fiber
950 popProvider(workInProgress);
1223 - bubbleProperties(workInProgress);
951 return null;
952 case IncompleteClassComponent: {
953 // Same as class component case. I put it down here so that the tags are
@@ -1229,7 +956,6 @@ function completeWork(
956 if (isLegacyContextProvider(Component)) {
957 popLegacyContext(workInProgress);
958 }
1232 - bubbleProperties(workInProgress);
959 return null;
960 }
961 case SuspenseListComponent: {
@@ -1241,7 +967,6 @@ function completeWork(
967 if (renderState === null) {
968 // We're running in the default, "independent" mode.
969 // We don't do anything in this mode.
1244 - bubbleProperties(workInProgress);
970 return null;
971 }
972
@@ -1294,8 +1019,12 @@ function completeWork(
1019
1020 // Rerender the whole list, but this time, we'll force fallbacks
1021 // to stay in place.
1022 + // Reset the effect list before doing the second pass since that's now invalid.
1023 + if (renderState.lastEffect === null) {
1024 + workInProgress.firstEffect = null;
1025 + }
1026 + workInProgress.lastEffect = renderState.lastEffect;
1027 // Reset the child fibers to their original state.
1298 - workInProgress.subtreeFlags = NoFlags;
1028 resetChildFibers(workInProgress, renderLanes);
1029
1030 // Set up the Suspense Context to force suspense and immediately
@@ -1307,7 +1036,6 @@ function completeWork(
1036 ForceSuspenseFallback,
1037 ),
1038 );
1310 - // Don't bubble properties in this case.
1039 return workInProgress.child;
1040 }
1041 row = row.sibling;
@@ -1364,8 +1092,16 @@ function completeWork(
1092 !renderedTail.alternate &&
1093 !getIsHydrating() // We don't cut it if we're hydrating.
1094 ) {
1095 + // We need to delete the row we just rendered.
1096 + // Reset the effect list to what it was before we rendered this
1097 + // child. The nested children have already appended themselves.
1098 + const lastEffect = (workInProgress.lastEffect =
1099 + renderState.lastEffect);
1100 + // Remove any effects that were appended after this point.
1101 + if (lastEffect !== null) {
1102 + lastEffect.nextEffect = null;
1103 + }
1104 // We're done.
1368 - bubbleProperties(workInProgress);
1105 return null;
1106 }
1107 } else if (
@@ -1385,10 +1121,13 @@ function completeWork(
1121 cutOffTailIfNeeded(renderState, false);
1122
1123 // Since nothing actually suspended, there will nothing to ping this
1388 - // to get it started back up to attempt the next item. If we can show
1389 - // them, then they really have the same priority as this render.
1390 - // So we'll pick it back up the very next render pass once we've had
1391 - // an opportunity to yield for paint.
1124 + // to get it started back up to attempt the next item. While in terms
1125 + // of priority this work has the same priority as this current render,
1126 + // it's not part of the same transition once the transition has
1127 + // committed. If it's sync, we still want to yield so that it can be
1128 + // painted. Conceptually, this is really the same as pinging.
1129 + // We can use any RetryLane even if it's the one currently rendering
1130 + // since we're leaving it behind on this node.
1131 workInProgress.lanes = SomeRetryLane;
1132 if (enableSchedulerTracing) {
1133 markSpawnedWork(SomeRetryLane);
@@ -1420,6 +1159,7 @@ function completeWork(
1159 const next = renderState.tail;
1160 renderState.rendering = next;
1161 renderState.tail = next.sibling;
1162 + renderState.lastEffect = workInProgress.lastEffect;
1163 renderState.renderingStartTime = now();
1164 next.sibling = null;
1165
@@ -1437,10 +1177,8 @@ function completeWork(
1177 }
1178 pushSuspenseContext(workInProgress, suspenseContext);
1179 // Do a pass over the next row.
1440 - // Don't bubble properties in this case.
1180 return next;
1181 }
1443 - bubbleProperties(workInProgress);
1182 return null;
1183 }
1184 case FundamentalComponent: {
@@ -1468,7 +1206,6 @@ function completeWork(
1206 ): any): Instance);
1207 fundamentalInstance.instance = instance;
1208 if (fundamentalImpl.reconcileChildren === false) {
1471 - bubbleProperties(workInProgress);
1209 return null;
1210 }
1211 appendAllChildren(instance, workInProgress, false, false);
@@ -1491,7 +1228,6 @@ function completeWork(
1228 markUpdate(workInProgress);
1229 }
1230 }
1494 - bubbleProperties(workInProgress);
1231 return null;
1232 }
1233 break;
@@ -1514,7 +1250,6 @@ function completeWork(
1250 markRef(workInProgress);
1251 }
1252 }
1517 - bubbleProperties(workInProgress);
1253 return null;
1254 }
1255 break;
@@ -1522,30 +1257,19 @@ function completeWork(
1257 case OffscreenComponent:
1258 case LegacyHiddenComponent: {
1259 popRenderLanes(workInProgress);
1525 - const nextState: OffscreenState | null = workInProgress.memoizedState;
1526 - const nextIsHidden = nextState !== null;
1527 -
1260 if (current !== null) {
1261 + const nextState: OffscreenState | null = workInProgress.memoizedState;
1262 const prevState: OffscreenState | null = current.memoizedState;
1263
1264 const prevIsHidden = prevState !== null;
1265 + const nextIsHidden = nextState !== null;
1266 if (
1267 prevIsHidden !== nextIsHidden &&
1268 newProps.mode !== 'unstable-defer-without-hiding'
1269 ) {
1536 - workInProgress.flags |= Update | Visibility;
1270 + workInProgress.flags |= Update;
1271 }
1272 }
1539 -
1540 - // Don't bubble properties for hidden children.
1541 - if (
1542 - !nextIsHidden ||
1543 - includesSomeLane(subtreeRenderLanes, (OffscreenLane: Lane)) ||
1544 - (workInProgress.mode & ConcurrentMode) === NoMode
1545 - ) {
1546 - bubbleProperties(workInProgress);
1547 - }
1548 -
1273 return null;
1274 }
1275 }
packages/react-reconciler/src/ReactFiberHooks.new.js
+25 -87
@@ -26,16 +26,10 @@ import {
26 enableSchedulingProfiler,
27 enableNewReconciler,
28 decoupleUpdatePriorityFromScheduler,
29 - enableDoubleInvokingEffects,
29 enableUseRefAccessWarning,
30 } from 'shared/ReactFeatureFlags';
31
33 -import {
34 - NoMode,
35 - BlockingMode,
36 - ConcurrentMode,
37 - DebugTracingMode,
38 -} from './ReactTypeOfMode';
32 +import {NoMode, BlockingMode, DebugTracingMode} from './ReactTypeOfMode';
33 import {
34 NoLane,
35 NoLanes,
@@ -54,9 +48,6 @@ import {readContext} from './ReactFiberNewContext.new';
48 import {
49 Update as UpdateEffect,
50 Passive as PassiveEffect,
57 - PassiveStatic as PassiveStaticEffect,
58 - MountLayoutDev as MountLayoutDevEffect,
59 - MountPassiveDev as MountPassiveDevEffect,
51 } from './ReactFiberFlags';
52 import {
53 HasEffect as HookHasEffect,
@@ -475,20 +466,7 @@ export function bailoutHooks(
466 lanes: Lanes,
467 ) {
468 workInProgress.updateQueue = current.updateQueue;
478 - if (
479 - __DEV__ &&
480 - enableDoubleInvokingEffects &&
481 - (workInProgress.mode & (BlockingMode | ConcurrentMode)) !== NoMode
482 - ) {
483 - workInProgress.flags &= ~(
484 - MountPassiveDevEffect |
485 - PassiveEffect |
486 - MountLayoutDevEffect |
487 - UpdateEffect
488 - );
489 - } else {
490 - workInProgress.flags &= ~(PassiveEffect | UpdateEffect);
491 - }
469 + workInProgress.flags &= ~(PassiveEffect | UpdateEffect);
470 current.lanes = removeLanes(current.lanes, lanes);
471 }
472
@@ -1320,30 +1298,16 @@ function mountEffect(
1298 ): void {
1299 if (__DEV__) {
1300 // $FlowExpectedError - jest isn't a global, and isn't recognized outside of tests
1323 - if (typeof jest !== 'undefined') {
1301 + if ('undefined' !== typeof jest) {
1302 warnIfNotCurrentlyActingEffectsInDEV(currentlyRenderingFiber);
1303 }
1304 }
1327 -
1328 - if (
1329 - __DEV__ &&
1330 - enableDoubleInvokingEffects &&
1331 - (currentlyRenderingFiber.mode & (BlockingMode | ConcurrentMode)) !== NoMode
1332 - ) {
1333 - return mountEffectImpl(
1334 - MountPassiveDevEffect | PassiveEffect | PassiveStaticEffect,
1335 - HookPassive,
1336 - create,
1337 - deps,
1338 - );
1339 - } else {
1340 - return mountEffectImpl(
1341 - PassiveEffect | PassiveStaticEffect,
1342 - HookPassive,
1343 - create,
1344 - deps,
1345 - );
1346 - }
1305 + return mountEffectImpl(
1306 + UpdateEffect | PassiveEffect,
1307 + HookPassive,
1308 + create,
1309 + deps,
1310 + );
1311 }
1312
1313 function updateEffect(
@@ -1352,31 +1316,23 @@ function updateEffect(
1316 ): void {
1317 if (__DEV__) {
1318 // $FlowExpectedError - jest isn't a global, and isn't recognized outside of tests
1355 - if (typeof jest !== 'undefined') {
1319 + if ('undefined' !== typeof jest) {
1320 warnIfNotCurrentlyActingEffectsInDEV(currentlyRenderingFiber);
1321 }
1322 }
1359 - return updateEffectImpl(PassiveEffect, HookPassive, create, deps);
1323 + return updateEffectImpl(
1324 + UpdateEffect | PassiveEffect,
1325 + HookPassive,
1326 + create,
1327 + deps,
1328 + );
1329 }
1330
1331 function mountLayoutEffect(
1332 create: () => (() => void) | void,
1333 deps: Array<mixed> | void | null,
1334 ): void {
1366 - if (
1367 - __DEV__ &&
1368 - enableDoubleInvokingEffects &&
1369 - (currentlyRenderingFiber.mode & (BlockingMode | ConcurrentMode)) !== NoMode
1370 - ) {
1371 - return mountEffectImpl(
1372 - MountLayoutDevEffect | UpdateEffect,
1373 - HookLayout,
1374 - create,
1375 - deps,
1376 - );
1377 - } else {
1378 - return mountEffectImpl(UpdateEffect, HookLayout, create, deps);
1379 - }
1335 + return mountEffectImpl(UpdateEffect, HookLayout, create, deps);
1336 }
1337
1338 function updateLayoutEffect(
@@ -1435,25 +1391,12 @@ function mountImperativeHandle<T>(
1391 const effectDeps =
1392 deps !== null && deps !== undefined ? deps.concat([ref]) : null;
1393
1438 - if (
1439 - __DEV__ &&
1440 - enableDoubleInvokingEffects &&
1441 - (currentlyRenderingFiber.mode & (BlockingMode | ConcurrentMode)) !== NoMode
1442 - ) {
1443 - return mountEffectImpl(
1444 - MountLayoutDevEffect | UpdateEffect,
1445 - HookLayout,
1446 - imperativeHandleEffect.bind(null, create, ref),
1447 - effectDeps,
1448 - );
1449 - } else {
1450 - return mountEffectImpl(
1451 - UpdateEffect,
1452 - HookLayout,
1453 - imperativeHandleEffect.bind(null, create, ref),
1454 - effectDeps,
1455 - );
1456 - }
1394 + return mountEffectImpl(
1395 + UpdateEffect,
1396 + HookLayout,
1397 + imperativeHandleEffect.bind(null, create, ref),
1398 + effectDeps,
1399 + );
1400 }
1401
1402 function updateImperativeHandle<T>(
@@ -1734,12 +1677,7 @@ function mountOpaqueIdentifier(): OpaqueIDType | void {
1677 const setId = mountState(id)[1];
1678
1679 if ((currentlyRenderingFiber.mode & BlockingMode) === NoMode) {
1737 - if (__DEV__ && enableDoubleInvokingEffects) {
1738 - currentlyRenderingFiber.flags |=
1739 - MountPassiveDevEffect | PassiveEffect | PassiveStaticEffect;
1740 - } else {
1741 - currentlyRenderingFiber.flags |= PassiveEffect | PassiveStaticEffect;
1742 - }
1680 + currentlyRenderingFiber.flags |= UpdateEffect | PassiveEffect;
1681 pushEffect(
1682 HookHasEffect | HookPassive,
1683 () => {
@@ -1855,7 +1793,7 @@ function dispatchAction<S, A>(
1793 }
1794 if (__DEV__) {
1795 // $FlowExpectedError - jest isn't a global, and isn't recognized outside of tests
1858 - if (typeof jest !== 'undefined') {
1796 + if ('undefined' !== typeof jest) {
1797 warnIfNotScopedWithMatchingAct(fiber);
1798 warnIfNotCurrentlyActingUpdatesInDev(fiber);
1799 }
packages/react-reconciler/src/ReactFiberHydrationContext.new.js
+11 -7
@@ -24,7 +24,7 @@ import {
24 HostRoot,
25 SuspenseComponent,
26 } from './ReactWorkTags';
27 -import {Deletion, Hydrating, Placement} from './ReactFiberFlags';
27 +import {Deletion, Placement, Hydrating} from './ReactFiberFlags';
28 import invariant from 'shared/invariant';
29
30 import {
@@ -124,14 +124,18 @@ function deleteHydratableInstance(
124 const childToDelete = createFiberFromHostInstanceForDeletion();
125 childToDelete.stateNode = instance;
126 childToDelete.return = returnFiber;
127 + childToDelete.flags = Deletion;
128
128 - const deletions = returnFiber.deletions;
129 - if (deletions === null) {
130 - returnFiber.deletions = [childToDelete];
131 - // TODO (effects) Rename this to better reflect its new usage (e.g. ChildDeletions)
132 - returnFiber.flags |= Deletion;
129 + // This might seem like it belongs on progressedFirstDeletion. However,
130 + // these children are not part of the reconciliation list of children.
131 + // Even if we abort and rereconcile the children, that will try to hydrate
132 + // again and the nodes are still in the host tree so these will be
133 + // recreated.
134 + if (returnFiber.lastEffect !== null) {
135 + returnFiber.lastEffect.nextEffect = childToDelete;
136 + returnFiber.lastEffect = childToDelete;
137 } else {
134 - deletions.push(childToDelete);
138 + returnFiber.firstEffect = returnFiber.lastEffect = childToDelete;
139 }
140 }
141
packages/react-reconciler/src/ReactFiberSuspenseComponent.new.js
+3
@@ -60,6 +60,9 @@ export type SuspenseListRenderState = {|
60 tail: null | Fiber,
61 // Tail insertions setting.
62 tailMode: SuspenseListTailMode,
63 + // Last Effect before we rendered the "rendering" item.
64 + // Used to remove new effects added by the rendered item.
65 + lastEffect: null | Fiber,
66 |};
67
68 export function shouldCaptureSuspense(
packages/react-reconciler/src/ReactFiberThrow.new.js
+2
@@ -185,6 +185,8 @@ function throwException(
185 ) {
186 // The source fiber did not complete.
187 sourceFiber.flags |= Incomplete;
188 + // Its effect list is no longer valid.
189 + sourceFiber.firstEffect = sourceFiber.lastEffect = null;
190
191 if (
192 value !== null &&
packages/react-reconciler/src/ReactFiberWorkLoop.new.js
+655 -114
@@ -13,14 +13,15 @@ import type {Lanes, Lane} from './ReactFiberLane';
13 import type {ReactPriorityLevel} from './ReactInternalTypes';
14 import type {Interaction} from 'scheduler/src/Tracing';
15 import type {SuspenseState} from './ReactFiberSuspenseComponent.new';
16 +import type {Effect as HookEffect} from './ReactFiberHooks.new';
17 import type {StackCursor} from './ReactFiberStack.new';
17 -import type {FunctionComponentUpdateQueue} from './ReactFiberHooks.new';
18
19 import {
20 warnAboutDeprecatedLifecycles,
21 enableSuspenseServerRenderer,
22 replayFailedUnitOfWorkWithInvokeGuardedCallback,
23 enableProfilerTimer,
24 + enableProfilerCommitHooks,
25 enableProfilerNestedUpdatePhase,
26 enableProfilerNestedUpdateScheduledHook,
27 enableSchedulerTracing,
@@ -29,7 +30,7 @@ import {
30 decoupleUpdatePriorityFromScheduler,
31 enableDebugTracing,
32 enableSchedulingProfiler,
32 - enableDoubleInvokingEffects,
33 + enableScopeAPI,
34 } from 'shared/ReactFeatureFlags';
35 import ReactSharedInternals from 'shared/ReactSharedInternals';
36 import invariant from 'shared/invariant';
@@ -49,10 +50,6 @@ import {
50 flushSyncCallbackQueue,
51 scheduleSyncCallback,
52 } from './SchedulerWithReactIntegration.new';
52 -import {
53 - NoFlags as NoHookEffect,
54 - Passive as HookPassive,
55 -} from './ReactHookEffectTags';
53 import {
54 logCommitStarted,
55 logCommitStopped,
@@ -81,11 +78,13 @@ import * as Scheduler from 'scheduler';
78 import {__interactionsRef, __subscriberRef} from 'scheduler/tracing';
79
80 import {
81 + prepareForCommit,
82 resetAfterCommit,
83 scheduleTimeout,
84 cancelTimeout,
85 noTimeout,
86 warnsIfNotActing,
87 + beforeActiveInstanceBlur,
88 afterActiveInstanceBlur,
89 clearContainer,
90 } from './ReactFiberHostConfig';
@@ -111,20 +110,29 @@ import {
110 ForwardRef,
111 MemoComponent,
112 SimpleMemoComponent,
113 + OffscreenComponent,
114 + LegacyHiddenComponent,
115 + ScopeComponent,
116 Profiler,
117 } from './ReactWorkTags';
118 import {LegacyRoot} from './ReactRootTags';
119 import {
120 NoFlags,
121 + PerformedWork,
122 Placement,
120 - PassiveStatic,
123 + Update,
124 + PlacementAndUpdate,
125 + Deletion,
126 + Ref,
127 + ContentReset,
128 + Snapshot,
129 + Callback,
130 + Passive,
131 + PassiveUnmountPendingDev,
132 Incomplete,
133 HostEffectMask,
134 Hydrating,
124 - BeforeMutationMask,
125 - MutationMask,
126 - LayoutMask,
127 - PassiveMask,
135 + HydratingAndUpdate,
136 } from './ReactFiberFlags';
137 import {
138 NoLanePriority,
@@ -136,6 +144,7 @@ import {
144 NoLane,
145 SyncLane,
146 SyncBatchedLane,
147 + OffscreenLane,
148 NoTimestamp,
149 findUpdateLane,
150 findTransitionLane,
@@ -175,12 +184,16 @@ import {
184 createClassErrorUpdate,
185 } from './ReactFiberThrow.new';
186 import {
178 - commitBeforeMutationEffects,
179 - commitMutationEffects,
180 - commitLayoutEffects,
181 - commitPassiveMountEffects,
182 - commitPassiveUnmountEffects,
183 - commitDoubleInvokeEffectsInDEV,
187 + commitBeforeMutationLifeCycles as commitBeforeMutationEffectOnFiber,
188 + commitLifeCycles as commitLayoutEffectOnFiber,
189 + commitPlacement,
190 + commitWork,
191 + commitDeletion,
192 + commitDetachRef,
193 + commitAttachRef,
194 + commitPassiveEffectDurations,
195 + commitResetTextContent,
196 + isSuspenseBoundaryBeingHidden,
197 } from './ReactFiberCommitWork.new';
198 import {enqueueUpdate} from './ReactUpdateQueue.new';
199 import {resetContextDependencies} from './ReactFiberNewContext.new';
@@ -199,7 +212,9 @@ import {
212 import {
213 markNestedUpdateScheduled,
214 recordCommitTime,
215 + recordPassiveEffectDuration,
216 resetNestedUpdateFlag,
217 + startPassiveEffectTimer,
218 startProfilerTimer,
219 stopProfilerTimerIfRunningAndRecordDelta,
220 syncNestedUpdateFlag,
@@ -224,6 +239,7 @@ import {onCommitRoot as onCommitRootTestSelector} from './ReactTestSelectors';
239
240 // Used by `act`
241 import enqueueTask from 'shared/enqueueTask';
242 +import {doesFiberContain} from './ReactFiberTreeReflection';
243
244 const ceil = Math.ceil;
245
@@ -261,10 +277,6 @@ let workInProgress: Fiber | null = null;
277 // The lanes we're rendering
278 let workInProgressRootRenderLanes: Lanes = NoLanes;
279
264 -// Only used when enableProfilerNestedUpdateScheduledHook is true;
265 -// to track which root is currently committing layout effects.
266 -let rootCommittingMutationOrLayoutEffects: FiberRoot | null = null;
267 -
280 // Stack that allows components to change the render lanes for its subtree
281 // This is a superset of the lanes we started working on at the root. The only
282 // case where it's different from `workInProgressRootRenderLanes` is when we
@@ -273,7 +285,7 @@ let rootCommittingMutationOrLayoutEffects: FiberRoot | null = null;
285 //
286 // Most things in the work loop should deal with workInProgressRootRenderLanes.
287 // Most things in begin/complete phases should deal with subtreeRenderLanes.
276 -export let subtreeRenderLanes: Lanes = NoLanes;
288 +let subtreeRenderLanes: Lanes = NoLanes;
289 const subtreeRenderLanesCursor: StackCursor<Lanes> = createCursor(NoLanes);
290
291 // Whether to root completed, errored, suspended, etc.
@@ -315,13 +327,22 @@ export function getRenderTargetTime(): number {
327 return workInProgressRootRenderTargetTime;
328 }
329
330 +let nextEffect: Fiber | null = null;
331 let hasUncaughtError = false;
332 let firstUncaughtError = null;
333 let legacyErrorBoundariesThatAlreadyFailed: Set<mixed> | null = null;
334
335 +// Only used when enableProfilerNestedUpdateScheduledHook is true;
336 +// to track which root is currently committing layout effects.
337 +let rootCommittingMutationOrLayoutEffects: FiberRoot | null = null;
338 +
339 +let rootDoesHavePassiveEffects: boolean = false;
340 let rootWithPendingPassiveEffects: FiberRoot | null = null;
341 let pendingPassiveEffectsRenderPriority: ReactPriorityLevel = NoSchedulerPriority;
342 let pendingPassiveEffectsLanes: Lanes = NoLanes;
343 +let pendingPassiveHookEffectsMount: Array<HookEffect | Fiber> = [];
344 +let pendingPassiveHookEffectsUnmount: Array<HookEffect | Fiber> = [];
345 +let pendingPassiveProfilerEffects: Array<Fiber> = [];
346
347 let rootsWithPendingDiscreteUpdates: Set<FiberRoot> | null = null;
348
@@ -351,6 +372,9 @@ let currentEventPendingLanes: Lanes = NoLanes;
372 // We warn about state updates for unmounted components differently in this case.
373 let isFlushingPassiveEffects = false;
374
375 +let focusedInstanceHandle: null | Fiber = null;
376 +let shouldFireAfterActiveInstanceBlur: boolean = false;
377 +
378 export function getWorkInProgressRoot(): FiberRoot | null {
379 return workInProgressRoot;
380 }
@@ -1717,6 +1741,47 @@ function completeUnitOfWork(unitOfWork: Fiber): void {
1741 workInProgress = next;
1742 return;
1743 }
1744 +
1745 + resetChildLanes(completedWork);
1746 +
1747 + if (
1748 + returnFiber !== null &&
1749 + // Do not append effects to parents if a sibling failed to complete
1750 + (returnFiber.flags & Incomplete) === NoFlags
1751 + ) {
1752 + // Append all the effects of the subtree and this fiber onto the effect
1753 + // list of the parent. The completion order of the children affects the
1754 + // side-effect order.
1755 + if (returnFiber.firstEffect === null) {
1756 + returnFiber.firstEffect = completedWork.firstEffect;
1757 + }
1758 + if (completedWork.lastEffect !== null) {
1759 + if (returnFiber.lastEffect !== null) {
1760 + returnFiber.lastEffect.nextEffect = completedWork.firstEffect;
1761 + }
1762 + returnFiber.lastEffect = completedWork.lastEffect;
1763 + }
1764 +
1765 + // If this fiber had side-effects, we append it AFTER the children's
1766 + // side-effects. We can perform certain side-effects earlier if needed,
1767 + // by doing multiple passes over the effect list. We don't want to
1768 + // schedule our own side-effect on our own list because if end up
1769 + // reusing children we'll schedule this effect onto itself since we're
1770 + // at the end.
1771 + const flags = completedWork.flags;
1772 +
1773 + // Skip both NoWork and PerformedWork tags when creating the effect
1774 + // list. PerformedWork effect is read by React DevTools but shouldn't be
1775 + // committed.
1776 + if (flags > PerformedWork) {
1777 + if (returnFiber.lastEffect !== null) {
1778 + returnFiber.lastEffect.nextEffect = completedWork;
1779 + } else {
1780 + returnFiber.firstEffect = completedWork;
1781 + }
1782 + returnFiber.lastEffect = completedWork;
1783 + }
1784 + }
1785 } else {
1786 // This fiber did not complete because something threw. Pop values off
1787 // the stack without entering the complete phase. If this is a boundary,
@@ -1753,10 +1818,9 @@ function completeUnitOfWork(unitOfWork: Fiber): void {
1818 }
1819
1820 if (returnFiber !== null) {
1756 - // Mark the parent fiber as incomplete
1821 + // Mark the parent fiber as incomplete and clear its effect list.
1822 + returnFiber.firstEffect = returnFiber.lastEffect = null;
1823 returnFiber.flags |= Incomplete;
1758 - returnFiber.subtreeFlags = NoFlags;
1759 - returnFiber.deletions = null;
1824 }
1825 }
1826
@@ -1778,6 +1842,81 @@ function completeUnitOfWork(unitOfWork: Fiber): void {
1842 }
1843 }
1844
1845 +function resetChildLanes(completedWork: Fiber) {
1846 + if (
1847 + // TODO: Move this check out of the hot path by moving `resetChildLanes`
1848 + // to switch statement in `completeWork`.
1849 + (completedWork.tag === LegacyHiddenComponent ||
1850 + completedWork.tag === OffscreenComponent) &&
1851 + completedWork.memoizedState !== null &&
1852 + !includesSomeLane(subtreeRenderLanes, (OffscreenLane: Lane)) &&
1853 + (completedWork.mode & ConcurrentMode) !== NoLanes
1854 + ) {
1855 + // The children of this component are hidden. Don't bubble their
1856 + // expiration times.
1857 + return;
1858 + }
1859 +
1860 + let newChildLanes = NoLanes;
1861 +
1862 + // Bubble up the earliest expiration time.
1863 + if (enableProfilerTimer && (completedWork.mode & ProfileMode) !== NoMode) {
1864 + // In profiling mode, resetChildExpirationTime is also used to reset
1865 + // profiler durations.
1866 + let actualDuration = completedWork.actualDuration;
1867 + let treeBaseDuration = ((completedWork.selfBaseDuration: any): number);
1868 +
1869 + // When a fiber is cloned, its actualDuration is reset to 0. This value will
1870 + // only be updated if work is done on the fiber (i.e. it doesn't bailout).
1871 + // When work is done, it should bubble to the parent's actualDuration. If
1872 + // the fiber has not been cloned though, (meaning no work was done), then
1873 + // this value will reflect the amount of time spent working on a previous
1874 + // render. In that case it should not bubble. We determine whether it was
1875 + // cloned by comparing the child pointer.
1876 + const shouldBubbleActualDurations =
1877 + completedWork.alternate === null ||
1878 + completedWork.child !== completedWork.alternate.child;
1879 +
1880 + let child = completedWork.child;
1881 + while (child !== null) {
1882 + newChildLanes = mergeLanes(
1883 + newChildLanes,
1884 + mergeLanes(child.lanes, child.childLanes),
1885 + );
1886 + if (shouldBubbleActualDurations) {
1887 + actualDuration += child.actualDuration;
1888 + }
1889 + treeBaseDuration += child.treeBaseDuration;
1890 + child = child.sibling;
1891 + }
1892 +
1893 + const isTimedOutSuspense =
1894 + completedWork.tag === SuspenseComponent &&
1895 + completedWork.memoizedState !== null;
1896 + if (isTimedOutSuspense) {
1897 + // Don't count time spent in a timed out Suspense subtree as part of the base duration.
1898 + const primaryChildFragment = completedWork.child;
1899 + if (primaryChildFragment !== null) {
1900 + treeBaseDuration -= ((primaryChildFragment.treeBaseDuration: any): number);
1901 + }
1902 + }
1903 +
1904 + completedWork.actualDuration = actualDuration;
1905 + completedWork.treeBaseDuration = treeBaseDuration;
1906 + } else {
1907 + let child = completedWork.child;
1908 + while (child !== null) {
1909 + newChildLanes = mergeLanes(
1910 + newChildLanes,
1911 + mergeLanes(child.lanes, child.childLanes),
1912 + );
1913 + child = child.sibling;
1914 + }
1915 + }
1916 +
1917 + completedWork.childLanes = newChildLanes;
1918 +}
1919 +
1920 function commitRoot(root) {
1921 const renderPriorityLevel = getCurrentPriorityLevel();
1922 runWithPriority(
@@ -1871,37 +2010,25 @@ function commitRootImpl(root, renderPriorityLevel) {
2010 // times out.
2011 }
2012
1874 - // If there are pending passive effects, schedule a callback to process them.
1875 - // Do this as early as possible, so it is queued before anything else that
1876 - // might get scheduled in the commit phase. (See #16714.)
1877 - const rootDoesHavePassiveEffects =
1878 - (finishedWork.subtreeFlags & PassiveMask) !== NoFlags ||
1879 - (finishedWork.flags & PassiveMask) !== NoFlags;
1880 - if (rootDoesHavePassiveEffects) {
1881 - rootWithPendingPassiveEffects = root;
1882 - pendingPassiveEffectsLanes = lanes;
1883 - pendingPassiveEffectsRenderPriority = renderPriorityLevel;
1884 - scheduleCallback(NormalSchedulerPriority, () => {
1885 - flushPassiveEffects();
1886 - return null;
1887 - });
2013 + // Get the list of effects.
2014 + let firstEffect;
2015 + if (finishedWork.flags > PerformedWork) {
2016 + // A fiber's effect list consists only of its children, not itself. So if
2017 + // the root has an effect, we need to add it to the end of the list. The
2018 + // resulting list is the set that would belong to the root's parent, if it
2019 + // had one; that is, all the effects in the tree including the root.
2020 + if (finishedWork.lastEffect !== null) {
2021 + finishedWork.lastEffect.nextEffect = finishedWork;
2022 + firstEffect = finishedWork.firstEffect;
2023 + } else {
2024 + firstEffect = finishedWork;
2025 + }
2026 + } else {
2027 + // There is no effect on the root.
2028 + firstEffect = finishedWork.firstEffect;
2029 }
2030
1890 - // Check if there are any effects in the whole tree.
1891 - // TODO: This is left over from the effect list implementation, where we had
1892 - // to check for the existence of `firstEffect` to satsify Flow. I think the
1893 - // only other reason this optimization exists is because it affects profiling.
1894 - // Reconsider whether this is necessary.
1895 - const subtreeHasEffects =
1896 - (finishedWork.subtreeFlags &
1897 - (BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !==
1898 - NoFlags;
1899 - const rootHasEffect =
1900 - (finishedWork.flags &
1901 - (BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !==
1902 - NoFlags;
1903 -
1904 - if (subtreeHasEffects || rootHasEffect) {
2031 + if (firstEffect !== null) {
2032 let previousLanePriority;
2033 if (decoupleUpdatePriorityFromScheduler) {
2034 previousLanePriority = getCurrentUpdateLanePriority();
@@ -1922,10 +2049,32 @@ function commitRootImpl(root, renderPriorityLevel) {
2049 // The first phase a "before mutation" phase. We use this phase to read the
2050 // state of the host tree right before we mutate it. This is where
2051 // getSnapshotBeforeUpdate is called.
1925 - const shouldFireAfterActiveInstanceBlur = commitBeforeMutationEffects(
1926 - root,
1927 - finishedWork,
1928 - );
2052 + focusedInstanceHandle = prepareForCommit(root.containerInfo);
2053 + shouldFireAfterActiveInstanceBlur = false;
2054 +
2055 + nextEffect = firstEffect;
2056 + do {
2057 + if (__DEV__) {
2058 + invokeGuardedCallback(null, commitBeforeMutationEffects, null);
2059 + if (hasCaughtError()) {
2060 + invariant(nextEffect !== null, 'Should be working on an effect.');
2061 + const error = clearCaughtError();
2062 + captureCommitPhaseError(nextEffect, error);
2063 + nextEffect = nextEffect.nextEffect;
2064 + }
2065 + } else {
2066 + try {
2067 + commitBeforeMutationEffects();
2068 + } catch (error) {
2069 + invariant(nextEffect !== null, 'Should be working on an effect.');
2070 + captureCommitPhaseError(nextEffect, error);
2071 + nextEffect = nextEffect.nextEffect;
2072 + }
2073 + }
2074 + } while (nextEffect !== null);
2075 +
2076 + // We no longer need to track the active instance fiber
2077 + focusedInstanceHandle = null;
2078
2079 if (enableProfilerTimer) {
2080 // Mark the current commit time to be shared by all Profilers in this
@@ -1934,11 +2083,38 @@ function commitRootImpl(root, renderPriorityLevel) {
2083 }
2084
2085 if (enableProfilerTimer && enableProfilerNestedUpdateScheduledHook) {
2086 + // Track the root here, rather than in commitLayoutEffects(), because of ref setters.
2087 + // Updates scheduled during ref detachment should also be flagged.
2088 rootCommittingMutationOrLayoutEffects = root;
2089 }
2090
2091 // The next phase is the mutation phase, where we mutate the host tree.
1941 - commitMutationEffects(finishedWork, root, renderPriorityLevel);
2092 + nextEffect = firstEffect;
2093 + do {
2094 + if (__DEV__) {
2095 + invokeGuardedCallback(
2096 + null,
2097 + commitMutationEffects,
2098 + null,
2099 + root,
2100 + renderPriorityLevel,
2101 + );
2102 + if (hasCaughtError()) {
2103 + invariant(nextEffect !== null, 'Should be working on an effect.');
2104 + const error = clearCaughtError();
2105 + captureCommitPhaseError(nextEffect, error);
2106 + nextEffect = nextEffect.nextEffect;
2107 + }
2108 + } else {
2109 + try {
2110 + commitMutationEffects(root, renderPriorityLevel);
2111 + } catch (error) {
2112 + invariant(nextEffect !== null, 'Should be working on an effect.');
2113 + captureCommitPhaseError(nextEffect, error);
2114 + nextEffect = nextEffect.nextEffect;
2115 + }
2116 + }
2117 + } while (nextEffect !== null);
2118
2119 if (shouldFireAfterActiveInstanceBlur) {
2120 afterActiveInstanceBlur();
@@ -1954,26 +2130,28 @@ function commitRootImpl(root, renderPriorityLevel) {
2130 // The next phase is the layout phase, where we call effects that read
2131 // the host tree after it's been mutated. The idiomatic use case for this is
2132 // layout, but class component lifecycles also fire here for legacy reasons.
1957 -
1958 - if (__DEV__) {
1959 - if (enableDebugTracing) {
1960 - logLayoutEffectsStarted(lanes);
2133 + nextEffect = firstEffect;
2134 + do {
2135 + if (__DEV__) {
2136 + invokeGuardedCallback(null, commitLayoutEffects, null, root, lanes);
2137 + if (hasCaughtError()) {
2138 + invariant(nextEffect !== null, 'Should be working on an effect.');
2139 + const error = clearCaughtError();
2140 + captureCommitPhaseError(nextEffect, error);
2141 + nextEffect = nextEffect.nextEffect;
2142 + }
2143 + } else {
2144 + try {
2145 + commitLayoutEffects(root, lanes);
2146 + } catch (error) {
2147 + invariant(nextEffect !== null, 'Should be working on an effect.');
2148 + captureCommitPhaseError(nextEffect, error);
2149 + nextEffect = nextEffect.nextEffect;
2150 + }
2151 }
1962 - }
1963 - if (enableSchedulingProfiler) {
1964 - markLayoutEffectsStarted(lanes);
1965 - }
2152 + } while (nextEffect !== null);
2153
1967 - commitLayoutEffects(finishedWork, root);
1968 -
1969 - if (__DEV__) {
1970 - if (enableDebugTracing) {
1971 - logLayoutEffectsStopped();
1972 - }
1973 - }
1974 - if (enableSchedulingProfiler) {
1975 - markLayoutEffectsStopped();
1976 - }
2154 + nextEffect = null;
2155
2156 if (enableProfilerTimer && enableProfilerNestedUpdateScheduledHook) {
2157 rootCommittingMutationOrLayoutEffects = null;
@@ -2003,6 +2181,30 @@ function commitRootImpl(root, renderPriorityLevel) {
2181 }
2182 }
2183
2184 + const rootDidHavePassiveEffects = rootDoesHavePassiveEffects;
2185 +
2186 + if (rootDoesHavePassiveEffects) {
2187 + // This commit has passive effects. Stash a reference to them. But don't
2188 + // schedule a callback until after flushing layout work.
2189 + rootDoesHavePassiveEffects = false;
2190 + rootWithPendingPassiveEffects = root;
2191 + pendingPassiveEffectsLanes = lanes;
2192 + pendingPassiveEffectsRenderPriority = renderPriorityLevel;
2193 + } else {
2194 + // We are done with the effect chain at this point so let's clear the
2195 + // nextEffect pointers to assist with GC. If we have passive effects, we'll
2196 + // clear this in flushPassiveEffects.
2197 + nextEffect = firstEffect;
2198 + while (nextEffect !== null) {
2199 + const nextNextEffect = nextEffect.nextEffect;
2200 + nextEffect.nextEffect = null;
2201 + if (nextEffect.flags & Deletion) {
2202 + detachFiberAfterEffects(nextEffect);
2203 + }
2204 + nextEffect = nextNextEffect;
2205 + }
2206 + }
2207 +
2208 // Read this again, since an effect might have updated it
2209 remainingLanes = root.pendingLanes;
2210
@@ -2028,14 +2230,8 @@ function commitRootImpl(root, renderPriorityLevel) {
2230 legacyErrorBoundariesThatAlreadyFailed = null;
2231 }
2232
2031 - if (__DEV__ && enableDoubleInvokingEffects) {
2032 - if (!rootDoesHavePassiveEffects) {
2033 - commitDoubleInvokeEffectsInDEV(root.current, false);
2034 - }
2035 - }
2036 -
2233 if (enableSchedulerTracing) {
2038 - if (!rootDoesHavePassiveEffects) {
2234 + if (!rootDidHavePassiveEffects) {
2235 // If there are no passive effects, then we can complete the pending interactions.
2236 // Otherwise, we'll wait until after the passive effects are flushed.
2237 // Wait to do this until after remaining work has been scheduled,
@@ -2112,6 +2308,181 @@ function commitRootImpl(root, renderPriorityLevel) {
2308 return null;
2309 }
2310
2311 +function commitBeforeMutationEffects() {
2312 + while (nextEffect !== null) {
2313 + const current = nextEffect.alternate;
2314 +
2315 + if (!shouldFireAfterActiveInstanceBlur && focusedInstanceHandle !== null) {
2316 + if ((nextEffect.flags & Deletion) !== NoFlags) {
2317 + if (doesFiberContain(nextEffect, focusedInstanceHandle)) {
2318 + shouldFireAfterActiveInstanceBlur = true;
2319 + beforeActiveInstanceBlur(nextEffect);
2320 + }
2321 + } else {
2322 + // TODO: Move this out of the hot path using a dedicated effect tag.
2323 + if (
2324 + nextEffect.tag === SuspenseComponent &&
2325 + isSuspenseBoundaryBeingHidden(current, nextEffect) &&
2326 + doesFiberContain(nextEffect, focusedInstanceHandle)
2327 + ) {
2328 + shouldFireAfterActiveInstanceBlur = true;
2329 + beforeActiveInstanceBlur(nextEffect);
2330 + }
2331 + }
2332 + }
2333 +
2334 + const flags = nextEffect.flags;
2335 + if ((flags & Snapshot) !== NoFlags) {
2336 + setCurrentDebugFiberInDEV(nextEffect);
2337 +
2338 + commitBeforeMutationEffectOnFiber(current, nextEffect);
2339 +
2340 + resetCurrentDebugFiberInDEV();
2341 + }
2342 + if ((flags & Passive) !== NoFlags) {
2343 + // If there are passive effects, schedule a callback to flush at
2344 + // the earliest opportunity.
2345 + if (!rootDoesHavePassiveEffects) {
2346 + rootDoesHavePassiveEffects = true;
2347 + scheduleCallback(NormalSchedulerPriority, () => {
2348 + flushPassiveEffects();
2349 + return null;
2350 + });
2351 + }
2352 + }
2353 + nextEffect = nextEffect.nextEffect;
2354 + }
2355 +}
2356 +
2357 +function commitMutationEffects(root: FiberRoot, renderPriorityLevel) {
2358 + // TODO: Should probably move the bulk of this function to commitWork.
2359 + while (nextEffect !== null) {
2360 + setCurrentDebugFiberInDEV(nextEffect);
2361 +
2362 + const flags = nextEffect.flags;
2363 +
2364 + if (flags & ContentReset) {
2365 + commitResetTextContent(nextEffect);
2366 + }
2367 +
2368 + if (flags & Ref) {
2369 + const current = nextEffect.alternate;
2370 + if (current !== null) {
2371 + commitDetachRef(current);
2372 + }
2373 + if (enableScopeAPI) {
2374 + // TODO: This is a temporary solution that allowed us to transition away
2375 + // from React Flare on www.
2376 + if (nextEffect.tag === ScopeComponent) {
2377 + commitAttachRef(nextEffect);
2378 + }
2379 + }
2380 + }
2381 +
2382 + // The following switch statement is only concerned about placement,
2383 + // updates, and deletions. To avoid needing to add a case for every possible
2384 + // bitmap value, we remove the secondary effects from the effect tag and
2385 + // switch on that value.
2386 + const primaryFlags = flags & (Placement | Update | Deletion | Hydrating);
2387 + switch (primaryFlags) {
2388 + case Placement: {
2389 + commitPlacement(nextEffect);
2390 + // Clear the "placement" from effect tag so that we know that this is
2391 + // inserted, before any life-cycles like componentDidMount gets called.
2392 + // TODO: findDOMNode doesn't rely on this any more but isMounted does
2393 + // and isMounted is deprecated anyway so we should be able to kill this.
2394 + nextEffect.flags &= ~Placement;
2395 + break;
2396 + }
2397 + case PlacementAndUpdate: {
2398 + // Placement
2399 + commitPlacement(nextEffect);
2400 + // Clear the "placement" from effect tag so that we know that this is
2401 + // inserted, before any life-cycles like componentDidMount gets called.
2402 + nextEffect.flags &= ~Placement;
2403 +
2404 + // Update
2405 + const current = nextEffect.alternate;
2406 + commitWork(current, nextEffect);
2407 + break;
2408 + }
2409 + case Hydrating: {
2410 + nextEffect.flags &= ~Hydrating;
2411 + break;
2412 + }
2413 + case HydratingAndUpdate: {
2414 + nextEffect.flags &= ~Hydrating;
2415 +
2416 + // Update
2417 + const current = nextEffect.alternate;
2418 + commitWork(current, nextEffect);
2419 + break;
2420 + }
2421 + case Update: {
2422 + const current = nextEffect.alternate;
2423 + commitWork(current, nextEffect);
2424 + break;
2425 + }
2426 + case Deletion: {
2427 + commitDeletion(root, nextEffect, renderPriorityLevel);
2428 + break;
2429 + }
2430 + }
2431 +
2432 + resetCurrentDebugFiberInDEV();
2433 + nextEffect = nextEffect.nextEffect;
2434 + }
2435 +}
2436 +
2437 +function commitLayoutEffects(root: FiberRoot, committedLanes: Lanes) {
2438 + if (__DEV__) {
2439 + if (enableDebugTracing) {
2440 + logLayoutEffectsStarted(committedLanes);
2441 + }
2442 + }
2443 +
2444 + if (enableSchedulingProfiler) {
2445 + markLayoutEffectsStarted(committedLanes);
2446 + }
2447 +
2448 + // TODO: Should probably move the bulk of this function to commitWork.
2449 + while (nextEffect !== null) {
2450 + setCurrentDebugFiberInDEV(nextEffect);
2451 +
2452 + const flags = nextEffect.flags;
2453 +
2454 + if (flags & (Update | Callback)) {
2455 + const current = nextEffect.alternate;
2456 + commitLayoutEffectOnFiber(root, current, nextEffect, committedLanes);
2457 + }
2458 +
2459 + if (enableScopeAPI) {
2460 + // TODO: This is a temporary solution that allowed us to transition away
2461 + // from React Flare on www.
2462 + if (flags & Ref && nextEffect.tag !== ScopeComponent) {
2463 + commitAttachRef(nextEffect);
2464 + }
2465 + } else {
2466 + if (flags & Ref) {
2467 + commitAttachRef(nextEffect);
2468 + }
2469 + }
2470 +
2471 + resetCurrentDebugFiberInDEV();
2472 + nextEffect = nextEffect.nextEffect;
2473 + }
2474 +
2475 + if (__DEV__) {
2476 + if (enableDebugTracing) {
2477 + logLayoutEffectsStopped();
2478 + }
2479 + }
2480 +
2481 + if (enableSchedulingProfiler) {
2482 + markLayoutEffectsStopped();
2483 + }
2484 +}
2485 +
2486 export function flushPassiveEffects(): boolean {
2487 // Returns whether passive effects were flushed.
2488 if (pendingPassiveEffectsRenderPriority !== NoSchedulerPriority) {
@@ -2137,6 +2508,59 @@ export function flushPassiveEffects(): boolean {
2508 return false;
2509 }
2510
2511 +export function enqueuePendingPassiveProfilerEffect(fiber: Fiber): void {
2512 + if (enableProfilerTimer && enableProfilerCommitHooks) {
2513 + pendingPassiveProfilerEffects.push(fiber);
2514 + if (!rootDoesHavePassiveEffects) {
2515 + rootDoesHavePassiveEffects = true;
2516 + scheduleCallback(NormalSchedulerPriority, () => {
2517 + flushPassiveEffects();
2518 + return null;
2519 + });
2520 + }
2521 + }
2522 +}
2523 +
2524 +export function enqueuePendingPassiveHookEffectMount(
2525 + fiber: Fiber,
2526 + effect: HookEffect,
2527 +): void {
2528 + pendingPassiveHookEffectsMount.push(effect, fiber);
2529 + if (!rootDoesHavePassiveEffects) {
2530 + rootDoesHavePassiveEffects = true;
2531 + scheduleCallback(NormalSchedulerPriority, () => {
2532 + flushPassiveEffects();
2533 + return null;
2534 + });
2535 + }
2536 +}
2537 +
2538 +export function enqueuePendingPassiveHookEffectUnmount(
2539 + fiber: Fiber,
2540 + effect: HookEffect,
2541 +): void {
2542 + pendingPassiveHookEffectsUnmount.push(effect, fiber);
2543 + if (__DEV__) {
2544 + fiber.flags |= PassiveUnmountPendingDev;
2545 + const alternate = fiber.alternate;
2546 + if (alternate !== null) {
2547 + alternate.flags |= PassiveUnmountPendingDev;
2548 + }
2549 + }
2550 + if (!rootDoesHavePassiveEffects) {
2551 + rootDoesHavePassiveEffects = true;
2552 + scheduleCallback(NormalSchedulerPriority, () => {
2553 + flushPassiveEffects();
2554 + return null;
2555 + });
2556 + }
2557 +}
2558 +
2559 +function invokePassiveEffectCreate(effect: HookEffect): void {
2560 + const create = effect.create;
2561 + effect.destroy = create();
2562 +}
2563 +
2564 function flushPassiveEffectsImpl() {
2565 if (rootWithPendingPassiveEffects === null) {
2566 return false;
@@ -2176,25 +2600,137 @@ function flushPassiveEffectsImpl() {
2600 // e.g. a destroy function in one component may unintentionally override a ref
2601 // value set by a create function in another component.
2602 // Layout effects have the same constraint.
2179 - commitPassiveUnmountEffects(root.current);
2180 - commitPassiveMountEffects(root, root.current);
2603
2182 - if (__DEV__) {
2183 - if (enableDebugTracing) {
2184 - logPassiveEffectsStopped();
2604 + // First pass: Destroy stale passive effects.
2605 + const unmountEffects = pendingPassiveHookEffectsUnmount;
2606 + pendingPassiveHookEffectsUnmount = [];
2607 + for (let i = 0; i < unmountEffects.length; i += 2) {
2608 + const effect = ((unmountEffects[i]: any): HookEffect);
2609 + const fiber = ((unmountEffects[i + 1]: any): Fiber);
2610 + const destroy = effect.destroy;
2611 + effect.destroy = undefined;
2612 +
2613 + if (__DEV__) {
2614 + fiber.flags &= ~PassiveUnmountPendingDev;
2615 + const alternate = fiber.alternate;
2616 + if (alternate !== null) {
2617 + alternate.flags &= ~PassiveUnmountPendingDev;
2618 + }
2619 }
2186 - }
2620
2188 - if (enableSchedulingProfiler) {
2189 - markPassiveEffectsStopped();
2621 + if (typeof destroy === 'function') {
2622 + if (__DEV__) {
2623 + setCurrentDebugFiberInDEV(fiber);
2624 + if (
2625 + enableProfilerTimer &&
2626 + enableProfilerCommitHooks &&
2627 + fiber.mode & ProfileMode
2628 + ) {
2629 + startPassiveEffectTimer();
2630 + invokeGuardedCallback(null, destroy, null);
2631 + recordPassiveEffectDuration(fiber);
2632 + } else {
2633 + invokeGuardedCallback(null, destroy, null);
2634 + }
2635 + if (hasCaughtError()) {
2636 + invariant(fiber !== null, 'Should be working on an effect.');
2637 + const error = clearCaughtError();
2638 + captureCommitPhaseError(fiber, error);
2639 + }
2640 + resetCurrentDebugFiberInDEV();
2641 + } else {
2642 + try {
2643 + if (
2644 + enableProfilerTimer &&
2645 + enableProfilerCommitHooks &&
2646 + fiber.mode & ProfileMode
2647 + ) {
2648 + try {
2649 + startPassiveEffectTimer();
2650 + destroy();
2651 + } finally {
2652 + recordPassiveEffectDuration(fiber);
2653 + }
2654 + } else {
2655 + destroy();
2656 + }
2657 + } catch (error) {
2658 + invariant(fiber !== null, 'Should be working on an effect.');
2659 + captureCommitPhaseError(fiber, error);
2660 + }
2661 + }
2662 + }
2663 + }
2664 + // Second pass: Create new passive effects.
2665 + const mountEffects = pendingPassiveHookEffectsMount;
2666 + pendingPassiveHookEffectsMount = [];
2667 + for (let i = 0; i < mountEffects.length; i += 2) {
2668 + const effect = ((mountEffects[i]: any): HookEffect);
2669 + const fiber = ((mountEffects[i + 1]: any): Fiber);
2670 + if (__DEV__) {
2671 + setCurrentDebugFiberInDEV(fiber);
2672 + if (
2673 + enableProfilerTimer &&
2674 + enableProfilerCommitHooks &&
2675 + fiber.mode & ProfileMode
2676 + ) {
2677 + startPassiveEffectTimer();
2678 + invokeGuardedCallback(null, invokePassiveEffectCreate, null, effect);
2679 + recordPassiveEffectDuration(fiber);
2680 + } else {
2681 + invokeGuardedCallback(null, invokePassiveEffectCreate, null, effect);
2682 + }
2683 + if (hasCaughtError()) {
2684 + invariant(fiber !== null, 'Should be working on an effect.');
2685 + const error = clearCaughtError();
2686 + captureCommitPhaseError(fiber, error);
2687 + }
2688 + resetCurrentDebugFiberInDEV();
2689 + } else {
2690 + try {
2691 + const create = effect.create;
2692 + if (
2693 + enableProfilerTimer &&
2694 + enableProfilerCommitHooks &&
2695 + fiber.mode & ProfileMode
2696 + ) {
2697 + try {
2698 + startPassiveEffectTimer();
2699 + effect.destroy = create();
2700 + } finally {
2701 + recordPassiveEffectDuration(fiber);
2702 + }
2703 + } else {
2704 + effect.destroy = create();
2705 + }
2706 + } catch (error) {
2707 + invariant(fiber !== null, 'Should be working on an effect.');
2708 + captureCommitPhaseError(fiber, error);
2709 + }
2710 + }
2711 }
2712
2192 - if (__DEV__ && enableDoubleInvokingEffects) {
2193 - commitDoubleInvokeEffectsInDEV(root.current, true);
2713 + // Note: This currently assumes there are no passive effects on the root fiber
2714 + // because the root is not part of its own effect list.
2715 + // This could change in the future.
2716 + let effect = root.current.firstEffect;
2717 + while (effect !== null) {
2718 + const nextNextEffect = effect.nextEffect;
2719 + // Remove nextEffect pointer to assist GC
2720 + effect.nextEffect = null;
2721 + if (effect.flags & Deletion) {
2722 + detachFiberAfterEffects(effect);
2723 + }
2724 + effect = nextNextEffect;
2725 }
2726
2196 - if (__DEV__) {
2197 - isFlushingPassiveEffects = false;
2727 + if (enableProfilerTimer && enableProfilerCommitHooks) {
2728 + const profilerEffects = pendingPassiveProfilerEffects;
2729 + pendingPassiveProfilerEffects = [];
2730 + for (let i = 0; i < profilerEffects.length; i++) {
2731 + const fiber = ((profilerEffects[i]: any): Fiber);
2732 + commitPassiveEffectDurations(root, fiber);
2733 + }
2734 }
2735
2736 if (enableSchedulerTracing) {
@@ -2202,6 +2738,20 @@ function flushPassiveEffectsImpl() {
2738 finishPendingInteractions(root, lanes);
2739 }
2740
2741 + if (__DEV__) {
2742 + isFlushingPassiveEffects = false;
2743 + }
2744 +
2745 + if (__DEV__) {
2746 + if (enableDebugTracing) {
2747 + logPassiveEffectsStopped();
2748 + }
2749 + }
2750 +
2751 + if (enableSchedulingProfiler) {
2752 + markPassiveEffectsStopped();
2753 + }
2754 +
2755 executionContext = prevExecutionContext;
2756
2757 flushSyncCallbackQueue();
@@ -2542,24 +3092,10 @@ function warnAboutUpdateOnUnmountedFiberInDEV(fiber) {
3092 return;
3093 }
3094
2545 - if ((fiber.flags & PassiveStatic) !== NoFlags) {
2546 - const updateQueue: FunctionComponentUpdateQueue | null = (fiber.updateQueue: any);
2547 - if (updateQueue !== null) {
2548 - const lastEffect = updateQueue.lastEffect;
2549 - if (lastEffect !== null) {
2550 - const firstEffect = lastEffect.next;
2551 -
2552 - let effect = firstEffect;
2553 - do {
2554 - if (effect.destroy !== undefined) {
2555 - if ((effect.tag & HookPassive) !== NoHookEffect) {
2556 - return;
2557 - }
2558 - }
2559 - effect = effect.next;
2560 - } while (effect !== firstEffect);
2561 - }
2562 - }
3095 + // If there are pending passive effects unmounts for this Fiber,
3096 + // we can assume that they would have prevented this update.
3097 + if ((fiber.flags & PassiveUnmountPendingDev) !== NoFlags) {
3098 + return;
3099 }
3100
3101 // We show the whole stack but dedupe on the top component's name because
@@ -3249,3 +3785,8 @@ export function act(callback: () => Thenable<mixed>): Thenable<void> {
3785 };
3786 }
3787 }
3788 +
3789 +function detachFiberAfterEffects(fiber: Fiber): void {
3790 + fiber.sibling = null;
3791 + fiber.stateNode = null;
3792 +}
packages/react-reconciler/src/ReactStrictModeWarnings.old.js
+1 -1
@@ -64,7 +64,7 @@ if (__DEV__) {
64 fiber: Fiber,
65 instance: any,
66 ) => {
67 - // Dedup strategy: Warn once per component.
67 + // Dedupe strategy: Warn once per component.
68 if (didWarnAboutUnsafeLifecycles.has(fiber.type)) {
69 return;
70 }
packages/react-reconciler/src/SchedulerWithReactIntegration.new.js
+2 -5
@@ -165,13 +165,13 @@ export function cancelCallback(callbackNode: mixed) {
165 }
166 }
167
168 -export function flushSyncCallbackQueue(): boolean {
168 +export function flushSyncCallbackQueue() {
169 if (immediateQueueCallbackNode !== null) {
170 const node = immediateQueueCallbackNode;
171 immediateQueueCallbackNode = null;
172 Scheduler_cancelCallback(node);
173 }
174 - return flushSyncCallbackQueueImpl();
174 + flushSyncCallbackQueueImpl();
175 }
176
177 function flushSyncCallbackQueueImpl() {
@@ -237,8 +237,5 @@ function flushSyncCallbackQueueImpl() {
237 isFlushingSyncQueue = false;
238 }
239 }
240 - return true;
241 - } else {
242 - return false;
240 }
241 }
packages/react-reconciler/src/__tests__/ReactDoubleInvokeEvents-test.js renamed
+19 -13
@@ -10,7 +10,6 @@
10 'use strict';
11
12 let React;
13 -let ReactFeatureFlags;
13 let ReactTestRenderer;
14 let Scheduler;
15 let act;
@@ -19,13 +18,20 @@ describe('ReactDoubleInvokeEvents', () => {
18 beforeEach(() => {
19 jest.resetModules();
20 React = require('react');
22 - ReactFeatureFlags = require('shared/ReactFeatureFlags');
21 ReactTestRenderer = require('react-test-renderer');
22 Scheduler = require('scheduler');
25 - ReactFeatureFlags.enableDoubleInvokingEffects = __VARIANT__;
23 act = ReactTestRenderer.unstable_concurrentAct;
24 });
25
26 + function supportsDoubleInvokeEffects() {
27 + return gate(
28 + flags =>
29 + flags.build === 'development' &&
30 + flags.enableDoubleInvokingEffects &&
31 + flags.dfsEffectsRefactor,
32 + );
33 + }
34 +
35 it('should not double invoke effects in legacy mode', () => {
36 function App({text}) {
37 React.useEffect(() => {
@@ -73,7 +79,7 @@ describe('ReactDoubleInvokeEvents', () => {
79 });
80 });
81
76 - if (__DEV__ && __VARIANT__) {
82 + if (supportsDoubleInvokeEffects()) {
83 expect(Scheduler).toHaveYielded([
84 'useLayoutEffect mount',
85 'useEffect mount',
@@ -132,7 +138,7 @@ describe('ReactDoubleInvokeEvents', () => {
138 });
139 });
140
135 - if (__DEV__ && __VARIANT__) {
141 + if (supportsDoubleInvokeEffects()) {
142 expect(Scheduler).toHaveYielded([
143 'useEffect One mount',
144 'useEffect Two mount',
@@ -193,7 +199,7 @@ describe('ReactDoubleInvokeEvents', () => {
199 });
200 });
201
196 - if (__DEV__ && __VARIANT__) {
202 + if (supportsDoubleInvokeEffects()) {
203 expect(Scheduler).toHaveYielded([
204 'useLayoutEffect One mount',
205 'useLayoutEffect Two mount',
@@ -250,7 +256,7 @@ describe('ReactDoubleInvokeEvents', () => {
256 });
257 });
258
253 - if (__DEV__ && __VARIANT__) {
259 + if (supportsDoubleInvokeEffects()) {
260 expect(Scheduler).toHaveYielded([
261 'useLayoutEffect mount',
262 'useEffect mount',
@@ -308,7 +314,7 @@ describe('ReactDoubleInvokeEvents', () => {
314 ReactTestRenderer.create(<App />, {unstable_isConcurrent: true});
315 });
316
311 - if (__DEV__ && __VARIANT__) {
317 + if (supportsDoubleInvokeEffects()) {
318 expect(Scheduler).toHaveYielded([
319 'componentDidMount',
320 'componentWillUnmount',
@@ -345,7 +351,7 @@ describe('ReactDoubleInvokeEvents', () => {
351 });
352 });
353
348 - if (__DEV__ && __VARIANT__) {
354 + if (supportsDoubleInvokeEffects()) {
355 expect(Scheduler).toHaveYielded([
356 'componentDidMount',
357 'componentWillUnmount',
@@ -420,7 +426,7 @@ describe('ReactDoubleInvokeEvents', () => {
426 });
427 });
428
423 - if (__DEV__ && __VARIANT__) {
429 + if (supportsDoubleInvokeEffects()) {
430 expect(Scheduler).toHaveYielded([
431 'mount',
432 'useLayoutEffect mount',
@@ -485,7 +491,7 @@ describe('ReactDoubleInvokeEvents', () => {
491 ReactTestRenderer.create(<App />, {unstable_isConcurrent: true});
492 });
493
488 - if (__DEV__ && __VARIANT__) {
494 + if (supportsDoubleInvokeEffects()) {
495 expect(Scheduler).toHaveYielded([
496 'App useLayoutEffect mount',
497 'App useEffect mount',
@@ -505,7 +511,7 @@ describe('ReactDoubleInvokeEvents', () => {
511 _setShowChild(true);
512 });
513
508 - if (__DEV__ && __VARIANT__) {
514 + if (supportsDoubleInvokeEffects()) {
515 expect(Scheduler).toHaveYielded([
516 'App useLayoutEffect unmount',
517 'Child useLayoutEffect mount',
@@ -573,7 +579,7 @@ describe('ReactDoubleInvokeEvents', () => {
579 });
580 });
581
576 - if (__DEV__ && __VARIANT__) {
582 + if (supportsDoubleInvokeEffects()) {
583 expect(Scheduler).toHaveYielded([
584 'componentDidMount',
585 'useLayoutEffect mount',
packages/react/src/__tests__/ReactDOMTracing-test.internal.js
+2 -2
@@ -152,7 +152,7 @@ describe('ReactDOMTracing', () => {
152 onInteractionScheduledWorkCompleted,
153 ).toHaveBeenLastNotifiedOfInteraction(interaction);
154
155 - if (gate(flags => flags.new)) {
155 + if (gate(flags => flags.dfsEffectsRefactor)) {
156 expect(onRender).toHaveBeenCalledTimes(3);
157 } else {
158 // TODO: This is 4 instead of 3 because this update was scheduled at
@@ -310,7 +310,7 @@ describe('ReactDOMTracing', () => {
310 expect(
311 onInteractionScheduledWorkCompleted,
312 ).toHaveBeenLastNotifiedOfInteraction(interaction);
313 - if (gate(flags => flags.new)) {
313 + if (gate(flags => flags.dfsEffectsRefactor)) {
314 expect(onRender).toHaveBeenCalledTimes(3);
315 } else {
316 // TODO: This is 4 instead of 3 because this update was scheduled at
packages/react/src/__tests__/ReactProfiler-test.internal.js
+4 -3
@@ -368,7 +368,7 @@ describe('Profiler', () => {
368
369 renderer.update(<App />);
370
371 - if (gate(flags => flags.new)) {
371 + if (gate(flags => flags.dfsEffectsRefactor)) {
372 // None of the Profiler's subtree was rendered because App bailed out before the Profiler.
373 // So we expect onRender not to be called.
374 expect(callback).not.toHaveBeenCalled();
@@ -4292,7 +4292,7 @@ describe('Profiler', () => {
4292 // because the resolved suspended subtree doesn't contain any passive effects.
4293 // If <AsyncComponentWithCascadingWork> or its decendents had a passive effect,
4294 // onPostCommit would be called again.
4295 - if (gate(flags => flags.new)) {
4295 + if (gate(flags => flags.dfsEffectsRefactor)) {
4296 expect(Scheduler).toFlushAndYield([]);
4297 } else {
4298 expect(Scheduler).toFlushAndYield(['onPostCommit']);
@@ -4783,7 +4783,8 @@ describe('Profiler', () => {
4783 });
4784
4785 if (__DEV__) {
4786 - // @gate new
4786 + // @gate dfsEffectsRefactor
4787 + // @gate enableDoubleInvokingEffects
4788 it('double invoking does not disconnect wrapped async work', () => {
4789 ReactFeatureFlags.enableDoubleInvokingEffects = true;
4790
scripts/jest/TestFlags.js
+4
@@ -44,6 +44,10 @@ const environmentFlags = {
44
45 // Use this for tests that are known to be broken.
46 FIXME: false,
47 +
48 + // Turn this flag back on (or delete) once the effect list is removed in favor
49 + // of a depth-first traversal using `subtreeTags`.
50 + dfsEffectsRefactor: false,
51 };
52
53 function getTestFlags() {