diff --git a/.circleci/config.yml b/.circleci/config.yml
index 59c6d267145d8..9b222f2b64153 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -462,35 +462,6 @@ jobs:
cp ./scripts/release/ci-npmrc ~/.npmrc
scripts/release/publish.js --ci --tags << parameters.dist_tag >>
- # We don't always keep the reconciler forks in sync (otherwise it we wouldn't
- # have forked it) but during periods when they are meant to be in sync, we
- # use this job to confirm there are no differences.
- sync_reconciler_forks:
- docker: *docker
- environment: *environment
- steps:
- - checkout
- - *restore_node_modules
- - run:
- name: Fetch revisions that contain an intentional fork
- # This will fetch each revision listed in the `forked-revisions` file,
- # which may be necessary if it's not part of main. For example, it
- # may have been part of a PR branch that was squashed on merge.
- command: |
- cut -d " " -f 1 scripts/merge-fork/forked-revisions | xargs -r git fetch origin
- - run:
- name: Revert forked revisions
- # This will revert the changes without committing. At the end, it's
- # expected that both forks will be identical.
- command: |
- cut -d " " -f 1 scripts/merge-fork/forked-revisions | xargs -r git revert --no-commit
- - run:
- name: Confirm reconciler forks are the same
- command: |
- yarn replace-fork
- git diff --quiet || (echo "Reconciler forks are not the same! Run yarn replace-fork. Or, if this was intentional, add the commit SHA to scripts/merge-fork/forked-revisions." && false)
-
-
workflows:
version: 2
@@ -506,11 +477,6 @@ workflows:
- yarn_flow:
requires:
- setup
- # NOTE: This job is only enabled when we want the forks to be in sync.
- # When the forks intentionally diverge, comment out the job to disable it.
- - sync_reconciler_forks:
- requires:
- - setup
- check_generated_fizz_runtime:
requires:
- setup
diff --git a/.eslintrc.js b/.eslintrc.js
index c5350074671ed..c14105b0c08a4 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -112,14 +112,6 @@ module.exports = {
'react-internal/no-to-warn-dev-within-to-throw': ERROR,
'react-internal/warning-args': ERROR,
'react-internal/no-production-logging': ERROR,
- 'react-internal/no-cross-fork-imports': ERROR,
- 'react-internal/no-cross-fork-types': [
- ERROR,
- {
- old: [],
- new: [],
- },
- ],
},
overrides: [
diff --git a/package.json b/package.json
index 3c72f0d80a83e..13bc6c18bdb54 100644
--- a/package.json
+++ b/package.json
@@ -140,8 +140,6 @@
"prettier": "node ./scripts/prettier/index.js write-changed",
"prettier-all": "node ./scripts/prettier/index.js write",
"version-check": "node ./scripts/tasks/version-check.js",
- "merge-fork": "node ./scripts/merge-fork/merge-fork.js",
- "replace-fork": "node ./scripts/merge-fork/replace-fork.js",
"publish-prereleases": "node ./scripts/release/publish-using-ci-workflow.js",
"download-build": "node ./scripts/release/download-experimental-build.js",
"download-build-for-head": "node ./scripts/release/download-experimental-build.js --commit=$(git rev-parse HEAD)",
diff --git a/packages/react-art/src/ReactART.js b/packages/react-art/src/ReactART.js
index 2bd66260f6d78..6ad1dfb6753c2 100644
--- a/packages/react-art/src/ReactART.js
+++ b/packages/react-art/src/ReactART.js
@@ -12,7 +12,7 @@ import {
createContainer,
updateContainer,
injectIntoDevTools,
-} from 'react-reconciler/src/ReactFiberReconciler';
+} from 'react-reconciler/src/ReactFiberReconciler.old';
import Transform from 'art/core/transform';
import Mode from 'art/modes/current';
import FastNoSideEffects from 'art/modes/fast-noSideEffects';
diff --git a/packages/react-art/src/ReactARTHostConfig.js b/packages/react-art/src/ReactARTHostConfig.js
index 493d2521ce5a5..101f8213bd325 100644
--- a/packages/react-art/src/ReactARTHostConfig.js
+++ b/packages/react-art/src/ReactARTHostConfig.js
@@ -10,7 +10,7 @@ import Mode from 'art/modes/current';
import {TYPES, EVENT_TYPES, childrenAsString} from './ReactARTInternals';
-import {DefaultEventPriority} from 'react-reconciler/src/ReactEventPriorities';
+import {DefaultEventPriority} from 'react-reconciler/src/ReactEventPriorities.old';
const pooledTransform = new Transform();
diff --git a/packages/react-dom-bindings/src/client/ReactDOMFloatClient.js b/packages/react-dom-bindings/src/client/ReactDOMFloatClient.js
index b09789d436fb7..6e8e737070e6e 100644
--- a/packages/react-dom-bindings/src/client/ReactDOMFloatClient.js
+++ b/packages/react-dom-bindings/src/client/ReactDOMFloatClient.js
@@ -29,7 +29,7 @@ import {
markNodeAsResource,
} from './ReactDOMComponentTree';
import {HTML_NAMESPACE, SVG_NAMESPACE} from '../shared/DOMNamespaces';
-import {getCurrentRootHostContainer} from 'react-reconciler/src/ReactFiberHostContext';
+import {getCurrentRootHostContainer} from 'react-reconciler/src/ReactFiberHostContext.old';
// The resource types we support. currently they match the form for the as argument.
// In the future this may need to change, especially when modules / scripts are supported
diff --git a/packages/react-dom-bindings/src/client/ReactDOMHostConfig.js b/packages/react-dom-bindings/src/client/ReactDOMHostConfig.js
index db42f2ed243a3..e10a8772d04aa 100644
--- a/packages/react-dom-bindings/src/client/ReactDOMHostConfig.js
+++ b/packages/react-dom-bindings/src/client/ReactDOMHostConfig.js
@@ -7,7 +7,7 @@
* @flow
*/
-import type {EventPriority} from 'react-reconciler/src/ReactEventPriorities';
+import type {EventPriority} from 'react-reconciler/src/ReactEventPriorities.old';
import type {DOMEventName} from '../events/DOMEventNames';
import type {Fiber, FiberRoot} from 'react-reconciler/src/ReactInternalTypes';
import type {
@@ -81,7 +81,7 @@ import {
} from 'react-reconciler/src/ReactWorkTags';
import {listenToAllSupportedEvents} from '../events/DOMPluginEventSystem';
-import {DefaultEventPriority} from 'react-reconciler/src/ReactEventPriorities';
+import {DefaultEventPriority} from 'react-reconciler/src/ReactEventPriorities.old';
// TODO: Remove this deep import when we delete the legacy root API
import {ConcurrentMode, NoMode} from 'react-reconciler/src/ReactTypeOfMode';
diff --git a/packages/react-dom-bindings/src/events/ReactDOMEventListener.js b/packages/react-dom-bindings/src/events/ReactDOMEventListener.js
index 2eca510571f12..241a7a1e48f82 100644
--- a/packages/react-dom-bindings/src/events/ReactDOMEventListener.js
+++ b/packages/react-dom-bindings/src/events/ReactDOMEventListener.js
@@ -7,7 +7,7 @@
* @flow
*/
-import type {EventPriority} from 'react-reconciler/src/ReactEventPriorities';
+import type {EventPriority} from 'react-reconciler/src/ReactEventPriorities.old';
import type {AnyNativeEvent} from '../events/PluginModuleType';
import type {Fiber, FiberRoot} from 'react-reconciler/src/ReactInternalTypes';
import type {Container, SuspenseInstance} from '../client/ReactDOMHostConfig';
@@ -52,7 +52,7 @@ import {
IdleEventPriority,
getCurrentUpdatePriority,
setCurrentUpdatePriority,
-} from 'react-reconciler/src/ReactEventPriorities';
+} from 'react-reconciler/src/ReactEventPriorities.old';
import ReactSharedInternals from 'shared/ReactSharedInternals';
import {isRootDehydrated} from 'react-reconciler/src/ReactFiberShellHydration';
diff --git a/packages/react-dom-bindings/src/events/ReactDOMEventReplaying.js b/packages/react-dom-bindings/src/events/ReactDOMEventReplaying.js
index a10524fdb4966..8157f9d3f07bd 100644
--- a/packages/react-dom-bindings/src/events/ReactDOMEventReplaying.js
+++ b/packages/react-dom-bindings/src/events/ReactDOMEventReplaying.js
@@ -12,7 +12,7 @@ import type {Container, SuspenseInstance} from '../client/ReactDOMHostConfig';
import type {DOMEventName} from '../events/DOMEventNames';
import type {EventSystemFlags} from './EventSystemFlags';
import type {FiberRoot} from 'react-reconciler/src/ReactInternalTypes';
-import type {EventPriority} from 'react-reconciler/src/ReactEventPriorities';
+import type {EventPriority} from 'react-reconciler/src/ReactEventPriorities.old';
import {enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay} from 'shared/ReactFeatureFlags';
import {
@@ -35,7 +35,7 @@ import {
getClosestInstanceFromNode,
} from '../client/ReactDOMComponentTree';
import {HostRoot, SuspenseComponent} from 'react-reconciler/src/ReactWorkTags';
-import {isHigherEventPriority} from 'react-reconciler/src/ReactEventPriorities';
+import {isHigherEventPriority} from 'react-reconciler/src/ReactEventPriorities.old';
import {isRootDehydrated} from 'react-reconciler/src/ReactFiberShellHydration';
let _attemptSynchronousHydration: (fiber: Object) => void;
diff --git a/packages/react-dom/index.classic.fb.js b/packages/react-dom/index.classic.fb.js
index 4d05d28ae3348..3b559bb43c419 100644
--- a/packages/react-dom/index.classic.fb.js
+++ b/packages/react-dom/index.classic.fb.js
@@ -30,7 +30,6 @@ export {
unstable_batchedUpdates,
unstable_createEventHandle,
unstable_flushControlled,
- unstable_isNewReconciler,
unstable_renderSubtreeIntoContainer,
unstable_runWithPriority, // DO NOT USE: Temporarily exposed to migrate off of Scheduler.runWithPriority.
preinit,
diff --git a/packages/react-dom/index.js b/packages/react-dom/index.js
index a5627c22e7a3e..1ea57d00072e5 100644
--- a/packages/react-dom/index.js
+++ b/packages/react-dom/index.js
@@ -22,7 +22,6 @@ export {
unstable_batchedUpdates,
unstable_createEventHandle,
unstable_flushControlled,
- unstable_isNewReconciler,
unstable_renderSubtreeIntoContainer,
unstable_runWithPriority, // DO NOT USE: Temporarily exposed to migrate off of Scheduler.runWithPriority.
preinit,
diff --git a/packages/react-dom/index.modern.fb.js b/packages/react-dom/index.modern.fb.js
index 8422e6b9ac602..deddb938ec987 100644
--- a/packages/react-dom/index.modern.fb.js
+++ b/packages/react-dom/index.modern.fb.js
@@ -16,7 +16,6 @@ export {
unstable_batchedUpdates,
unstable_createEventHandle,
unstable_flushControlled,
- unstable_isNewReconciler,
unstable_runWithPriority, // DO NOT USE: Temporarily exposed to migrate off of Scheduler.runWithPriority.
preinit,
preload,
diff --git a/packages/react-dom/src/__tests__/react-dom-server-rendering-stub-test.js b/packages/react-dom/src/__tests__/react-dom-server-rendering-stub-test.js
index 153ea2d310bf2..a5772f968ff6c 100644
--- a/packages/react-dom/src/__tests__/react-dom-server-rendering-stub-test.js
+++ b/packages/react-dom/src/__tests__/react-dom-server-rendering-stub-test.js
@@ -36,7 +36,6 @@ describe('react-dom-server-rendering-stub', () => {
expect(ReactDOM.unstable_batchedUpdates).toBe(undefined);
expect(ReactDOM.unstable_createEventHandle).toBe(undefined);
expect(ReactDOM.unstable_flushControlled).toBe(undefined);
- expect(ReactDOM.unstable_isNewReconciler).toBe(undefined);
expect(ReactDOM.unstable_renderSubtreeIntoContainer).toBe(undefined);
expect(ReactDOM.unstable_runWithPriority).toBe(undefined);
});
diff --git a/packages/react-dom/src/client/ReactDOM.js b/packages/react-dom/src/client/ReactDOM.js
index a2246229b115f..316c4c5e0338e 100644
--- a/packages/react-dom/src/client/ReactDOM.js
+++ b/packages/react-dom/src/client/ReactDOM.js
@@ -43,15 +43,14 @@ import {
attemptDiscreteHydration,
attemptContinuousHydration,
attemptHydrationAtCurrentPriority,
-} from 'react-reconciler/src/ReactFiberReconciler';
+} from 'react-reconciler/src/ReactFiberReconciler.old';
import {
runWithPriority,
getCurrentUpdatePriority,
-} from 'react-reconciler/src/ReactEventPriorities';
+} from 'react-reconciler/src/ReactEventPriorities.old';
import {createPortal as createPortalImpl} from 'react-reconciler/src/ReactPortal';
import {canUseDOM} from 'shared/ExecutionEnvironment';
import ReactVersion from 'shared/ReactVersion';
-import {enableNewReconciler} from 'shared/ReactFeatureFlags';
import {
getClosestInstanceFromNode,
@@ -256,5 +255,3 @@ if (__DEV__) {
}
}
}
-
-export const unstable_isNewReconciler = enableNewReconciler;
diff --git a/packages/react-dom/src/client/ReactDOMLegacy.js b/packages/react-dom/src/client/ReactDOMLegacy.js
index 39e00ffc89a68..80e3315114ccb 100644
--- a/packages/react-dom/src/client/ReactDOMLegacy.js
+++ b/packages/react-dom/src/client/ReactDOMLegacy.js
@@ -37,7 +37,7 @@ import {
getPublicRootInstance,
findHostInstance,
findHostInstanceWithWarning,
-} from 'react-reconciler/src/ReactFiberReconciler';
+} from 'react-reconciler/src/ReactFiberReconciler.old';
import {LegacyRoot} from 'react-reconciler/src/ReactRootTags';
import getComponentNameFromType from 'shared/getComponentNameFromType';
import ReactSharedInternals from 'shared/ReactSharedInternals';
diff --git a/packages/react-dom/src/client/ReactDOMRoot.js b/packages/react-dom/src/client/ReactDOMRoot.js
index a4b901d156d78..a2b4994952a12 100644
--- a/packages/react-dom/src/client/ReactDOMRoot.js
+++ b/packages/react-dom/src/client/ReactDOMRoot.js
@@ -76,7 +76,7 @@ import {
registerMutableSourceForHydration,
flushSync,
isAlreadyRendering,
-} from 'react-reconciler/src/ReactFiberReconciler';
+} from 'react-reconciler/src/ReactFiberReconciler.old';
import {ConcurrentRoot} from 'react-reconciler/src/ReactRootTags';
/* global reportError */
diff --git a/packages/react-dom/unstable_testing.classic.fb.js b/packages/react-dom/unstable_testing.classic.fb.js
index 2f623787958d3..4c4700f6aa7b5 100644
--- a/packages/react-dom/unstable_testing.classic.fb.js
+++ b/packages/react-dom/unstable_testing.classic.fb.js
@@ -19,4 +19,4 @@ export {
findBoundingRects,
focusWithin,
observeVisibleRects,
-} from 'react-reconciler/src/ReactFiberReconciler';
+} from 'react-reconciler/src/ReactFiberReconciler.old';
diff --git a/packages/react-dom/unstable_testing.experimental.js b/packages/react-dom/unstable_testing.experimental.js
index 3ed081d85b9b1..dc12b7042c38b 100644
--- a/packages/react-dom/unstable_testing.experimental.js
+++ b/packages/react-dom/unstable_testing.experimental.js
@@ -19,4 +19,4 @@ export {
findBoundingRects,
focusWithin,
observeVisibleRects,
-} from 'react-reconciler/src/ReactFiberReconciler';
+} from 'react-reconciler/src/ReactFiberReconciler.old';
diff --git a/packages/react-dom/unstable_testing.js b/packages/react-dom/unstable_testing.js
index fdad56d13c77d..aaa570ca28985 100644
--- a/packages/react-dom/unstable_testing.js
+++ b/packages/react-dom/unstable_testing.js
@@ -19,4 +19,4 @@ export {
findBoundingRects,
focusWithin,
observeVisibleRects,
-} from 'react-reconciler/src/ReactFiberReconciler';
+} from 'react-reconciler/src/ReactFiberReconciler.old';
diff --git a/packages/react-dom/unstable_testing.modern.fb.js b/packages/react-dom/unstable_testing.modern.fb.js
index c0a0e45ad79ca..cb9a8309d8e1a 100644
--- a/packages/react-dom/unstable_testing.modern.fb.js
+++ b/packages/react-dom/unstable_testing.modern.fb.js
@@ -19,4 +19,4 @@ export {
findBoundingRects,
focusWithin,
observeVisibleRects,
-} from 'react-reconciler/src/ReactFiberReconciler';
+} from 'react-reconciler/src/ReactFiberReconciler.old';
diff --git a/packages/react-native-renderer/src/ReactFabric.js b/packages/react-native-renderer/src/ReactFabric.js
index 1e3d299ece9e8..fa26a33099228 100644
--- a/packages/react-native-renderer/src/ReactFabric.js
+++ b/packages/react-native-renderer/src/ReactFabric.js
@@ -22,7 +22,7 @@ import {
updateContainer,
injectIntoDevTools,
getPublicRootInstance,
-} from 'react-reconciler/src/ReactFiberReconciler';
+} from 'react-reconciler/src/ReactFiberReconciler.old';
import {createPortal as createPortalImpl} from 'react-reconciler/src/ReactPortal';
import {setBatchingImplementation} from './legacy-events/ReactGenericBatching';
diff --git a/packages/react-native-renderer/src/ReactFabricHostConfig.js b/packages/react-native-renderer/src/ReactFabricHostConfig.js
index 078bf1f11ac6f..43bb2f646d3cd 100644
--- a/packages/react-native-renderer/src/ReactFabricHostConfig.js
+++ b/packages/react-native-renderer/src/ReactFabricHostConfig.js
@@ -26,7 +26,7 @@ import {dispatchEvent} from './ReactFabricEventEmitter';
import {
DefaultEventPriority,
DiscreteEventPriority,
-} from 'react-reconciler/src/ReactEventPriorities';
+} from 'react-reconciler/src/ReactEventPriorities.old';
// Modules provided by RN:
import {
diff --git a/packages/react-native-renderer/src/ReactNativeHostConfig.js b/packages/react-native-renderer/src/ReactNativeHostConfig.js
index 03cfa415f6937..ae3ce588559be 100644
--- a/packages/react-native-renderer/src/ReactNativeHostConfig.js
+++ b/packages/react-native-renderer/src/ReactNativeHostConfig.js
@@ -24,7 +24,7 @@ import {
} from './ReactNativeComponentTree';
import ReactNativeFiberHostComponent from './ReactNativeFiberHostComponent';
-import {DefaultEventPriority} from 'react-reconciler/src/ReactEventPriorities';
+import {DefaultEventPriority} from 'react-reconciler/src/ReactEventPriorities.old';
const {get: getViewConfigForType} = ReactNativeViewConfigRegistry;
diff --git a/packages/react-native-renderer/src/ReactNativeRenderer.js b/packages/react-native-renderer/src/ReactNativeRenderer.js
index d9577a6eb6d58..802cd831dd215 100644
--- a/packages/react-native-renderer/src/ReactNativeRenderer.js
+++ b/packages/react-native-renderer/src/ReactNativeRenderer.js
@@ -22,7 +22,7 @@ import {
updateContainer,
injectIntoDevTools,
getPublicRootInstance,
-} from 'react-reconciler/src/ReactFiberReconciler';
+} from 'react-reconciler/src/ReactFiberReconciler.old';
// TODO: direct imports like some-package/src/* are bad. Fix me.
import {getStackByFiberInDevAndProd} from 'react-reconciler/src/ReactFiberComponentStack';
import {createPortal as createPortalImpl} from 'react-reconciler/src/ReactPortal';
diff --git a/packages/react-noop-renderer/src/createReactNoop.js b/packages/react-noop-renderer/src/createReactNoop.js
index 5555657e3589c..47a62f2c15624 100644
--- a/packages/react-noop-renderer/src/createReactNoop.js
+++ b/packages/react-noop-renderer/src/createReactNoop.js
@@ -18,7 +18,7 @@ import type {
Fiber,
TransitionTracingCallbacks,
} from 'react-reconciler/src/ReactInternalTypes';
-import type {UpdateQueue} from 'react-reconciler/src/ReactFiberClassUpdateQueue.new';
+import type {UpdateQueue} from 'react-reconciler/src/ReactFiberClassUpdateQueue';
import type {ReactNodeList} from 'shared/ReactTypes';
import type {RootTag} from 'react-reconciler/src/ReactRootTags';
diff --git a/packages/react-reconciler/index.js b/packages/react-reconciler/index.js
index afffd715cffe5..0e41352a8c4e4 100644
--- a/packages/react-reconciler/index.js
+++ b/packages/react-reconciler/index.js
@@ -7,4 +7,4 @@
* @flow
*/
-export * from './src/ReactFiberReconciler';
+export * from './src/ReactFiberReconciler.old';
diff --git a/packages/react-reconciler/src/ReactChildFiber.new.js b/packages/react-reconciler/src/ReactChildFiber.new.js
deleted file mode 100644
index ea72a3de92047..0000000000000
--- a/packages/react-reconciler/src/ReactChildFiber.new.js
+++ /dev/null
@@ -1,1411 +0,0 @@
-/**
- * Copyright (c) Meta Platforms, Inc. and affiliates.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- *
- * @flow
- */
-
-import type {ReactElement} from 'shared/ReactElementType';
-import type {ReactPortal} from 'shared/ReactTypes';
-import type {Fiber} from './ReactInternalTypes';
-import type {Lanes} from './ReactFiberLane.new';
-
-import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
-import {
- Placement,
- ChildDeletion,
- Forked,
- PlacementDEV,
-} from './ReactFiberFlags';
-import {
- getIteratorFn,
- REACT_ELEMENT_TYPE,
- REACT_FRAGMENT_TYPE,
- REACT_PORTAL_TYPE,
- REACT_LAZY_TYPE,
-} from 'shared/ReactSymbols';
-import {ClassComponent, HostText, HostPortal, Fragment} from './ReactWorkTags';
-import isArray from 'shared/isArray';
-import {warnAboutStringRefs} from 'shared/ReactFeatureFlags';
-import {checkPropStringCoercion} from 'shared/CheckStringCoercion';
-
-import {
- createWorkInProgress,
- resetWorkInProgress,
- createFiberFromElement,
- createFiberFromFragment,
- createFiberFromText,
- createFiberFromPortal,
-} from './ReactFiber.new';
-import {isCompatibleFamilyForHotReloading} from './ReactFiberHotReloading.new';
-import {StrictLegacyMode} from './ReactTypeOfMode';
-import {getIsHydrating} from './ReactFiberHydrationContext.new';
-import {pushTreeFork} from './ReactFiberTreeContext.new';
-
-let didWarnAboutMaps;
-let didWarnAboutGenerators;
-let didWarnAboutStringRefs;
-let ownerHasKeyUseWarning;
-let ownerHasFunctionTypeWarning;
-let warnForMissingKey = (child: mixed, returnFiber: Fiber) => {};
-
-if (__DEV__) {
- didWarnAboutMaps = false;
- didWarnAboutGenerators = false;
- didWarnAboutStringRefs = {};
-
- /**
- * Warn if there's no key explicitly set on dynamic arrays of children or
- * object keys are not valid. This allows us to keep track of children between
- * updates.
- */
- ownerHasKeyUseWarning = {};
- ownerHasFunctionTypeWarning = {};
-
- warnForMissingKey = (child: mixed, returnFiber: Fiber) => {
- if (child === null || typeof child !== 'object') {
- return;
- }
- if (!child._store || child._store.validated || child.key != null) {
- return;
- }
-
- if (typeof child._store !== 'object') {
- throw new Error(
- 'React Component in warnForMissingKey should have a _store. ' +
- 'This error is likely caused by a bug in React. Please file an issue.',
- );
- }
-
- // $FlowFixMe unable to narrow type from mixed to writable object
- child._store.validated = true;
-
- const componentName = getComponentNameFromFiber(returnFiber) || 'Component';
-
- if (ownerHasKeyUseWarning[componentName]) {
- return;
- }
- ownerHasKeyUseWarning[componentName] = true;
-
- console.error(
- 'Each child in a list should have a unique ' +
- '"key" prop. See https://reactjs.org/link/warning-keys for ' +
- 'more information.',
- );
- };
-}
-
-function isReactClass(type) {
- return type.prototype && type.prototype.isReactComponent;
-}
-
-function coerceRef(
- returnFiber: Fiber,
- current: Fiber | null,
- element: ReactElement,
-) {
- const mixedRef = element.ref;
- if (
- mixedRef !== null &&
- typeof mixedRef !== 'function' &&
- typeof mixedRef !== 'object'
- ) {
- if (__DEV__) {
- // TODO: Clean this up once we turn on the string ref warning for
- // everyone, because the strict mode case will no longer be relevant
- if (
- (returnFiber.mode & StrictLegacyMode || warnAboutStringRefs) &&
- // We warn in ReactElement.js if owner and self are equal for string refs
- // because these cannot be automatically converted to an arrow function
- // using a codemod. Therefore, we don't have to warn about string refs again.
- !(
- element._owner &&
- element._self &&
- element._owner.stateNode !== element._self
- ) &&
- // Will already throw with "Function components cannot have string refs"
- !(
- element._owner &&
- ((element._owner: any): Fiber).tag !== ClassComponent
- ) &&
- // Will already warn with "Function components cannot be given refs"
- !(typeof element.type === 'function' && !isReactClass(element.type)) &&
- // Will already throw with "Element ref was specified as a string (someStringRef) but no owner was set"
- element._owner
- ) {
- const componentName =
- getComponentNameFromFiber(returnFiber) || 'Component';
- if (!didWarnAboutStringRefs[componentName]) {
- if (warnAboutStringRefs) {
- console.error(
- 'Component "%s" contains the string ref "%s". Support for string refs ' +
- 'will be removed in a future major release. We recommend using ' +
- 'useRef() or createRef() instead. ' +
- 'Learn more about using refs safely here: ' +
- 'https://reactjs.org/link/strict-mode-string-ref',
- componentName,
- mixedRef,
- );
- } else {
- console.error(
- 'A string ref, "%s", has been found within a strict mode tree. ' +
- 'String refs are a source of potential bugs and should be avoided. ' +
- 'We recommend using useRef() or createRef() instead. ' +
- 'Learn more about using refs safely here: ' +
- 'https://reactjs.org/link/strict-mode-string-ref',
- mixedRef,
- );
- }
- didWarnAboutStringRefs[componentName] = true;
- }
- }
- }
-
- if (element._owner) {
- const owner: ?Fiber = (element._owner: any);
- let inst;
- if (owner) {
- const ownerFiber = ((owner: any): Fiber);
-
- if (ownerFiber.tag !== ClassComponent) {
- throw new Error(
- 'Function components cannot have string refs. ' +
- 'We recommend using useRef() instead. ' +
- 'Learn more about using refs safely here: ' +
- 'https://reactjs.org/link/strict-mode-string-ref',
- );
- }
-
- inst = ownerFiber.stateNode;
- }
-
- if (!inst) {
- throw new Error(
- `Missing owner for string ref ${mixedRef}. This error is likely caused by a ` +
- 'bug in React. Please file an issue.',
- );
- }
- // Assigning this to a const so Flow knows it won't change in the closure
- const resolvedInst = inst;
-
- if (__DEV__) {
- checkPropStringCoercion(mixedRef, 'ref');
- }
- const stringRef = '' + mixedRef;
- // Check if previous string ref matches new string ref
- if (
- current !== null &&
- current.ref !== null &&
- typeof current.ref === 'function' &&
- current.ref._stringRef === stringRef
- ) {
- return current.ref;
- }
- const ref = function(value) {
- const refs = resolvedInst.refs;
- if (value === null) {
- delete refs[stringRef];
- } else {
- refs[stringRef] = value;
- }
- };
- ref._stringRef = stringRef;
- return ref;
- } else {
- if (typeof mixedRef !== 'string') {
- throw new Error(
- 'Expected ref to be a function, a string, an object returned by React.createRef(), or null.',
- );
- }
-
- if (!element._owner) {
- throw new Error(
- `Element ref was specified as a string (${mixedRef}) but no owner was set. This could happen for one of` +
- ' the following reasons:\n' +
- '1. You may be adding a ref to a function component\n' +
- "2. You may be adding a ref to a component that was not created inside a component's render method\n" +
- '3. You have multiple copies of React loaded\n' +
- 'See https://reactjs.org/link/refs-must-have-owner for more information.',
- );
- }
- }
- }
- return mixedRef;
-}
-
-function throwOnInvalidObjectType(returnFiber: Fiber, newChild: Object) {
- // $FlowFixMe[method-unbinding]
- const childString = Object.prototype.toString.call(newChild);
-
- throw new Error(
- `Objects are not valid as a React child (found: ${
- childString === '[object Object]'
- ? 'object with keys {' + Object.keys(newChild).join(', ') + '}'
- : childString
- }). ` +
- 'If you meant to render a collection of children, use an array ' +
- 'instead.',
- );
-}
-
-function warnOnFunctionType(returnFiber: Fiber) {
- if (__DEV__) {
- const componentName = getComponentNameFromFiber(returnFiber) || 'Component';
-
- if (ownerHasFunctionTypeWarning[componentName]) {
- return;
- }
- ownerHasFunctionTypeWarning[componentName] = true;
-
- console.error(
- 'Functions are not valid as a React child. This may happen if ' +
- 'you return a Component instead of from render. ' +
- 'Or maybe you meant to call this function rather than return it.',
- );
- }
-}
-
-function resolveLazy(lazyType) {
- const payload = lazyType._payload;
- const init = lazyType._init;
- return init(payload);
-}
-
-type ChildReconciler = (
- returnFiber: Fiber,
- currentFirstChild: Fiber | null,
- newChild: any,
- lanes: Lanes,
-) => Fiber | null;
-
-// This wrapper function exists because I expect to clone the code in each path
-// to be able to optimize each path individually by branching early. This needs
-// a compiler or we can do it manually. Helpers that don't need this branching
-// live outside of this function.
-function createChildReconciler(shouldTrackSideEffects): ChildReconciler {
- function deleteChild(returnFiber: Fiber, childToDelete: Fiber): void {
- if (!shouldTrackSideEffects) {
- // Noop.
- return;
- }
- const deletions = returnFiber.deletions;
- if (deletions === null) {
- returnFiber.deletions = [childToDelete];
- returnFiber.flags |= ChildDeletion;
- } else {
- deletions.push(childToDelete);
- }
- }
-
- function deleteRemainingChildren(
- returnFiber: Fiber,
- currentFirstChild: Fiber | null,
- ): null {
- if (!shouldTrackSideEffects) {
- // Noop.
- return null;
- }
-
- // TODO: For the shouldClone case, this could be micro-optimized a bit by
- // assuming that after the first child we've already added everything.
- let childToDelete = currentFirstChild;
- while (childToDelete !== null) {
- deleteChild(returnFiber, childToDelete);
- childToDelete = childToDelete.sibling;
- }
- return null;
- }
-
- function mapRemainingChildren(
- returnFiber: Fiber,
- currentFirstChild: Fiber,
- ): Map {
- // Add the remaining children to a temporary map so that we can find them by
- // keys quickly. Implicit (null) keys get added to this set with their index
- // instead.
- const existingChildren: Map = new Map();
-
- let existingChild: null | Fiber = currentFirstChild;
- while (existingChild !== null) {
- if (existingChild.key !== null) {
- existingChildren.set(existingChild.key, existingChild);
- } else {
- existingChildren.set(existingChild.index, existingChild);
- }
- existingChild = existingChild.sibling;
- }
- return existingChildren;
- }
-
- function useFiber(fiber: Fiber, pendingProps: mixed): Fiber {
- // We currently set sibling to null and index to 0 here because it is easy
- // to forget to do before returning it. E.g. for the single child case.
- const clone = createWorkInProgress(fiber, pendingProps);
- clone.index = 0;
- clone.sibling = null;
- return clone;
- }
-
- function placeChild(
- newFiber: Fiber,
- lastPlacedIndex: number,
- newIndex: number,
- ): number {
- newFiber.index = newIndex;
- if (!shouldTrackSideEffects) {
- // During hydration, the useId algorithm needs to know which fibers are
- // part of a list of children (arrays, iterators).
- newFiber.flags |= Forked;
- return lastPlacedIndex;
- }
- const current = newFiber.alternate;
- if (current !== null) {
- const oldIndex = current.index;
- if (oldIndex < lastPlacedIndex) {
- // This is a move.
- newFiber.flags |= Placement | PlacementDEV;
- return lastPlacedIndex;
- } else {
- // This item can stay in place.
- return oldIndex;
- }
- } else {
- // This is an insertion.
- newFiber.flags |= Placement | PlacementDEV;
- return lastPlacedIndex;
- }
- }
-
- function placeSingleChild(newFiber: Fiber): Fiber {
- // This is simpler for the single child case. We only need to do a
- // placement for inserting new children.
- if (shouldTrackSideEffects && newFiber.alternate === null) {
- newFiber.flags |= Placement | PlacementDEV;
- }
- return newFiber;
- }
-
- function updateTextNode(
- returnFiber: Fiber,
- current: Fiber | null,
- textContent: string,
- lanes: Lanes,
- ) {
- if (current === null || current.tag !== HostText) {
- // Insert
- const created = createFiberFromText(textContent, returnFiber.mode, lanes);
- created.return = returnFiber;
- return created;
- } else {
- // Update
- const existing = useFiber(current, textContent);
- existing.return = returnFiber;
- return existing;
- }
- }
-
- function updateElement(
- returnFiber: Fiber,
- current: Fiber | null,
- element: ReactElement,
- lanes: Lanes,
- ): Fiber {
- const elementType = element.type;
- if (elementType === REACT_FRAGMENT_TYPE) {
- return updateFragment(
- returnFiber,
- current,
- element.props.children,
- lanes,
- element.key,
- );
- }
- if (current !== null) {
- if (
- current.elementType === elementType ||
- // Keep this check inline so it only runs on the false path:
- (__DEV__
- ? isCompatibleFamilyForHotReloading(current, element)
- : false) ||
- // Lazy types should reconcile their resolved type.
- // We need to do this after the Hot Reloading check above,
- // because hot reloading has different semantics than prod because
- // it doesn't resuspend. So we can't let the call below suspend.
- (typeof elementType === 'object' &&
- elementType !== null &&
- elementType.$$typeof === REACT_LAZY_TYPE &&
- resolveLazy(elementType) === current.type)
- ) {
- // Move based on index
- const existing = useFiber(current, element.props);
- existing.ref = coerceRef(returnFiber, current, element);
- existing.return = returnFiber;
- if (__DEV__) {
- existing._debugSource = element._source;
- existing._debugOwner = element._owner;
- }
- return existing;
- }
- }
- // Insert
- const created = createFiberFromElement(element, returnFiber.mode, lanes);
- created.ref = coerceRef(returnFiber, current, element);
- created.return = returnFiber;
- return created;
- }
-
- function updatePortal(
- returnFiber: Fiber,
- current: Fiber | null,
- portal: ReactPortal,
- lanes: Lanes,
- ): Fiber {
- if (
- current === null ||
- current.tag !== HostPortal ||
- current.stateNode.containerInfo !== portal.containerInfo ||
- current.stateNode.implementation !== portal.implementation
- ) {
- // Insert
- const created = createFiberFromPortal(portal, returnFiber.mode, lanes);
- created.return = returnFiber;
- return created;
- } else {
- // Update
- const existing = useFiber(current, portal.children || []);
- existing.return = returnFiber;
- return existing;
- }
- }
-
- function updateFragment(
- returnFiber: Fiber,
- current: Fiber | null,
- fragment: Iterable,
- lanes: Lanes,
- key: null | string,
- ): Fiber {
- if (current === null || current.tag !== Fragment) {
- // Insert
- const created = createFiberFromFragment(
- fragment,
- returnFiber.mode,
- lanes,
- key,
- );
- created.return = returnFiber;
- return created;
- } else {
- // Update
- const existing = useFiber(current, fragment);
- existing.return = returnFiber;
- return existing;
- }
- }
-
- function createChild(
- returnFiber: Fiber,
- newChild: any,
- lanes: Lanes,
- ): Fiber | null {
- if (
- (typeof newChild === 'string' && newChild !== '') ||
- typeof newChild === 'number'
- ) {
- // Text nodes don't have keys. If the previous node is implicitly keyed
- // we can continue to replace it without aborting even if it is not a text
- // node.
- const created = createFiberFromText(
- '' + newChild,
- returnFiber.mode,
- lanes,
- );
- created.return = returnFiber;
- return created;
- }
-
- if (typeof newChild === 'object' && newChild !== null) {
- switch (newChild.$$typeof) {
- case REACT_ELEMENT_TYPE: {
- const created = createFiberFromElement(
- newChild,
- returnFiber.mode,
- lanes,
- );
- created.ref = coerceRef(returnFiber, null, newChild);
- created.return = returnFiber;
- return created;
- }
- case REACT_PORTAL_TYPE: {
- const created = createFiberFromPortal(
- newChild,
- returnFiber.mode,
- lanes,
- );
- created.return = returnFiber;
- return created;
- }
- case REACT_LAZY_TYPE: {
- const payload = newChild._payload;
- const init = newChild._init;
- return createChild(returnFiber, init(payload), lanes);
- }
- }
-
- if (isArray(newChild) || getIteratorFn(newChild)) {
- const created = createFiberFromFragment(
- newChild,
- returnFiber.mode,
- lanes,
- null,
- );
- created.return = returnFiber;
- return created;
- }
-
- throwOnInvalidObjectType(returnFiber, newChild);
- }
-
- if (__DEV__) {
- if (typeof newChild === 'function') {
- warnOnFunctionType(returnFiber);
- }
- }
-
- return null;
- }
-
- function updateSlot(
- returnFiber: Fiber,
- oldFiber: Fiber | null,
- newChild: any,
- lanes: Lanes,
- ): Fiber | null {
- // Update the fiber if the keys match, otherwise return null.
-
- const key = oldFiber !== null ? oldFiber.key : null;
-
- if (
- (typeof newChild === 'string' && newChild !== '') ||
- typeof newChild === 'number'
- ) {
- // Text nodes don't have keys. If the previous node is implicitly keyed
- // we can continue to replace it without aborting even if it is not a text
- // node.
- if (key !== null) {
- return null;
- }
- return updateTextNode(returnFiber, oldFiber, '' + newChild, lanes);
- }
-
- if (typeof newChild === 'object' && newChild !== null) {
- switch (newChild.$$typeof) {
- case REACT_ELEMENT_TYPE: {
- if (newChild.key === key) {
- return updateElement(returnFiber, oldFiber, newChild, lanes);
- } else {
- return null;
- }
- }
- case REACT_PORTAL_TYPE: {
- if (newChild.key === key) {
- return updatePortal(returnFiber, oldFiber, newChild, lanes);
- } else {
- return null;
- }
- }
- case REACT_LAZY_TYPE: {
- const payload = newChild._payload;
- const init = newChild._init;
- return updateSlot(returnFiber, oldFiber, init(payload), lanes);
- }
- }
-
- if (isArray(newChild) || getIteratorFn(newChild)) {
- if (key !== null) {
- return null;
- }
-
- return updateFragment(returnFiber, oldFiber, newChild, lanes, null);
- }
-
- throwOnInvalidObjectType(returnFiber, newChild);
- }
-
- if (__DEV__) {
- if (typeof newChild === 'function') {
- warnOnFunctionType(returnFiber);
- }
- }
-
- return null;
- }
-
- function updateFromMap(
- existingChildren: Map,
- returnFiber: Fiber,
- newIdx: number,
- newChild: any,
- lanes: Lanes,
- ): Fiber | null {
- if (
- (typeof newChild === 'string' && newChild !== '') ||
- typeof newChild === 'number'
- ) {
- // Text nodes don't have keys, so we neither have to check the old nor
- // new node for the key. If both are text nodes, they match.
- const matchedFiber = existingChildren.get(newIdx) || null;
- return updateTextNode(returnFiber, matchedFiber, '' + newChild, lanes);
- }
-
- if (typeof newChild === 'object' && newChild !== null) {
- switch (newChild.$$typeof) {
- case REACT_ELEMENT_TYPE: {
- const matchedFiber =
- existingChildren.get(
- newChild.key === null ? newIdx : newChild.key,
- ) || null;
- return updateElement(returnFiber, matchedFiber, newChild, lanes);
- }
- case REACT_PORTAL_TYPE: {
- const matchedFiber =
- existingChildren.get(
- newChild.key === null ? newIdx : newChild.key,
- ) || null;
- return updatePortal(returnFiber, matchedFiber, newChild, lanes);
- }
- case REACT_LAZY_TYPE:
- const payload = newChild._payload;
- const init = newChild._init;
- return updateFromMap(
- existingChildren,
- returnFiber,
- newIdx,
- init(payload),
- lanes,
- );
- }
-
- if (isArray(newChild) || getIteratorFn(newChild)) {
- const matchedFiber = existingChildren.get(newIdx) || null;
- return updateFragment(returnFiber, matchedFiber, newChild, lanes, null);
- }
-
- throwOnInvalidObjectType(returnFiber, newChild);
- }
-
- if (__DEV__) {
- if (typeof newChild === 'function') {
- warnOnFunctionType(returnFiber);
- }
- }
-
- return null;
- }
-
- /**
- * Warns if there is a duplicate or missing key
- */
- function warnOnInvalidKey(
- child: mixed,
- knownKeys: Set | null,
- returnFiber: Fiber,
- ): Set | null {
- if (__DEV__) {
- if (typeof child !== 'object' || child === null) {
- return knownKeys;
- }
- switch (child.$$typeof) {
- case REACT_ELEMENT_TYPE:
- case REACT_PORTAL_TYPE:
- warnForMissingKey(child, returnFiber);
- const key = child.key;
- if (typeof key !== 'string') {
- break;
- }
- if (knownKeys === null) {
- knownKeys = new Set();
- knownKeys.add(key);
- break;
- }
- if (!knownKeys.has(key)) {
- knownKeys.add(key);
- break;
- }
- console.error(
- 'Encountered two children with the same key, `%s`. ' +
- 'Keys should be unique so that components maintain their identity ' +
- 'across updates. Non-unique keys may cause children to be ' +
- 'duplicated and/or omitted — the behavior is unsupported and ' +
- 'could change in a future version.',
- key,
- );
- break;
- case REACT_LAZY_TYPE:
- const payload = child._payload;
- const init = (child._init: any);
- warnOnInvalidKey(init(payload), knownKeys, returnFiber);
- break;
- default:
- break;
- }
- }
- return knownKeys;
- }
-
- function reconcileChildrenArray(
- returnFiber: Fiber,
- currentFirstChild: Fiber | null,
- newChildren: Array,
- lanes: Lanes,
- ): Fiber | null {
- // This algorithm can't optimize by searching from both ends since we
- // don't have backpointers on fibers. I'm trying to see how far we can get
- // with that model. If it ends up not being worth the tradeoffs, we can
- // add it later.
-
- // Even with a two ended optimization, we'd want to optimize for the case
- // where there are few changes and brute force the comparison instead of
- // going for the Map. It'd like to explore hitting that path first in
- // forward-only mode and only go for the Map once we notice that we need
- // lots of look ahead. This doesn't handle reversal as well as two ended
- // search but that's unusual. Besides, for the two ended optimization to
- // work on Iterables, we'd need to copy the whole set.
-
- // In this first iteration, we'll just live with hitting the bad case
- // (adding everything to a Map) in for every insert/move.
-
- // If you change this code, also update reconcileChildrenIterator() which
- // uses the same algorithm.
-
- if (__DEV__) {
- // First, validate keys.
- let knownKeys = null;
- for (let i = 0; i < newChildren.length; i++) {
- const child = newChildren[i];
- knownKeys = warnOnInvalidKey(child, knownKeys, returnFiber);
- }
- }
-
- let resultingFirstChild: Fiber | null = null;
- let previousNewFiber: Fiber | null = null;
-
- let oldFiber = currentFirstChild;
- let lastPlacedIndex = 0;
- let newIdx = 0;
- let nextOldFiber = null;
- for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) {
- if (oldFiber.index > newIdx) {
- nextOldFiber = oldFiber;
- oldFiber = null;
- } else {
- nextOldFiber = oldFiber.sibling;
- }
- const newFiber = updateSlot(
- returnFiber,
- oldFiber,
- newChildren[newIdx],
- lanes,
- );
- if (newFiber === null) {
- // TODO: This breaks on empty slots like null children. That's
- // unfortunate because it triggers the slow path all the time. We need
- // a better way to communicate whether this was a miss or null,
- // boolean, undefined, etc.
- if (oldFiber === null) {
- oldFiber = nextOldFiber;
- }
- break;
- }
- if (shouldTrackSideEffects) {
- if (oldFiber && newFiber.alternate === null) {
- // We matched the slot, but we didn't reuse the existing fiber, so we
- // need to delete the existing child.
- deleteChild(returnFiber, oldFiber);
- }
- }
- lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
- if (previousNewFiber === null) {
- // TODO: Move out of the loop. This only happens for the first run.
- resultingFirstChild = newFiber;
- } else {
- // TODO: Defer siblings if we're not at the right index for this slot.
- // I.e. if we had null values before, then we want to defer this
- // for each null value. However, we also don't want to call updateSlot
- // with the previous one.
- previousNewFiber.sibling = newFiber;
- }
- previousNewFiber = newFiber;
- oldFiber = nextOldFiber;
- }
-
- if (newIdx === newChildren.length) {
- // We've reached the end of the new children. We can delete the rest.
- deleteRemainingChildren(returnFiber, oldFiber);
- if (getIsHydrating()) {
- const numberOfForks = newIdx;
- pushTreeFork(returnFiber, numberOfForks);
- }
- return resultingFirstChild;
- }
-
- if (oldFiber === null) {
- // If we don't have any more existing children we can choose a fast path
- // since the rest will all be insertions.
- for (; newIdx < newChildren.length; newIdx++) {
- const newFiber = createChild(returnFiber, newChildren[newIdx], lanes);
- if (newFiber === null) {
- continue;
- }
- lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
- if (previousNewFiber === null) {
- // TODO: Move out of the loop. This only happens for the first run.
- resultingFirstChild = newFiber;
- } else {
- previousNewFiber.sibling = newFiber;
- }
- previousNewFiber = newFiber;
- }
- if (getIsHydrating()) {
- const numberOfForks = newIdx;
- pushTreeFork(returnFiber, numberOfForks);
- }
- return resultingFirstChild;
- }
-
- // Add all children to a key map for quick lookups.
- const existingChildren = mapRemainingChildren(returnFiber, oldFiber);
-
- // Keep scanning and use the map to restore deleted items as moves.
- for (; newIdx < newChildren.length; newIdx++) {
- const newFiber = updateFromMap(
- existingChildren,
- returnFiber,
- newIdx,
- newChildren[newIdx],
- lanes,
- );
- if (newFiber !== null) {
- if (shouldTrackSideEffects) {
- if (newFiber.alternate !== null) {
- // The new fiber is a work in progress, but if there exists a
- // current, that means that we reused the fiber. We need to delete
- // it from the child list so that we don't add it to the deletion
- // list.
- existingChildren.delete(
- newFiber.key === null ? newIdx : newFiber.key,
- );
- }
- }
- lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
- if (previousNewFiber === null) {
- resultingFirstChild = newFiber;
- } else {
- previousNewFiber.sibling = newFiber;
- }
- previousNewFiber = newFiber;
- }
- }
-
- if (shouldTrackSideEffects) {
- // Any existing children that weren't consumed above were deleted. We need
- // to add them to the deletion list.
- existingChildren.forEach(child => deleteChild(returnFiber, child));
- }
-
- if (getIsHydrating()) {
- const numberOfForks = newIdx;
- pushTreeFork(returnFiber, numberOfForks);
- }
- return resultingFirstChild;
- }
-
- function reconcileChildrenIterator(
- returnFiber: Fiber,
- currentFirstChild: Fiber | null,
- newChildrenIterable: Iterable,
- lanes: Lanes,
- ): Fiber | null {
- // This is the same implementation as reconcileChildrenArray(),
- // but using the iterator instead.
-
- const iteratorFn = getIteratorFn(newChildrenIterable);
-
- if (typeof iteratorFn !== 'function') {
- throw new Error(
- 'An object is not an iterable. This error is likely caused by a bug in ' +
- 'React. Please file an issue.',
- );
- }
-
- if (__DEV__) {
- // We don't support rendering Generators because it's a mutation.
- // See https://github.com/facebook/react/issues/12995
- if (
- typeof Symbol === 'function' &&
- // $FlowFixMe Flow doesn't know about toStringTag
- newChildrenIterable[Symbol.toStringTag] === 'Generator'
- ) {
- if (!didWarnAboutGenerators) {
- console.error(
- 'Using Generators as children is unsupported and will likely yield ' +
- 'unexpected results because enumerating a generator mutates it. ' +
- 'You may convert it to an array with `Array.from()` or the ' +
- '`[...spread]` operator before rendering. Keep in mind ' +
- 'you might need to polyfill these features for older browsers.',
- );
- }
- didWarnAboutGenerators = true;
- }
-
- // Warn about using Maps as children
- if ((newChildrenIterable: any).entries === iteratorFn) {
- if (!didWarnAboutMaps) {
- console.error(
- 'Using Maps as children is not supported. ' +
- 'Use an array of keyed ReactElements instead.',
- );
- }
- didWarnAboutMaps = true;
- }
-
- // First, validate keys.
- // We'll get a different iterator later for the main pass.
- const newChildren = iteratorFn.call(newChildrenIterable);
- if (newChildren) {
- let knownKeys = null;
- let step = newChildren.next();
- for (; !step.done; step = newChildren.next()) {
- const child = step.value;
- knownKeys = warnOnInvalidKey(child, knownKeys, returnFiber);
- }
- }
- }
-
- const newChildren = iteratorFn.call(newChildrenIterable);
-
- if (newChildren == null) {
- throw new Error('An iterable object provided no iterator.');
- }
-
- let resultingFirstChild: Fiber | null = null;
- let previousNewFiber: Fiber | null = null;
-
- let oldFiber = currentFirstChild;
- let lastPlacedIndex = 0;
- let newIdx = 0;
- let nextOldFiber = null;
-
- let step = newChildren.next();
- for (
- ;
- oldFiber !== null && !step.done;
- newIdx++, step = newChildren.next()
- ) {
- if (oldFiber.index > newIdx) {
- nextOldFiber = oldFiber;
- oldFiber = null;
- } else {
- nextOldFiber = oldFiber.sibling;
- }
- const newFiber = updateSlot(returnFiber, oldFiber, step.value, lanes);
- if (newFiber === null) {
- // TODO: This breaks on empty slots like null children. That's
- // unfortunate because it triggers the slow path all the time. We need
- // a better way to communicate whether this was a miss or null,
- // boolean, undefined, etc.
- if (oldFiber === null) {
- oldFiber = nextOldFiber;
- }
- break;
- }
- if (shouldTrackSideEffects) {
- if (oldFiber && newFiber.alternate === null) {
- // We matched the slot, but we didn't reuse the existing fiber, so we
- // need to delete the existing child.
- deleteChild(returnFiber, oldFiber);
- }
- }
- lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
- if (previousNewFiber === null) {
- // TODO: Move out of the loop. This only happens for the first run.
- resultingFirstChild = newFiber;
- } else {
- // TODO: Defer siblings if we're not at the right index for this slot.
- // I.e. if we had null values before, then we want to defer this
- // for each null value. However, we also don't want to call updateSlot
- // with the previous one.
- previousNewFiber.sibling = newFiber;
- }
- previousNewFiber = newFiber;
- oldFiber = nextOldFiber;
- }
-
- if (step.done) {
- // We've reached the end of the new children. We can delete the rest.
- deleteRemainingChildren(returnFiber, oldFiber);
- if (getIsHydrating()) {
- const numberOfForks = newIdx;
- pushTreeFork(returnFiber, numberOfForks);
- }
- return resultingFirstChild;
- }
-
- if (oldFiber === null) {
- // If we don't have any more existing children we can choose a fast path
- // since the rest will all be insertions.
- for (; !step.done; newIdx++, step = newChildren.next()) {
- const newFiber = createChild(returnFiber, step.value, lanes);
- if (newFiber === null) {
- continue;
- }
- lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
- if (previousNewFiber === null) {
- // TODO: Move out of the loop. This only happens for the first run.
- resultingFirstChild = newFiber;
- } else {
- previousNewFiber.sibling = newFiber;
- }
- previousNewFiber = newFiber;
- }
- if (getIsHydrating()) {
- const numberOfForks = newIdx;
- pushTreeFork(returnFiber, numberOfForks);
- }
- return resultingFirstChild;
- }
-
- // Add all children to a key map for quick lookups.
- const existingChildren = mapRemainingChildren(returnFiber, oldFiber);
-
- // Keep scanning and use the map to restore deleted items as moves.
- for (; !step.done; newIdx++, step = newChildren.next()) {
- const newFiber = updateFromMap(
- existingChildren,
- returnFiber,
- newIdx,
- step.value,
- lanes,
- );
- if (newFiber !== null) {
- if (shouldTrackSideEffects) {
- if (newFiber.alternate !== null) {
- // The new fiber is a work in progress, but if there exists a
- // current, that means that we reused the fiber. We need to delete
- // it from the child list so that we don't add it to the deletion
- // list.
- existingChildren.delete(
- newFiber.key === null ? newIdx : newFiber.key,
- );
- }
- }
- lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
- if (previousNewFiber === null) {
- resultingFirstChild = newFiber;
- } else {
- previousNewFiber.sibling = newFiber;
- }
- previousNewFiber = newFiber;
- }
- }
-
- if (shouldTrackSideEffects) {
- // Any existing children that weren't consumed above were deleted. We need
- // to add them to the deletion list.
- existingChildren.forEach(child => deleteChild(returnFiber, child));
- }
-
- if (getIsHydrating()) {
- const numberOfForks = newIdx;
- pushTreeFork(returnFiber, numberOfForks);
- }
- return resultingFirstChild;
- }
-
- function reconcileSingleTextNode(
- returnFiber: Fiber,
- currentFirstChild: Fiber | null,
- textContent: string,
- lanes: Lanes,
- ): Fiber {
- // There's no need to check for keys on text nodes since we don't have a
- // way to define them.
- if (currentFirstChild !== null && currentFirstChild.tag === HostText) {
- // We already have an existing node so let's just update it and delete
- // the rest.
- deleteRemainingChildren(returnFiber, currentFirstChild.sibling);
- const existing = useFiber(currentFirstChild, textContent);
- existing.return = returnFiber;
- return existing;
- }
- // The existing first child is not a text node so we need to create one
- // and delete the existing ones.
- deleteRemainingChildren(returnFiber, currentFirstChild);
- const created = createFiberFromText(textContent, returnFiber.mode, lanes);
- created.return = returnFiber;
- return created;
- }
-
- function reconcileSingleElement(
- returnFiber: Fiber,
- currentFirstChild: Fiber | null,
- element: ReactElement,
- lanes: Lanes,
- ): Fiber {
- const key = element.key;
- let child = currentFirstChild;
- while (child !== null) {
- // TODO: If key === null and child.key === null, then this only applies to
- // the first item in the list.
- if (child.key === key) {
- const elementType = element.type;
- if (elementType === REACT_FRAGMENT_TYPE) {
- if (child.tag === Fragment) {
- deleteRemainingChildren(returnFiber, child.sibling);
- const existing = useFiber(child, element.props.children);
- existing.return = returnFiber;
- if (__DEV__) {
- existing._debugSource = element._source;
- existing._debugOwner = element._owner;
- }
- return existing;
- }
- } else {
- if (
- child.elementType === elementType ||
- // Keep this check inline so it only runs on the false path:
- (__DEV__
- ? isCompatibleFamilyForHotReloading(child, element)
- : false) ||
- // Lazy types should reconcile their resolved type.
- // We need to do this after the Hot Reloading check above,
- // because hot reloading has different semantics than prod because
- // it doesn't resuspend. So we can't let the call below suspend.
- (typeof elementType === 'object' &&
- elementType !== null &&
- elementType.$$typeof === REACT_LAZY_TYPE &&
- resolveLazy(elementType) === child.type)
- ) {
- deleteRemainingChildren(returnFiber, child.sibling);
- const existing = useFiber(child, element.props);
- existing.ref = coerceRef(returnFiber, child, element);
- existing.return = returnFiber;
- if (__DEV__) {
- existing._debugSource = element._source;
- existing._debugOwner = element._owner;
- }
- return existing;
- }
- }
- // Didn't match.
- deleteRemainingChildren(returnFiber, child);
- break;
- } else {
- deleteChild(returnFiber, child);
- }
- child = child.sibling;
- }
-
- if (element.type === REACT_FRAGMENT_TYPE) {
- const created = createFiberFromFragment(
- element.props.children,
- returnFiber.mode,
- lanes,
- element.key,
- );
- created.return = returnFiber;
- return created;
- } else {
- const created = createFiberFromElement(element, returnFiber.mode, lanes);
- created.ref = coerceRef(returnFiber, currentFirstChild, element);
- created.return = returnFiber;
- return created;
- }
- }
-
- function reconcileSinglePortal(
- returnFiber: Fiber,
- currentFirstChild: Fiber | null,
- portal: ReactPortal,
- lanes: Lanes,
- ): Fiber {
- const key = portal.key;
- let child = currentFirstChild;
- while (child !== null) {
- // TODO: If key === null and child.key === null, then this only applies to
- // the first item in the list.
- if (child.key === key) {
- if (
- child.tag === HostPortal &&
- child.stateNode.containerInfo === portal.containerInfo &&
- child.stateNode.implementation === portal.implementation
- ) {
- deleteRemainingChildren(returnFiber, child.sibling);
- const existing = useFiber(child, portal.children || []);
- existing.return = returnFiber;
- return existing;
- } else {
- deleteRemainingChildren(returnFiber, child);
- break;
- }
- } else {
- deleteChild(returnFiber, child);
- }
- child = child.sibling;
- }
-
- const created = createFiberFromPortal(portal, returnFiber.mode, lanes);
- created.return = returnFiber;
- return created;
- }
-
- // This API will tag the children with the side-effect of the reconciliation
- // itself. They will be added to the side-effect list as we pass through the
- // children and the parent.
- function reconcileChildFibers(
- returnFiber: Fiber,
- currentFirstChild: Fiber | null,
- newChild: any,
- lanes: Lanes,
- ): Fiber | null {
- // This function is not recursive.
- // If the top level item is an array, we treat it as a set of children,
- // not as a fragment. Nested arrays on the other hand will be treated as
- // fragment nodes. Recursion happens at the normal flow.
-
- // Handle top level unkeyed fragments as if they were arrays.
- // This leads to an ambiguity between <>{[...]}> and <>...>.
- // We treat the ambiguous cases above the same.
- const isUnkeyedTopLevelFragment =
- typeof newChild === 'object' &&
- newChild !== null &&
- newChild.type === REACT_FRAGMENT_TYPE &&
- newChild.key === null;
- if (isUnkeyedTopLevelFragment) {
- newChild = newChild.props.children;
- }
-
- // Handle object types
- if (typeof newChild === 'object' && newChild !== null) {
- switch (newChild.$$typeof) {
- case REACT_ELEMENT_TYPE:
- return placeSingleChild(
- reconcileSingleElement(
- returnFiber,
- currentFirstChild,
- newChild,
- lanes,
- ),
- );
- case REACT_PORTAL_TYPE:
- return placeSingleChild(
- reconcileSinglePortal(
- returnFiber,
- currentFirstChild,
- newChild,
- lanes,
- ),
- );
- case REACT_LAZY_TYPE:
- const payload = newChild._payload;
- const init = newChild._init;
- // TODO: This function is supposed to be non-recursive.
- return reconcileChildFibers(
- returnFiber,
- currentFirstChild,
- init(payload),
- lanes,
- );
- }
-
- if (isArray(newChild)) {
- return reconcileChildrenArray(
- returnFiber,
- currentFirstChild,
- newChild,
- lanes,
- );
- }
-
- if (getIteratorFn(newChild)) {
- return reconcileChildrenIterator(
- returnFiber,
- currentFirstChild,
- newChild,
- lanes,
- );
- }
-
- throwOnInvalidObjectType(returnFiber, newChild);
- }
-
- if (
- (typeof newChild === 'string' && newChild !== '') ||
- typeof newChild === 'number'
- ) {
- return placeSingleChild(
- reconcileSingleTextNode(
- returnFiber,
- currentFirstChild,
- '' + newChild,
- lanes,
- ),
- );
- }
-
- if (__DEV__) {
- if (typeof newChild === 'function') {
- warnOnFunctionType(returnFiber);
- }
- }
-
- // Remaining cases are all treated as empty.
- return deleteRemainingChildren(returnFiber, currentFirstChild);
- }
-
- return reconcileChildFibers;
-}
-
-export const reconcileChildFibers: ChildReconciler = createChildReconciler(
- true,
-);
-export const mountChildFibers: ChildReconciler = createChildReconciler(false);
-
-export function cloneChildFibers(
- current: Fiber | null,
- workInProgress: Fiber,
-): void {
- if (current !== null && workInProgress.child !== current.child) {
- throw new Error('Resuming work not yet implemented.');
- }
-
- if (workInProgress.child === null) {
- return;
- }
-
- let currentChild = workInProgress.child;
- let newChild = createWorkInProgress(currentChild, currentChild.pendingProps);
- workInProgress.child = newChild;
-
- newChild.return = workInProgress;
- while (currentChild.sibling !== null) {
- currentChild = currentChild.sibling;
- newChild = newChild.sibling = createWorkInProgress(
- currentChild,
- currentChild.pendingProps,
- );
- newChild.return = workInProgress;
- }
- newChild.sibling = null;
-}
-
-// Reset a workInProgress child set to prepare it for a second pass.
-export function resetChildFibers(workInProgress: Fiber, lanes: Lanes): void {
- let child = workInProgress.child;
- while (child !== null) {
- resetWorkInProgress(child, lanes);
- child = child.sibling;
- }
-}
diff --git a/packages/react-reconciler/src/ReactEventPriorities.js b/packages/react-reconciler/src/ReactEventPriorities.js
deleted file mode 100644
index 1aff2a4c91f80..0000000000000
--- a/packages/react-reconciler/src/ReactEventPriorities.js
+++ /dev/null
@@ -1,74 +0,0 @@
-/**
- * Copyright (c) Meta Platforms, Inc. and affiliates.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- *
- * @flow
- */
-
-import {enableNewReconciler} from 'shared/ReactFeatureFlags';
-
-import {
- DiscreteEventPriority as DiscreteEventPriority_old,
- ContinuousEventPriority as ContinuousEventPriority_old,
- DefaultEventPriority as DefaultEventPriority_old,
- IdleEventPriority as IdleEventPriority_old,
- getCurrentUpdatePriority as getCurrentUpdatePriority_old,
- setCurrentUpdatePriority as setCurrentUpdatePriority_old,
- runWithPriority as runWithPriority_old,
- isHigherEventPriority as isHigherEventPriority_old,
-} from './ReactEventPriorities.old';
-
-import {
- DiscreteEventPriority as DiscreteEventPriority_new,
- ContinuousEventPriority as ContinuousEventPriority_new,
- DefaultEventPriority as DefaultEventPriority_new,
- IdleEventPriority as IdleEventPriority_new,
- getCurrentUpdatePriority as getCurrentUpdatePriority_new,
- setCurrentUpdatePriority as setCurrentUpdatePriority_new,
- runWithPriority as runWithPriority_new,
- isHigherEventPriority as isHigherEventPriority_new,
-} from './ReactEventPriorities.new';
-
-export opaque type EventPriority = number;
-
-export const DiscreteEventPriority: EventPriority = enableNewReconciler
- ? (DiscreteEventPriority_new: any)
- : (DiscreteEventPriority_old: any);
-export const ContinuousEventPriority: EventPriority = enableNewReconciler
- ? (ContinuousEventPriority_new: any)
- : (ContinuousEventPriority_old: any);
-export const DefaultEventPriority: EventPriority = enableNewReconciler
- ? (DefaultEventPriority_new: any)
- : (DefaultEventPriority_old: any);
-export const IdleEventPriority: EventPriority = enableNewReconciler
- ? (IdleEventPriority_new: any)
- : (IdleEventPriority_old: any);
-
-export function runWithPriority(priority: EventPriority, fn: () => T): T {
- return enableNewReconciler
- ? runWithPriority_new((priority: any), fn)
- : runWithPriority_old((priority: any), fn);
-}
-
-export function getCurrentUpdatePriority(): EventPriority {
- return enableNewReconciler
- ? (getCurrentUpdatePriority_new(): any)
- : (getCurrentUpdatePriority_old(): any);
-}
-
-export function setCurrentUpdatePriority(priority: EventPriority): void {
- return enableNewReconciler
- ? setCurrentUpdatePriority_new((priority: any))
- : setCurrentUpdatePriority_old((priority: any));
-}
-
-export function isHigherEventPriority(
- a: EventPriority,
- b: EventPriority,
-): boolean {
- return enableNewReconciler
- ? isHigherEventPriority_new((a: any), (b: any))
- : isHigherEventPriority_old((a: any), (b: any));
-}
diff --git a/packages/react-reconciler/src/ReactEventPriorities.new.js b/packages/react-reconciler/src/ReactEventPriorities.new.js
deleted file mode 100644
index f9cc1e3ee9338..0000000000000
--- a/packages/react-reconciler/src/ReactEventPriorities.new.js
+++ /dev/null
@@ -1,82 +0,0 @@
-/**
- * Copyright (c) Meta Platforms, Inc. and affiliates.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- *
- * @flow
- */
-
-import type {Lane, Lanes} from './ReactFiberLane.new';
-
-import {
- NoLane,
- SyncLane,
- InputContinuousLane,
- DefaultLane,
- IdleLane,
- getHighestPriorityLane,
- includesNonIdleWork,
-} from './ReactFiberLane.new';
-
-export opaque type EventPriority = Lane;
-
-export const DiscreteEventPriority: EventPriority = SyncLane;
-export const ContinuousEventPriority: EventPriority = InputContinuousLane;
-export const DefaultEventPriority: EventPriority = DefaultLane;
-export const IdleEventPriority: EventPriority = IdleLane;
-
-let currentUpdatePriority: EventPriority = NoLane;
-
-export function getCurrentUpdatePriority(): EventPriority {
- return currentUpdatePriority;
-}
-
-export function setCurrentUpdatePriority(newPriority: EventPriority) {
- currentUpdatePriority = newPriority;
-}
-
-export function runWithPriority(priority: EventPriority, fn: () => T): T {
- const previousPriority = currentUpdatePriority;
- try {
- currentUpdatePriority = priority;
- return fn();
- } finally {
- currentUpdatePriority = previousPriority;
- }
-}
-
-export function higherEventPriority(
- a: EventPriority,
- b: EventPriority,
-): EventPriority {
- return a !== 0 && a < b ? a : b;
-}
-
-export function lowerEventPriority(
- a: EventPriority,
- b: EventPriority,
-): EventPriority {
- return a === 0 || a > b ? a : b;
-}
-
-export function isHigherEventPriority(
- a: EventPriority,
- b: EventPriority,
-): boolean {
- return a !== 0 && a < b;
-}
-
-export function lanesToEventPriority(lanes: Lanes): EventPriority {
- const lane = getHighestPriorityLane(lanes);
- if (!isHigherEventPriority(DiscreteEventPriority, lane)) {
- return DiscreteEventPriority;
- }
- if (!isHigherEventPriority(ContinuousEventPriority, lane)) {
- return ContinuousEventPriority;
- }
- if (includesNonIdleWork(lane)) {
- return DefaultEventPriority;
- }
- return IdleEventPriority;
-}
diff --git a/packages/react-reconciler/src/ReactFiber.new.js b/packages/react-reconciler/src/ReactFiber.new.js
deleted file mode 100644
index 9a60de9797392..0000000000000
--- a/packages/react-reconciler/src/ReactFiber.new.js
+++ /dev/null
@@ -1,910 +0,0 @@
-/**
- * Copyright (c) Meta Platforms, Inc. and affiliates.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- *
- * @flow
- */
-
-import type {ReactElement} from 'shared/ReactElementType';
-import type {ReactFragment, ReactPortal, ReactScope} from 'shared/ReactTypes';
-import type {Fiber} from './ReactInternalTypes';
-import type {RootTag} from './ReactRootTags';
-import type {WorkTag} from './ReactWorkTags';
-import type {TypeOfMode} from './ReactTypeOfMode';
-import type {Lanes} from './ReactFiberLane.new';
-import type {SuspenseInstance} from './ReactFiberHostConfig';
-import type {
- OffscreenProps,
- OffscreenInstance,
-} from './ReactFiberOffscreenComponent';
-import type {TracingMarkerInstance} from './ReactFiberTracingMarkerComponent.new';
-
-import {
- supportsResources,
- supportsSingletons,
- isHostResourceType,
- isHostSingletonType,
-} from './ReactFiberHostConfig';
-import {
- createRootStrictEffectsByDefault,
- enableCache,
- enableProfilerTimer,
- enableScopeAPI,
- enableLegacyHidden,
- enableSyncDefaultUpdates,
- allowConcurrentByDefault,
- enableTransitionTracing,
- enableDebugTracing,
- enableFloat,
- enableHostSingletons,
-} from 'shared/ReactFeatureFlags';
-import {NoFlags, Placement, StaticMask} from './ReactFiberFlags';
-import {ConcurrentRoot} from './ReactRootTags';
-import {
- IndeterminateComponent,
- ClassComponent,
- HostRoot,
- HostComponent,
- HostText,
- HostPortal,
- HostResource,
- HostSingleton,
- ForwardRef,
- Fragment,
- Mode,
- ContextProvider,
- ContextConsumer,
- Profiler,
- SuspenseComponent,
- SuspenseListComponent,
- DehydratedFragment,
- FunctionComponent,
- MemoComponent,
- SimpleMemoComponent,
- LazyComponent,
- ScopeComponent,
- OffscreenComponent,
- LegacyHiddenComponent,
- CacheComponent,
- TracingMarkerComponent,
-} from './ReactWorkTags';
-import {OffscreenVisible} from './ReactFiberOffscreenComponent';
-import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
-import {isDevToolsPresent} from './ReactFiberDevToolsHook.new';
-import {
- resolveClassForHotReloading,
- resolveFunctionForHotReloading,
- resolveForwardRefForHotReloading,
-} from './ReactFiberHotReloading.new';
-import {NoLanes} from './ReactFiberLane.new';
-import {
- NoMode,
- ConcurrentMode,
- DebugTracingMode,
- ProfileMode,
- StrictLegacyMode,
- StrictEffectsMode,
- ConcurrentUpdatesByDefaultMode,
-} from './ReactTypeOfMode';
-import {
- REACT_FORWARD_REF_TYPE,
- REACT_FRAGMENT_TYPE,
- REACT_DEBUG_TRACING_MODE_TYPE,
- REACT_STRICT_MODE_TYPE,
- REACT_PROFILER_TYPE,
- REACT_PROVIDER_TYPE,
- REACT_CONTEXT_TYPE,
- REACT_SUSPENSE_TYPE,
- REACT_SUSPENSE_LIST_TYPE,
- REACT_MEMO_TYPE,
- REACT_LAZY_TYPE,
- REACT_SCOPE_TYPE,
- REACT_OFFSCREEN_TYPE,
- REACT_LEGACY_HIDDEN_TYPE,
- REACT_CACHE_TYPE,
- REACT_TRACING_MARKER_TYPE,
-} from 'shared/ReactSymbols';
-import {TransitionTracingMarker} from './ReactFiberTracingMarkerComponent.new';
-import {detachOffscreenInstance} from './ReactFiberCommitWork.new';
-import {getHostContext} from './ReactFiberHostContext.new';
-
-export type {Fiber};
-
-let hasBadMapPolyfill;
-
-if (__DEV__) {
- hasBadMapPolyfill = false;
- try {
- const nonExtensibleObject = Object.preventExtensions({});
- /* eslint-disable no-new */
- new Map([[nonExtensibleObject, null]]);
- new Set([nonExtensibleObject]);
- /* eslint-enable no-new */
- } catch (e) {
- // TODO: Consider warning about bad polyfills
- hasBadMapPolyfill = true;
- }
-}
-
-function FiberNode(
- tag: WorkTag,
- pendingProps: mixed,
- key: null | string,
- mode: TypeOfMode,
-) {
- // Instance
- this.tag = tag;
- this.key = key;
- this.elementType = null;
- this.type = null;
- this.stateNode = null;
-
- // Fiber
- this.return = null;
- this.child = null;
- this.sibling = null;
- this.index = 0;
-
- this.ref = null;
- this.refCleanup = null;
-
- this.pendingProps = pendingProps;
- this.memoizedProps = null;
- this.updateQueue = null;
- this.memoizedState = null;
- this.dependencies = null;
-
- this.mode = mode;
-
- // Effects
- this.flags = NoFlags;
- this.subtreeFlags = NoFlags;
- this.deletions = null;
-
- this.lanes = NoLanes;
- this.childLanes = NoLanes;
-
- this.alternate = null;
-
- if (enableProfilerTimer) {
- // Note: The following is done to avoid a v8 performance cliff.
- //
- // Initializing the fields below to smis and later updating them with
- // double values will cause Fibers to end up having separate shapes.
- // This behavior/bug has something to do with Object.preventExtension().
- // Fortunately this only impacts DEV builds.
- // Unfortunately it makes React unusably slow for some applications.
- // To work around this, initialize the fields below with doubles.
- //
- // Learn more about this here:
- // https://github.com/facebook/react/issues/14365
- // https://bugs.chromium.org/p/v8/issues/detail?id=8538
- this.actualDuration = Number.NaN;
- this.actualStartTime = Number.NaN;
- this.selfBaseDuration = Number.NaN;
- this.treeBaseDuration = Number.NaN;
-
- // It's okay to replace the initial doubles with smis after initialization.
- // This won't trigger the performance cliff mentioned above,
- // and it simplifies other profiler code (including DevTools).
- this.actualDuration = 0;
- this.actualStartTime = -1;
- this.selfBaseDuration = 0;
- this.treeBaseDuration = 0;
- }
-
- if (__DEV__) {
- // This isn't directly used but is handy for debugging internals:
-
- this._debugSource = null;
- this._debugOwner = null;
- this._debugNeedsRemount = false;
- this._debugHookTypes = null;
- if (!hasBadMapPolyfill && typeof Object.preventExtensions === 'function') {
- Object.preventExtensions(this);
- }
- }
-}
-
-// This is a constructor function, rather than a POJO constructor, still
-// please ensure we do the following:
-// 1) Nobody should add any instance methods on this. Instance methods can be
-// more difficult to predict when they get optimized and they are almost
-// never inlined properly in static compilers.
-// 2) Nobody should rely on `instanceof Fiber` for type testing. We should
-// always know when it is a fiber.
-// 3) We might want to experiment with using numeric keys since they are easier
-// to optimize in a non-JIT environment.
-// 4) We can easily go from a constructor to a createFiber object literal if that
-// is faster.
-// 5) It should be easy to port this to a C struct and keep a C implementation
-// compatible.
-const createFiber = function(
- tag: WorkTag,
- pendingProps: mixed,
- key: null | string,
- mode: TypeOfMode,
-): Fiber {
- // $FlowFixMe: the shapes are exact here but Flow doesn't like constructors
- return new FiberNode(tag, pendingProps, key, mode);
-};
-
-function shouldConstruct(Component: Function) {
- const prototype = Component.prototype;
- return !!(prototype && prototype.isReactComponent);
-}
-
-export function isSimpleFunctionComponent(type: any): boolean {
- return (
- typeof type === 'function' &&
- !shouldConstruct(type) &&
- type.defaultProps === undefined
- );
-}
-
-export function resolveLazyComponentTag(Component: Function): WorkTag {
- if (typeof Component === 'function') {
- return shouldConstruct(Component) ? ClassComponent : FunctionComponent;
- } else if (Component !== undefined && Component !== null) {
- const $$typeof = Component.$$typeof;
- if ($$typeof === REACT_FORWARD_REF_TYPE) {
- return ForwardRef;
- }
- if ($$typeof === REACT_MEMO_TYPE) {
- return MemoComponent;
- }
- }
- return IndeterminateComponent;
-}
-
-// This is used to create an alternate fiber to do work on.
-export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
- let workInProgress = current.alternate;
- if (workInProgress === null) {
- // We use a double buffering pooling technique because we know that we'll
- // only ever need at most two versions of a tree. We pool the "other" unused
- // node that we're free to reuse. This is lazily created to avoid allocating
- // extra objects for things that are never updated. It also allow us to
- // reclaim the extra memory if needed.
- workInProgress = createFiber(
- current.tag,
- pendingProps,
- current.key,
- current.mode,
- );
- workInProgress.elementType = current.elementType;
- workInProgress.type = current.type;
- workInProgress.stateNode = current.stateNode;
-
- if (__DEV__) {
- // DEV-only fields
-
- workInProgress._debugSource = current._debugSource;
- workInProgress._debugOwner = current._debugOwner;
- workInProgress._debugHookTypes = current._debugHookTypes;
- }
-
- workInProgress.alternate = current;
- current.alternate = workInProgress;
- } else {
- workInProgress.pendingProps = pendingProps;
- // Needed because Blocks store data on type.
- workInProgress.type = current.type;
-
- // We already have an alternate.
- // Reset the effect tag.
- workInProgress.flags = NoFlags;
-
- // The effects are no longer valid.
- workInProgress.subtreeFlags = NoFlags;
- workInProgress.deletions = null;
-
- if (enableProfilerTimer) {
- // We intentionally reset, rather than copy, actualDuration & actualStartTime.
- // This prevents time from endlessly accumulating in new commits.
- // This has the downside of resetting values for different priority renders,
- // But works for yielding (the common case) and should support resuming.
- workInProgress.actualDuration = 0;
- workInProgress.actualStartTime = -1;
- }
- }
-
- // Reset all effects except static ones.
- // Static effects are not specific to a render.
- workInProgress.flags = current.flags & StaticMask;
- workInProgress.childLanes = current.childLanes;
- workInProgress.lanes = current.lanes;
-
- workInProgress.child = current.child;
- workInProgress.memoizedProps = current.memoizedProps;
- workInProgress.memoizedState = current.memoizedState;
- workInProgress.updateQueue = current.updateQueue;
-
- // Clone the dependencies object. This is mutated during the render phase, so
- // it cannot be shared with the current fiber.
- const currentDependencies = current.dependencies;
- workInProgress.dependencies =
- currentDependencies === null
- ? null
- : {
- lanes: currentDependencies.lanes,
- firstContext: currentDependencies.firstContext,
- };
-
- // These will be overridden during the parent's reconciliation
- workInProgress.sibling = current.sibling;
- workInProgress.index = current.index;
- workInProgress.ref = current.ref;
- workInProgress.refCleanup = current.refCleanup;
-
- if (enableProfilerTimer) {
- workInProgress.selfBaseDuration = current.selfBaseDuration;
- workInProgress.treeBaseDuration = current.treeBaseDuration;
- }
-
- if (__DEV__) {
- workInProgress._debugNeedsRemount = current._debugNeedsRemount;
- switch (workInProgress.tag) {
- case IndeterminateComponent:
- case FunctionComponent:
- case SimpleMemoComponent:
- workInProgress.type = resolveFunctionForHotReloading(current.type);
- break;
- case ClassComponent:
- workInProgress.type = resolveClassForHotReloading(current.type);
- break;
- case ForwardRef:
- workInProgress.type = resolveForwardRefForHotReloading(current.type);
- break;
- default:
- break;
- }
- }
-
- return workInProgress;
-}
-
-// Used to reuse a Fiber for a second pass.
-export function resetWorkInProgress(
- workInProgress: Fiber,
- renderLanes: Lanes,
-): Fiber {
- // This resets the Fiber to what createFiber or createWorkInProgress would
- // have set the values to before during the first pass. Ideally this wouldn't
- // be necessary but unfortunately many code paths reads from the workInProgress
- // when they should be reading from current and writing to workInProgress.
-
- // We assume pendingProps, index, key, ref, return are still untouched to
- // avoid doing another reconciliation.
-
- // Reset the effect flags but keep any Placement tags, since that's something
- // that child fiber is setting, not the reconciliation.
- workInProgress.flags &= StaticMask | Placement;
-
- // The effects are no longer valid.
-
- const current = workInProgress.alternate;
- if (current === null) {
- // Reset to createFiber's initial values.
- workInProgress.childLanes = NoLanes;
- workInProgress.lanes = renderLanes;
-
- workInProgress.child = null;
- workInProgress.subtreeFlags = NoFlags;
- workInProgress.memoizedProps = null;
- workInProgress.memoizedState = null;
- workInProgress.updateQueue = null;
-
- workInProgress.dependencies = null;
-
- workInProgress.stateNode = null;
-
- if (enableProfilerTimer) {
- // Note: We don't reset the actualTime counts. It's useful to accumulate
- // actual time across multiple render passes.
- workInProgress.selfBaseDuration = 0;
- workInProgress.treeBaseDuration = 0;
- }
- } else {
- // Reset to the cloned values that createWorkInProgress would've.
- workInProgress.childLanes = current.childLanes;
- workInProgress.lanes = current.lanes;
-
- workInProgress.child = current.child;
- workInProgress.subtreeFlags = NoFlags;
- workInProgress.deletions = null;
- workInProgress.memoizedProps = current.memoizedProps;
- workInProgress.memoizedState = current.memoizedState;
- workInProgress.updateQueue = current.updateQueue;
- // Needed because Blocks store data on type.
- workInProgress.type = current.type;
-
- // Clone the dependencies object. This is mutated during the render phase, so
- // it cannot be shared with the current fiber.
- const currentDependencies = current.dependencies;
- workInProgress.dependencies =
- currentDependencies === null
- ? null
- : {
- lanes: currentDependencies.lanes,
- firstContext: currentDependencies.firstContext,
- };
-
- if (enableProfilerTimer) {
- // Note: We don't reset the actualTime counts. It's useful to accumulate
- // actual time across multiple render passes.
- workInProgress.selfBaseDuration = current.selfBaseDuration;
- workInProgress.treeBaseDuration = current.treeBaseDuration;
- }
- }
-
- return workInProgress;
-}
-
-export function createHostRootFiber(
- tag: RootTag,
- isStrictMode: boolean,
- concurrentUpdatesByDefaultOverride: null | boolean,
-): Fiber {
- let mode;
- if (tag === ConcurrentRoot) {
- mode = ConcurrentMode;
- if (isStrictMode === true || createRootStrictEffectsByDefault) {
- mode |= StrictLegacyMode | StrictEffectsMode;
- }
- if (
- // We only use this flag for our repo tests to check both behaviors.
- // TODO: Flip this flag and rename it something like "forceConcurrentByDefaultForTesting"
- !enableSyncDefaultUpdates ||
- // Only for internal experiments.
- (allowConcurrentByDefault && concurrentUpdatesByDefaultOverride)
- ) {
- mode |= ConcurrentUpdatesByDefaultMode;
- }
- } else {
- mode = NoMode;
- }
-
- if (enableProfilerTimer && isDevToolsPresent) {
- // Always collect profile timings when DevTools are present.
- // This enables DevTools to start capturing timing at any point–
- // Without some nodes in the tree having empty base times.
- mode |= ProfileMode;
- }
-
- return createFiber(HostRoot, null, null, mode);
-}
-
-export function createFiberFromTypeAndProps(
- type: any, // React$ElementType
- key: null | string,
- pendingProps: any,
- owner: null | Fiber,
- mode: TypeOfMode,
- lanes: Lanes,
-): Fiber {
- let fiberTag = IndeterminateComponent;
- // The resolved type is set if we know what the final type will be. I.e. it's not lazy.
- let resolvedType = type;
- if (typeof type === 'function') {
- if (shouldConstruct(type)) {
- fiberTag = ClassComponent;
- if (__DEV__) {
- resolvedType = resolveClassForHotReloading(resolvedType);
- }
- } else {
- if (__DEV__) {
- resolvedType = resolveFunctionForHotReloading(resolvedType);
- }
- }
- } else if (typeof type === 'string') {
- if (
- enableFloat &&
- supportsResources &&
- enableHostSingletons &&
- supportsSingletons
- ) {
- const hostContext = getHostContext();
- fiberTag = isHostResourceType(type, pendingProps, hostContext)
- ? HostResource
- : isHostSingletonType(type)
- ? HostSingleton
- : HostComponent;
- } else if (enableFloat && supportsResources) {
- const hostContext = getHostContext();
- fiberTag = isHostResourceType(type, pendingProps, hostContext)
- ? HostResource
- : HostComponent;
- } else if (enableHostSingletons && supportsSingletons) {
- fiberTag = isHostSingletonType(type) ? HostSingleton : HostComponent;
- } else {
- fiberTag = HostComponent;
- }
- } else {
- getTag: switch (type) {
- case REACT_FRAGMENT_TYPE:
- return createFiberFromFragment(pendingProps.children, mode, lanes, key);
- case REACT_STRICT_MODE_TYPE:
- fiberTag = Mode;
- mode |= StrictLegacyMode;
- if ((mode & ConcurrentMode) !== NoMode) {
- // Strict effects should never run on legacy roots
- mode |= StrictEffectsMode;
- }
- break;
- case REACT_PROFILER_TYPE:
- return createFiberFromProfiler(pendingProps, mode, lanes, key);
- case REACT_SUSPENSE_TYPE:
- return createFiberFromSuspense(pendingProps, mode, lanes, key);
- case REACT_SUSPENSE_LIST_TYPE:
- return createFiberFromSuspenseList(pendingProps, mode, lanes, key);
- case REACT_OFFSCREEN_TYPE:
- return createFiberFromOffscreen(pendingProps, mode, lanes, key);
- case REACT_LEGACY_HIDDEN_TYPE:
- if (enableLegacyHidden) {
- return createFiberFromLegacyHidden(pendingProps, mode, lanes, key);
- }
- // eslint-disable-next-line no-fallthrough
- case REACT_SCOPE_TYPE:
- if (enableScopeAPI) {
- return createFiberFromScope(type, pendingProps, mode, lanes, key);
- }
- // eslint-disable-next-line no-fallthrough
- case REACT_CACHE_TYPE:
- if (enableCache) {
- return createFiberFromCache(pendingProps, mode, lanes, key);
- }
- // eslint-disable-next-line no-fallthrough
- case REACT_TRACING_MARKER_TYPE:
- if (enableTransitionTracing) {
- return createFiberFromTracingMarker(pendingProps, mode, lanes, key);
- }
- // eslint-disable-next-line no-fallthrough
- case REACT_DEBUG_TRACING_MODE_TYPE:
- if (enableDebugTracing) {
- fiberTag = Mode;
- mode |= DebugTracingMode;
- break;
- }
- // eslint-disable-next-line no-fallthrough
- default: {
- if (typeof type === 'object' && type !== null) {
- switch (type.$$typeof) {
- case REACT_PROVIDER_TYPE:
- fiberTag = ContextProvider;
- break getTag;
- case REACT_CONTEXT_TYPE:
- // This is a consumer
- fiberTag = ContextConsumer;
- break getTag;
- case REACT_FORWARD_REF_TYPE:
- fiberTag = ForwardRef;
- if (__DEV__) {
- resolvedType = resolveForwardRefForHotReloading(resolvedType);
- }
- break getTag;
- case REACT_MEMO_TYPE:
- fiberTag = MemoComponent;
- break getTag;
- case REACT_LAZY_TYPE:
- fiberTag = LazyComponent;
- resolvedType = null;
- break getTag;
- }
- }
- let info = '';
- if (__DEV__) {
- if (
- type === undefined ||
- (typeof type === 'object' &&
- type !== null &&
- Object.keys(type).length === 0)
- ) {
- info +=
- ' You likely forgot to export your component from the file ' +
- "it's defined in, or you might have mixed up default and " +
- 'named imports.';
- }
- const ownerName = owner ? getComponentNameFromFiber(owner) : null;
- if (ownerName) {
- info += '\n\nCheck the render method of `' + ownerName + '`.';
- }
- }
-
- throw new Error(
- 'Element type is invalid: expected a string (for built-in ' +
- 'components) or a class/function (for composite components) ' +
- `but got: ${type == null ? type : typeof type}.${info}`,
- );
- }
- }
- }
-
- const fiber = createFiber(fiberTag, pendingProps, key, mode);
- fiber.elementType = type;
- fiber.type = resolvedType;
- fiber.lanes = lanes;
-
- if (__DEV__) {
- fiber._debugOwner = owner;
- }
-
- return fiber;
-}
-
-export function createFiberFromElement(
- element: ReactElement,
- mode: TypeOfMode,
- lanes: Lanes,
-): Fiber {
- let owner = null;
- if (__DEV__) {
- owner = element._owner;
- }
- const type = element.type;
- const key = element.key;
- const pendingProps = element.props;
- const fiber = createFiberFromTypeAndProps(
- type,
- key,
- pendingProps,
- owner,
- mode,
- lanes,
- );
- if (__DEV__) {
- fiber._debugSource = element._source;
- fiber._debugOwner = element._owner;
- }
- return fiber;
-}
-
-export function createFiberFromFragment(
- elements: ReactFragment,
- mode: TypeOfMode,
- lanes: Lanes,
- key: null | string,
-): Fiber {
- const fiber = createFiber(Fragment, elements, key, mode);
- fiber.lanes = lanes;
- return fiber;
-}
-
-function createFiberFromScope(
- scope: ReactScope,
- pendingProps: any,
- mode: TypeOfMode,
- lanes: Lanes,
- key: null | string,
-) {
- const fiber = createFiber(ScopeComponent, pendingProps, key, mode);
- fiber.type = scope;
- fiber.elementType = scope;
- fiber.lanes = lanes;
- return fiber;
-}
-
-function createFiberFromProfiler(
- pendingProps: any,
- mode: TypeOfMode,
- lanes: Lanes,
- key: null | string,
-): Fiber {
- if (__DEV__) {
- if (typeof pendingProps.id !== 'string') {
- console.error(
- 'Profiler must specify an "id" of type `string` as a prop. Received the type `%s` instead.',
- typeof pendingProps.id,
- );
- }
- }
-
- const fiber = createFiber(Profiler, pendingProps, key, mode | ProfileMode);
- fiber.elementType = REACT_PROFILER_TYPE;
- fiber.lanes = lanes;
-
- if (enableProfilerTimer) {
- fiber.stateNode = {
- effectDuration: 0,
- passiveEffectDuration: 0,
- };
- }
-
- return fiber;
-}
-
-export function createFiberFromSuspense(
- pendingProps: any,
- mode: TypeOfMode,
- lanes: Lanes,
- key: null | string,
-): Fiber {
- const fiber = createFiber(SuspenseComponent, pendingProps, key, mode);
- fiber.elementType = REACT_SUSPENSE_TYPE;
- fiber.lanes = lanes;
- return fiber;
-}
-
-export function createFiberFromSuspenseList(
- pendingProps: any,
- mode: TypeOfMode,
- lanes: Lanes,
- key: null | string,
-): Fiber {
- const fiber = createFiber(SuspenseListComponent, pendingProps, key, mode);
- fiber.elementType = REACT_SUSPENSE_LIST_TYPE;
- fiber.lanes = lanes;
- return fiber;
-}
-
-export function createFiberFromOffscreen(
- pendingProps: OffscreenProps,
- mode: TypeOfMode,
- lanes: Lanes,
- key: null | string,
-): Fiber {
- const fiber = createFiber(OffscreenComponent, pendingProps, key, mode);
- fiber.elementType = REACT_OFFSCREEN_TYPE;
- fiber.lanes = lanes;
- const primaryChildInstance: OffscreenInstance = {
- _visibility: OffscreenVisible,
- _pendingMarkers: null,
- _retryCache: null,
- _transitions: null,
- _current: null,
- detach: () => detachOffscreenInstance(primaryChildInstance),
- };
- fiber.stateNode = primaryChildInstance;
- return fiber;
-}
-
-export function createFiberFromLegacyHidden(
- pendingProps: OffscreenProps,
- mode: TypeOfMode,
- lanes: Lanes,
- key: null | string,
-): Fiber {
- const fiber = createFiber(LegacyHiddenComponent, pendingProps, key, mode);
- fiber.elementType = REACT_LEGACY_HIDDEN_TYPE;
- fiber.lanes = lanes;
- // Adding a stateNode for legacy hidden because it's currently using
- // the offscreen implementation, which depends on a state node
- const instance: OffscreenInstance = {
- _visibility: OffscreenVisible,
- _pendingMarkers: null,
- _transitions: null,
- _retryCache: null,
- _current: null,
- detach: () => detachOffscreenInstance(instance),
- };
- fiber.stateNode = instance;
- return fiber;
-}
-
-export function createFiberFromCache(
- pendingProps: any,
- mode: TypeOfMode,
- lanes: Lanes,
- key: null | string,
-): Fiber {
- const fiber = createFiber(CacheComponent, pendingProps, key, mode);
- fiber.elementType = REACT_CACHE_TYPE;
- fiber.lanes = lanes;
- return fiber;
-}
-
-export function createFiberFromTracingMarker(
- pendingProps: any,
- mode: TypeOfMode,
- lanes: Lanes,
- key: null | string,
-): Fiber {
- const fiber = createFiber(TracingMarkerComponent, pendingProps, key, mode);
- fiber.elementType = REACT_TRACING_MARKER_TYPE;
- fiber.lanes = lanes;
- const tracingMarkerInstance: TracingMarkerInstance = {
- tag: TransitionTracingMarker,
- transitions: null,
- pendingBoundaries: null,
- aborts: null,
- name: pendingProps.name,
- };
- fiber.stateNode = tracingMarkerInstance;
- return fiber;
-}
-
-export function createFiberFromText(
- content: string,
- mode: TypeOfMode,
- lanes: Lanes,
-): Fiber {
- const fiber = createFiber(HostText, content, null, mode);
- fiber.lanes = lanes;
- return fiber;
-}
-
-export function createFiberFromHostInstanceForDeletion(): Fiber {
- const fiber = createFiber(HostComponent, null, null, NoMode);
- fiber.elementType = 'DELETED';
- return fiber;
-}
-
-export function createFiberFromDehydratedFragment(
- dehydratedNode: SuspenseInstance,
-): Fiber {
- const fiber = createFiber(DehydratedFragment, null, null, NoMode);
- fiber.stateNode = dehydratedNode;
- return fiber;
-}
-
-export function createFiberFromPortal(
- portal: ReactPortal,
- mode: TypeOfMode,
- lanes: Lanes,
-): Fiber {
- const pendingProps = portal.children !== null ? portal.children : [];
- const fiber = createFiber(HostPortal, pendingProps, portal.key, mode);
- fiber.lanes = lanes;
- fiber.stateNode = {
- containerInfo: portal.containerInfo,
- pendingChildren: null, // Used by persistent updates
- implementation: portal.implementation,
- };
- return fiber;
-}
-
-// Used for stashing WIP properties to replay failed work in DEV.
-export function assignFiberPropertiesInDEV(
- target: Fiber | null,
- source: Fiber,
-): Fiber {
- if (target === null) {
- // This Fiber's initial properties will always be overwritten.
- // We only use a Fiber to ensure the same hidden class so DEV isn't slow.
- target = createFiber(IndeterminateComponent, null, null, NoMode);
- }
-
- // This is intentionally written as a list of all properties.
- // We tried to use Object.assign() instead but this is called in
- // the hottest path, and Object.assign() was too slow:
- // https://github.com/facebook/react/issues/12502
- // This code is DEV-only so size is not a concern.
-
- target.tag = source.tag;
- target.key = source.key;
- target.elementType = source.elementType;
- target.type = source.type;
- target.stateNode = source.stateNode;
- target.return = source.return;
- target.child = source.child;
- target.sibling = source.sibling;
- target.index = source.index;
- target.ref = source.ref;
- target.refCleanup = source.refCleanup;
- target.pendingProps = source.pendingProps;
- target.memoizedProps = source.memoizedProps;
- target.updateQueue = source.updateQueue;
- target.memoizedState = source.memoizedState;
- target.dependencies = source.dependencies;
- target.mode = source.mode;
- target.flags = source.flags;
- target.subtreeFlags = source.subtreeFlags;
- target.deletions = source.deletions;
- target.lanes = source.lanes;
- target.childLanes = source.childLanes;
- target.alternate = source.alternate;
- if (enableProfilerTimer) {
- target.actualDuration = source.actualDuration;
- target.actualStartTime = source.actualStartTime;
- target.selfBaseDuration = source.selfBaseDuration;
- target.treeBaseDuration = source.treeBaseDuration;
- }
-
- target._debugSource = source._debugSource;
- target._debugOwner = source._debugOwner;
- target._debugNeedsRemount = source._debugNeedsRemount;
- target._debugHookTypes = source._debugHookTypes;
- return target;
-}
diff --git a/packages/react-reconciler/src/ReactFiberAct.new.js b/packages/react-reconciler/src/ReactFiberAct.new.js
deleted file mode 100644
index 40f50d1952788..0000000000000
--- a/packages/react-reconciler/src/ReactFiberAct.new.js
+++ /dev/null
@@ -1,57 +0,0 @@
-/**
- * Copyright (c) Meta Platforms, Inc. and affiliates.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- *
- * @flow
- */
-
-import type {Fiber} from './ReactFiber.new';
-
-import ReactSharedInternals from 'shared/ReactSharedInternals';
-
-import {warnsIfNotActing} from './ReactFiberHostConfig';
-
-const {ReactCurrentActQueue} = ReactSharedInternals;
-
-export function isLegacyActEnvironment(fiber: Fiber): boolean {
- if (__DEV__) {
- // Legacy mode. We preserve the behavior of React 17's act. It assumes an
- // act environment whenever `jest` is defined, but you can still turn off
- // spurious warnings by setting IS_REACT_ACT_ENVIRONMENT explicitly
- // to false.
-
- const isReactActEnvironmentGlobal =
- // $FlowFixMe – Flow doesn't know about IS_REACT_ACT_ENVIRONMENT global
- typeof IS_REACT_ACT_ENVIRONMENT !== 'undefined'
- ? IS_REACT_ACT_ENVIRONMENT
- : undefined;
-
- // $FlowFixMe - Flow doesn't know about jest
- const jestIsDefined = typeof jest !== 'undefined';
- return (
- warnsIfNotActing && jestIsDefined && isReactActEnvironmentGlobal !== false
- );
- }
- return false;
-}
-
-export function isConcurrentActEnvironment(): void | boolean {
- if (__DEV__) {
- const isReactActEnvironmentGlobal =
- typeof IS_REACT_ACT_ENVIRONMENT !== 'undefined'
- ? IS_REACT_ACT_ENVIRONMENT
- : undefined;
-
- if (!isReactActEnvironmentGlobal && ReactCurrentActQueue.current !== null) {
- // TODO: Include link to relevant documentation page.
- console.error(
- 'The current testing environment is not configured to support ' +
- 'act(...)',
- );
- }
- return isReactActEnvironmentGlobal;
- }
- return false;
-}
diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.new.js b/packages/react-reconciler/src/ReactFiberBeginWork.new.js
deleted file mode 100644
index 50dd170a6028f..0000000000000
--- a/packages/react-reconciler/src/ReactFiberBeginWork.new.js
+++ /dev/null
@@ -1,4212 +0,0 @@
-/**
- * Copyright (c) Meta Platforms, Inc. and affiliates.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- *
- * @flow
- */
-
-import type {
- ReactProviderType,
- ReactContext,
- ReactNodeList,
- MutableSource,
-} from 'shared/ReactTypes';
-import type {LazyComponent as LazyComponentType} from 'react/src/ReactLazy';
-import type {Fiber, FiberRoot} from './ReactInternalTypes';
-import type {TypeOfMode} from './ReactTypeOfMode';
-import type {Lanes, Lane} from './ReactFiberLane.new';
-import type {
- SuspenseState,
- SuspenseListRenderState,
- SuspenseListTailMode,
-} from './ReactFiberSuspenseComponent.new';
-import type {SuspenseContext} from './ReactFiberSuspenseContext.new';
-import type {
- OffscreenProps,
- OffscreenState,
- OffscreenQueue,
- OffscreenInstance,
-} from './ReactFiberOffscreenComponent';
-import {OffscreenDetached} from './ReactFiberOffscreenComponent';
-import type {
- Cache,
- CacheComponentState,
- SpawnedCachePool,
-} from './ReactFiberCacheComponent.new';
-import type {UpdateQueue} from './ReactFiberClassUpdateQueue.new';
-import type {RootState} from './ReactFiberRoot.new';
-import type {TracingMarkerInstance} from './ReactFiberTracingMarkerComponent.new';
-
-import checkPropTypes from 'shared/checkPropTypes';
-import {
- markComponentRenderStarted,
- markComponentRenderStopped,
- setIsStrictModeForDevtools,
-} from './ReactFiberDevToolsHook.new';
-import {
- IndeterminateComponent,
- FunctionComponent,
- ClassComponent,
- HostRoot,
- HostComponent,
- HostResource,
- HostSingleton,
- HostText,
- HostPortal,
- ForwardRef,
- Fragment,
- Mode,
- ContextProvider,
- ContextConsumer,
- Profiler,
- SuspenseComponent,
- SuspenseListComponent,
- MemoComponent,
- SimpleMemoComponent,
- LazyComponent,
- IncompleteClassComponent,
- ScopeComponent,
- OffscreenComponent,
- LegacyHiddenComponent,
- CacheComponent,
- TracingMarkerComponent,
-} from './ReactWorkTags';
-import {
- NoFlags,
- PerformedWork,
- Placement,
- Hydrating,
- ContentReset,
- DidCapture,
- Update,
- Ref,
- RefStatic,
- ChildDeletion,
- ForceUpdateForLegacySuspense,
- StaticMask,
- ShouldCapture,
- ForceClientRender,
- Passive,
-} from './ReactFiberFlags';
-import ReactSharedInternals from 'shared/ReactSharedInternals';
-import {
- debugRenderPhaseSideEffectsForStrictMode,
- disableLegacyContext,
- disableModulePatternComponents,
- enableProfilerCommitHooks,
- enableProfilerTimer,
- warnAboutDefaultPropsOnFunctionComponents,
- enableScopeAPI,
- enableCache,
- enableLazyContextPropagation,
- enableSchedulingProfiler,
- enableTransitionTracing,
- enableLegacyHidden,
- enableCPUSuspense,
- enableUseMutableSource,
- enableFloat,
- enableHostSingletons,
-} from 'shared/ReactFeatureFlags';
-import isArray from 'shared/isArray';
-import shallowEqual from 'shared/shallowEqual';
-import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
-import getComponentNameFromType from 'shared/getComponentNameFromType';
-import ReactStrictModeWarnings from './ReactStrictModeWarnings.new';
-import {REACT_LAZY_TYPE, getIteratorFn} from 'shared/ReactSymbols';
-import {
- getCurrentFiberOwnerNameInDevOrNull,
- setIsRendering,
-} from './ReactCurrentFiber';
-import {
- resolveFunctionForHotReloading,
- resolveForwardRefForHotReloading,
- resolveClassForHotReloading,
-} from './ReactFiberHotReloading.new';
-
-import {
- mountChildFibers,
- reconcileChildFibers,
- cloneChildFibers,
-} from './ReactChildFiber.new';
-import {
- processUpdateQueue,
- cloneUpdateQueue,
- initializeUpdateQueue,
- enqueueCapturedUpdate,
-} from './ReactFiberClassUpdateQueue.new';
-import {
- NoLane,
- NoLanes,
- SyncLane,
- OffscreenLane,
- DefaultHydrationLane,
- SomeRetryLane,
- NoTimestamp,
- includesSomeLane,
- laneToLanes,
- removeLanes,
- mergeLanes,
- getBumpedLaneForHydration,
- pickArbitraryLane,
-} from './ReactFiberLane.new';
-import {
- ConcurrentMode,
- NoMode,
- ProfileMode,
- StrictLegacyMode,
-} from './ReactTypeOfMode';
-import {
- shouldSetTextContent,
- isSuspenseInstancePending,
- isSuspenseInstanceFallback,
- getSuspenseInstanceFallbackErrorDetails,
- registerSuspenseInstanceRetry,
- supportsHydration,
- supportsResources,
- supportsSingletons,
- isPrimaryRenderer,
- getResource,
-} from './ReactFiberHostConfig';
-import type {SuspenseInstance} from './ReactFiberHostConfig';
-import {shouldError, shouldSuspend} from './ReactFiberReconciler';
-import {pushHostContext, pushHostContainer} from './ReactFiberHostContext.new';
-import {
- suspenseStackCursor,
- pushSuspenseListContext,
- ForceSuspenseFallback,
- hasSuspenseListContext,
- setDefaultShallowSuspenseListContext,
- setShallowSuspenseListContext,
- pushPrimaryTreeSuspenseHandler,
- pushFallbackTreeSuspenseHandler,
- pushOffscreenSuspenseHandler,
- reuseSuspenseHandlerOnStack,
- popSuspenseHandler,
-} from './ReactFiberSuspenseContext.new';
-import {
- pushHiddenContext,
- reuseHiddenContextOnStack,
-} from './ReactFiberHiddenContext.new';
-import {findFirstSuspended} from './ReactFiberSuspenseComponent.new';
-import {
- pushProvider,
- propagateContextChange,
- lazilyPropagateParentContextChanges,
- propagateParentContextChangesToDeferredTree,
- checkIfContextChanged,
- readContext,
- prepareToReadContext,
- scheduleContextWorkOnParentPath,
-} from './ReactFiberNewContext.new';
-import {
- renderWithHooks,
- checkDidRenderIdHook,
- bailoutHooks,
- replaySuspendedComponentWithHooks,
-} from './ReactFiberHooks.new';
-import {stopProfilerTimerIfRunning} from './ReactProfilerTimer.new';
-import {
- getMaskedContext,
- getUnmaskedContext,
- hasContextChanged as hasLegacyContextChanged,
- pushContextProvider as pushLegacyContextProvider,
- isContextProvider as isLegacyContextProvider,
- pushTopLevelContextObject,
- invalidateContextProvider,
-} from './ReactFiberContext.new';
-import {
- getIsHydrating,
- enterHydrationState,
- reenterHydrationStateFromDehydratedSuspenseInstance,
- resetHydrationState,
- claimHydratableSingleton,
- tryToClaimNextHydratableInstance,
- warnIfHydrating,
- queueHydrationError,
-} from './ReactFiberHydrationContext.new';
-import {
- adoptClassInstance,
- constructClassInstance,
- mountClassInstance,
- resumeMountClassInstance,
- updateClassInstance,
-} from './ReactFiberClassComponent.new';
-import {resolveDefaultProps} from './ReactFiberLazyComponent.new';
-import {
- resolveLazyComponentTag,
- createFiberFromTypeAndProps,
- createFiberFromFragment,
- createFiberFromOffscreen,
- createWorkInProgress,
- isSimpleFunctionComponent,
-} from './ReactFiber.new';
-import {
- retryDehydratedSuspenseBoundary,
- scheduleUpdateOnFiber,
- renderDidSuspendDelayIfPossible,
- markSkippedUpdateLanes,
- getWorkInProgressRoot,
-} from './ReactFiberWorkLoop.new';
-import {enqueueConcurrentRenderForLane} from './ReactFiberConcurrentUpdates.new';
-import {setWorkInProgressVersion} from './ReactMutableSource.new';
-import {pushCacheProvider, CacheContext} from './ReactFiberCacheComponent.new';
-import {
- createCapturedValue,
- createCapturedValueAtFiber,
- type CapturedValue,
-} from './ReactCapturedValue';
-import {createClassErrorUpdate} from './ReactFiberThrow.new';
-import is from 'shared/objectIs';
-import {
- getForksAtLevel,
- isForkedChild,
- pushTreeId,
- pushMaterializedTreeId,
-} from './ReactFiberTreeContext.new';
-import {
- requestCacheFromPool,
- pushRootTransition,
- getSuspendedCache,
- pushTransition,
- getOffscreenDeferredCache,
- getPendingTransitions,
-} from './ReactFiberTransition.new';
-import {
- getMarkerInstances,
- pushMarkerInstance,
- pushRootMarkerInstance,
- TransitionTracingMarker,
-} from './ReactFiberTracingMarkerComponent.new';
-
-const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
-
-// A special exception that's used to unwind the stack when an update flows
-// into a dehydrated boundary.
-export const SelectiveHydrationException: mixed = new Error(
- "This is not a real error. It's an implementation detail of React's " +
- "selective hydration feature. If this leaks into userspace, it's a bug in " +
- 'React. Please file an issue.',
-);
-
-let didReceiveUpdate: boolean = false;
-
-let didWarnAboutBadClass;
-let didWarnAboutModulePatternComponent;
-let didWarnAboutContextTypeOnFunctionComponent;
-let didWarnAboutGetDerivedStateOnFunctionComponent;
-let didWarnAboutFunctionRefs;
-export let didWarnAboutReassigningProps: boolean;
-let didWarnAboutRevealOrder;
-let didWarnAboutTailOptions;
-let didWarnAboutDefaultPropsOnFunctionComponent;
-
-if (__DEV__) {
- didWarnAboutBadClass = {};
- didWarnAboutModulePatternComponent = {};
- didWarnAboutContextTypeOnFunctionComponent = {};
- didWarnAboutGetDerivedStateOnFunctionComponent = {};
- didWarnAboutFunctionRefs = {};
- didWarnAboutReassigningProps = false;
- didWarnAboutRevealOrder = {};
- didWarnAboutTailOptions = {};
- didWarnAboutDefaultPropsOnFunctionComponent = {};
-}
-
-export function reconcileChildren(
- current: Fiber | null,
- workInProgress: Fiber,
- nextChildren: any,
- renderLanes: Lanes,
-) {
- if (current === null) {
- // If this is a fresh new component that hasn't been rendered yet, we
- // won't update its child set by applying minimal side-effects. Instead,
- // we will add them all to the child before it gets rendered. That means
- // we can optimize this reconciliation pass by not tracking side-effects.
- workInProgress.child = mountChildFibers(
- workInProgress,
- null,
- nextChildren,
- renderLanes,
- );
- } else {
- // If the current child is the same as the work in progress, it means that
- // we haven't yet started any work on these children. Therefore, we use
- // the clone algorithm to create a copy of all the current children.
-
- // If we had any progressed work already, that is invalid at this point so
- // let's throw it out.
- workInProgress.child = reconcileChildFibers(
- workInProgress,
- current.child,
- nextChildren,
- renderLanes,
- );
- }
-}
-
-function forceUnmountCurrentAndReconcile(
- current: Fiber,
- workInProgress: Fiber,
- nextChildren: any,
- renderLanes: Lanes,
-) {
- // This function is fork of reconcileChildren. It's used in cases where we
- // want to reconcile without matching against the existing set. This has the
- // effect of all current children being unmounted; even if the type and key
- // are the same, the old child is unmounted and a new child is created.
- //
- // To do this, we're going to go through the reconcile algorithm twice. In
- // the first pass, we schedule a deletion for all the current children by
- // passing null.
- workInProgress.child = reconcileChildFibers(
- workInProgress,
- current.child,
- null,
- renderLanes,
- );
- // In the second pass, we mount the new children. The trick here is that we
- // pass null in place of where we usually pass the current child set. This has
- // the effect of remounting all children regardless of whether their
- // identities match.
- workInProgress.child = reconcileChildFibers(
- workInProgress,
- null,
- nextChildren,
- renderLanes,
- );
-}
-
-function updateForwardRef(
- current: Fiber | null,
- workInProgress: Fiber,
- Component: any,
- nextProps: any,
- renderLanes: Lanes,
-) {
- // TODO: current can be non-null here even if the component
- // hasn't yet mounted. This happens after the first render suspends.
- // We'll need to figure out if this is fine or can cause issues.
-
- if (__DEV__) {
- if (workInProgress.type !== workInProgress.elementType) {
- // Lazy component props can't be validated in createElement
- // because they're only guaranteed to be resolved here.
- const innerPropTypes = Component.propTypes;
- if (innerPropTypes) {
- checkPropTypes(
- innerPropTypes,
- nextProps, // Resolved props
- 'prop',
- getComponentNameFromType(Component),
- );
- }
- }
- }
-
- const render = Component.render;
- const ref = workInProgress.ref;
-
- // The rest is a fork of updateFunctionComponent
- let nextChildren;
- let hasId;
- prepareToReadContext(workInProgress, renderLanes);
- if (enableSchedulingProfiler) {
- markComponentRenderStarted(workInProgress);
- }
- if (__DEV__) {
- ReactCurrentOwner.current = workInProgress;
- setIsRendering(true);
- nextChildren = renderWithHooks(
- current,
- workInProgress,
- render,
- nextProps,
- ref,
- renderLanes,
- );
- hasId = checkDidRenderIdHook();
- setIsRendering(false);
- } else {
- nextChildren = renderWithHooks(
- current,
- workInProgress,
- render,
- nextProps,
- ref,
- renderLanes,
- );
- hasId = checkDidRenderIdHook();
- }
- if (enableSchedulingProfiler) {
- markComponentRenderStopped();
- }
-
- if (current !== null && !didReceiveUpdate) {
- bailoutHooks(current, workInProgress, renderLanes);
- return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
- }
-
- if (getIsHydrating() && hasId) {
- pushMaterializedTreeId(workInProgress);
- }
-
- // React DevTools reads this flag.
- workInProgress.flags |= PerformedWork;
- reconcileChildren(current, workInProgress, nextChildren, renderLanes);
- return workInProgress.child;
-}
-
-function updateMemoComponent(
- current: Fiber | null,
- workInProgress: Fiber,
- Component: any,
- nextProps: any,
- renderLanes: Lanes,
-): null | Fiber {
- if (current === null) {
- const type = Component.type;
- if (
- isSimpleFunctionComponent(type) &&
- Component.compare === null &&
- // SimpleMemoComponent codepath doesn't resolve outer props either.
- Component.defaultProps === undefined
- ) {
- let resolvedType = type;
- if (__DEV__) {
- resolvedType = resolveFunctionForHotReloading(type);
- }
- // If this is a plain function component without default props,
- // and with only the default shallow comparison, we upgrade it
- // to a SimpleMemoComponent to allow fast path updates.
- workInProgress.tag = SimpleMemoComponent;
- workInProgress.type = resolvedType;
- if (__DEV__) {
- validateFunctionComponentInDev(workInProgress, type);
- }
- return updateSimpleMemoComponent(
- current,
- workInProgress,
- resolvedType,
- nextProps,
- renderLanes,
- );
- }
- if (__DEV__) {
- const innerPropTypes = type.propTypes;
- if (innerPropTypes) {
- // Inner memo component props aren't currently validated in createElement.
- // We could move it there, but we'd still need this for lazy code path.
- checkPropTypes(
- innerPropTypes,
- nextProps, // Resolved props
- 'prop',
- getComponentNameFromType(type),
- );
- }
- if (
- warnAboutDefaultPropsOnFunctionComponents &&
- Component.defaultProps !== undefined
- ) {
- const componentName = getComponentNameFromType(type) || 'Unknown';
- if (!didWarnAboutDefaultPropsOnFunctionComponent[componentName]) {
- console.error(
- '%s: Support for defaultProps will be removed from memo components ' +
- 'in a future major release. Use JavaScript default parameters instead.',
- componentName,
- );
- didWarnAboutDefaultPropsOnFunctionComponent[componentName] = true;
- }
- }
- }
- const child = createFiberFromTypeAndProps(
- Component.type,
- null,
- nextProps,
- workInProgress,
- workInProgress.mode,
- renderLanes,
- );
- child.ref = workInProgress.ref;
- child.return = workInProgress;
- workInProgress.child = child;
- return child;
- }
- if (__DEV__) {
- const type = Component.type;
- const innerPropTypes = type.propTypes;
- if (innerPropTypes) {
- // Inner memo component props aren't currently validated in createElement.
- // We could move it there, but we'd still need this for lazy code path.
- checkPropTypes(
- innerPropTypes,
- nextProps, // Resolved props
- 'prop',
- getComponentNameFromType(type),
- );
- }
- }
- const currentChild = ((current.child: any): Fiber); // This is always exactly one child
- const hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(
- current,
- renderLanes,
- );
- if (!hasScheduledUpdateOrContext) {
- // This will be the props with resolved defaultProps,
- // unlike current.memoizedProps which will be the unresolved ones.
- const prevProps = currentChild.memoizedProps;
- // Default to shallow comparison
- let compare = Component.compare;
- compare = compare !== null ? compare : shallowEqual;
- if (compare(prevProps, nextProps) && current.ref === workInProgress.ref) {
- return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
- }
- }
- // React DevTools reads this flag.
- workInProgress.flags |= PerformedWork;
- const newChild = createWorkInProgress(currentChild, nextProps);
- newChild.ref = workInProgress.ref;
- newChild.return = workInProgress;
- workInProgress.child = newChild;
- return newChild;
-}
-
-function updateSimpleMemoComponent(
- current: Fiber | null,
- workInProgress: Fiber,
- Component: any,
- nextProps: any,
- renderLanes: Lanes,
-): null | Fiber {
- // TODO: current can be non-null here even if the component
- // hasn't yet mounted. This happens when the inner render suspends.
- // We'll need to figure out if this is fine or can cause issues.
-
- if (__DEV__) {
- if (workInProgress.type !== workInProgress.elementType) {
- // Lazy component props can't be validated in createElement
- // because they're only guaranteed to be resolved here.
- let outerMemoType = workInProgress.elementType;
- if (outerMemoType.$$typeof === REACT_LAZY_TYPE) {
- // We warn when you define propTypes on lazy()
- // so let's just skip over it to find memo() outer wrapper.
- // Inner props for memo are validated later.
- const lazyComponent: LazyComponentType = outerMemoType;
- const payload = lazyComponent._payload;
- const init = lazyComponent._init;
- try {
- outerMemoType = init(payload);
- } catch (x) {
- // $FlowFixMe[incompatible-type] found when upgrading Flow
- outerMemoType = null;
- }
- // Inner propTypes will be validated in the function component path.
- const outerPropTypes = outerMemoType && (outerMemoType: any).propTypes;
- if (outerPropTypes) {
- checkPropTypes(
- outerPropTypes,
- nextProps, // Resolved (SimpleMemoComponent has no defaultProps)
- 'prop',
- getComponentNameFromType(outerMemoType),
- );
- }
- }
- }
- }
- if (current !== null) {
- const prevProps = current.memoizedProps;
- if (
- shallowEqual(prevProps, nextProps) &&
- current.ref === workInProgress.ref &&
- // Prevent bailout if the implementation changed due to hot reload.
- (__DEV__ ? workInProgress.type === current.type : true)
- ) {
- didReceiveUpdate = false;
-
- // The props are shallowly equal. Reuse the previous props object, like we
- // would during a normal fiber bailout.
- //
- // We don't have strong guarantees that the props object is referentially
- // equal during updates where we can't bail out anyway — like if the props
- // are shallowly equal, but there's a local state or context update in the
- // same batch.
- //
- // However, as a principle, we should aim to make the behavior consistent
- // across different ways of memoizing a component. For example, React.memo
- // has a different internal Fiber layout if you pass a normal function
- // component (SimpleMemoComponent) versus if you pass a different type
- // like forwardRef (MemoComponent). But this is an implementation detail.
- // Wrapping a component in forwardRef (or React.lazy, etc) shouldn't
- // affect whether the props object is reused during a bailout.
- workInProgress.pendingProps = nextProps = prevProps;
-
- if (!checkScheduledUpdateOrContext(current, renderLanes)) {
- // The pending lanes were cleared at the beginning of beginWork. We're
- // about to bail out, but there might be other lanes that weren't
- // included in the current render. Usually, the priority level of the
- // remaining updates is accumulated during the evaluation of the
- // component (i.e. when processing the update queue). But since since
- // we're bailing out early *without* evaluating the component, we need
- // to account for it here, too. Reset to the value of the current fiber.
- // NOTE: This only applies to SimpleMemoComponent, not MemoComponent,
- // because a MemoComponent fiber does not have hooks or an update queue;
- // rather, it wraps around an inner component, which may or may not
- // contains hooks.
- // TODO: Move the reset at in beginWork out of the common path so that
- // this is no longer necessary.
- workInProgress.lanes = current.lanes;
- return bailoutOnAlreadyFinishedWork(
- current,
- workInProgress,
- renderLanes,
- );
- } else if ((current.flags & ForceUpdateForLegacySuspense) !== NoFlags) {
- // This is a special case that only exists for legacy mode.
- // See https://github.com/facebook/react/pull/19216.
- didReceiveUpdate = true;
- }
- }
- }
- return updateFunctionComponent(
- current,
- workInProgress,
- Component,
- nextProps,
- renderLanes,
- );
-}
-
-function updateOffscreenComponent(
- current: Fiber | null,
- workInProgress: Fiber,
- renderLanes: Lanes,
-) {
- const nextProps: OffscreenProps = workInProgress.pendingProps;
- const nextChildren = nextProps.children;
-
- const prevState: OffscreenState | null =
- current !== null ? current.memoizedState : null;
-
- markRef(current, workInProgress);
-
- if (
- nextProps.mode === 'hidden' ||
- (enableLegacyHidden &&
- nextProps.mode === 'unstable-defer-without-hiding') ||
- // TODO: remove read from stateNode.
- workInProgress.stateNode._visibility & OffscreenDetached
- ) {
- // Rendering a hidden tree.
-
- const didSuspend = (workInProgress.flags & DidCapture) !== NoFlags;
- if (didSuspend) {
- // Something suspended inside a hidden tree
-
- // Include the base lanes from the last render
- const nextBaseLanes =
- prevState !== null
- ? mergeLanes(prevState.baseLanes, renderLanes)
- : renderLanes;
-
- if (current !== null) {
- // Reset to the current children
- let currentChild = (workInProgress.child = current.child);
-
- // The current render suspended, but there may be other lanes with
- // pending work. We can't read `childLanes` from the current Offscreen
- // fiber because we reset it when it was deferred; however, we can read
- // the pending lanes from the child fibers.
- let currentChildLanes = NoLanes;
- while (currentChild !== null) {
- currentChildLanes = mergeLanes(
- mergeLanes(currentChildLanes, currentChild.lanes),
- currentChild.childLanes,
- );
- currentChild = currentChild.sibling;
- }
- const lanesWeJustAttempted = nextBaseLanes;
- const remainingChildLanes = removeLanes(
- currentChildLanes,
- lanesWeJustAttempted,
- );
- workInProgress.childLanes = remainingChildLanes;
- } else {
- workInProgress.childLanes = NoLanes;
- workInProgress.child = null;
- }
-
- return deferHiddenOffscreenComponent(
- current,
- workInProgress,
- nextBaseLanes,
- renderLanes,
- );
- }
-
- if ((workInProgress.mode & ConcurrentMode) === NoMode) {
- // In legacy sync mode, don't defer the subtree. Render it now.
- // TODO: Consider how Offscreen should work with transitions in the future
- const nextState: OffscreenState = {
- baseLanes: NoLanes,
- cachePool: null,
- };
- workInProgress.memoizedState = nextState;
- if (enableCache) {
- // push the cache pool even though we're going to bail out
- // because otherwise there'd be a context mismatch
- if (current !== null) {
- pushTransition(workInProgress, null, null);
- }
- }
- reuseHiddenContextOnStack(workInProgress);
- pushOffscreenSuspenseHandler(workInProgress);
- } else if (!includesSomeLane(renderLanes, (OffscreenLane: Lane))) {
- // We're hidden, and we're not rendering at Offscreen. We will bail out
- // and resume this tree later.
-
- // Schedule this fiber to re-render at Offscreen priority
- workInProgress.lanes = workInProgress.childLanes = laneToLanes(
- OffscreenLane,
- );
-
- // Include the base lanes from the last render
- const nextBaseLanes =
- prevState !== null
- ? mergeLanes(prevState.baseLanes, renderLanes)
- : renderLanes;
-
- return deferHiddenOffscreenComponent(
- current,
- workInProgress,
- nextBaseLanes,
- renderLanes,
- );
- } else {
- // This is the second render. The surrounding visible content has already
- // committed. Now we resume rendering the hidden tree.
-
- // Rendering at offscreen, so we can clear the base lanes.
- const nextState: OffscreenState = {
- baseLanes: NoLanes,
- cachePool: null,
- };
- workInProgress.memoizedState = nextState;
- if (enableCache && current !== null) {
- // If the render that spawned this one accessed the cache pool, resume
- // using the same cache. Unless the parent changed, since that means
- // there was a refresh.
- const prevCachePool = prevState !== null ? prevState.cachePool : null;
- // TODO: Consider if and how Offscreen pre-rendering should
- // be attributed to the transition that spawned it
- pushTransition(workInProgress, prevCachePool, null);
- }
-
- // Push the lanes that were skipped when we bailed out.
- if (prevState !== null) {
- pushHiddenContext(workInProgress, prevState);
- } else {
- reuseHiddenContextOnStack(workInProgress);
- }
- pushOffscreenSuspenseHandler(workInProgress);
- }
- } else {
- // Rendering a visible tree.
- if (prevState !== null) {
- // We're going from hidden -> visible.
- let prevCachePool = null;
- if (enableCache) {
- // If the render that spawned this one accessed the cache pool, resume
- // using the same cache. Unless the parent changed, since that means
- // there was a refresh.
- prevCachePool = prevState.cachePool;
- }
-
- let transitions = null;
- if (enableTransitionTracing) {
- // We have now gone from hidden to visible, so any transitions should
- // be added to the stack to get added to any Offscreen/suspense children
- const instance: OffscreenInstance | null = workInProgress.stateNode;
- if (instance !== null && instance._transitions != null) {
- transitions = Array.from(instance._transitions);
- }
- }
-
- pushTransition(workInProgress, prevCachePool, transitions);
-
- // Push the lanes that were skipped when we bailed out.
- pushHiddenContext(workInProgress, prevState);
- reuseSuspenseHandlerOnStack(workInProgress);
-
- // Since we're not hidden anymore, reset the state
- workInProgress.memoizedState = null;
- } else {
- // We weren't previously hidden, and we still aren't, so there's nothing
- // special to do. Need to push to the stack regardless, though, to avoid
- // a push/pop misalignment.
-
- if (enableCache) {
- // If the render that spawned this one accessed the cache pool, resume
- // using the same cache. Unless the parent changed, since that means
- // there was a refresh.
- if (current !== null) {
- pushTransition(workInProgress, null, null);
- }
- }
-
- // We're about to bail out, but we need to push this to the stack anyway
- // to avoid a push/pop misalignment.
- reuseHiddenContextOnStack(workInProgress);
- reuseSuspenseHandlerOnStack(workInProgress);
- }
- }
-
- reconcileChildren(current, workInProgress, nextChildren, renderLanes);
- return workInProgress.child;
-}
-
-function deferHiddenOffscreenComponent(
- current: Fiber | null,
- workInProgress: Fiber,
- nextBaseLanes: Lanes,
- renderLanes: Lanes,
-) {
- const nextState: OffscreenState = {
- baseLanes: nextBaseLanes,
- // Save the cache pool so we can resume later.
- cachePool: enableCache ? getOffscreenDeferredCache() : null,
- };
- workInProgress.memoizedState = nextState;
- if (enableCache) {
- // push the cache pool even though we're going to bail out
- // because otherwise there'd be a context mismatch
- if (current !== null) {
- pushTransition(workInProgress, null, null);
- }
- }
-
- // We're about to bail out, but we need to push this to the stack anyway
- // to avoid a push/pop misalignment.
- reuseHiddenContextOnStack(workInProgress);
-
- pushOffscreenSuspenseHandler(workInProgress);
-
- if (enableLazyContextPropagation && current !== null) {
- // Since this tree will resume rendering in a separate render, we need
- // to propagate parent contexts now so we don't lose track of which
- // ones changed.
- propagateParentContextChangesToDeferredTree(
- current,
- workInProgress,
- renderLanes,
- );
- }
-
- return null;
-}
-
-// Note: These happen to have identical begin phases, for now. We shouldn't hold
-// ourselves to this constraint, though. If the behavior diverges, we should
-// fork the function.
-const updateLegacyHiddenComponent = updateOffscreenComponent;
-
-function updateCacheComponent(
- current: Fiber | null,
- workInProgress: Fiber,
- renderLanes: Lanes,
-) {
- if (!enableCache) {
- return null;
- }
-
- prepareToReadContext(workInProgress, renderLanes);
- const parentCache = readContext(CacheContext);
-
- if (current === null) {
- // Initial mount. Request a fresh cache from the pool.
- const freshCache = requestCacheFromPool(renderLanes);
- const initialState: CacheComponentState = {
- parent: parentCache,
- cache: freshCache,
- };
- workInProgress.memoizedState = initialState;
- initializeUpdateQueue(workInProgress);
- pushCacheProvider(workInProgress, freshCache);
- } else {
- // Check for updates
- if (includesSomeLane(current.lanes, renderLanes)) {
- cloneUpdateQueue(current, workInProgress);
- processUpdateQueue(workInProgress, null, null, renderLanes);
- }
- const prevState: CacheComponentState = current.memoizedState;
- const nextState: CacheComponentState = workInProgress.memoizedState;
-
- // Compare the new parent cache to the previous to see detect there was
- // a refresh.
- if (prevState.parent !== parentCache) {
- // Refresh in parent. Update the parent.
- const derivedState: CacheComponentState = {
- parent: parentCache,
- cache: parentCache,
- };
-
- // Copied from getDerivedStateFromProps implementation. Once the update
- // queue is empty, persist the derived state onto the base state.
- workInProgress.memoizedState = derivedState;
- if (workInProgress.lanes === NoLanes) {
- const updateQueue: UpdateQueue = (workInProgress.updateQueue: any);
- workInProgress.memoizedState = updateQueue.baseState = derivedState;
- }
-
- pushCacheProvider(workInProgress, parentCache);
- // No need to propagate a context change because the refreshed parent
- // already did.
- } else {
- // The parent didn't refresh. Now check if this cache did.
- const nextCache = nextState.cache;
- pushCacheProvider(workInProgress, nextCache);
- if (nextCache !== prevState.cache) {
- // This cache refreshed. Propagate a context change.
- propagateContextChange(workInProgress, CacheContext, renderLanes);
- }
- }
- }
-
- const nextChildren = workInProgress.pendingProps.children;
- reconcileChildren(current, workInProgress, nextChildren, renderLanes);
- return workInProgress.child;
-}
-
-// This should only be called if the name changes
-function updateTracingMarkerComponent(
- current: Fiber | null,
- workInProgress: Fiber,
- renderLanes: Lanes,
-) {
- if (!enableTransitionTracing) {
- return null;
- }
-
- // TODO: (luna) Only update the tracing marker if it's newly rendered or it's name changed.
- // A tracing marker is only associated with the transitions that rendered
- // or updated it, so we can create a new set of transitions each time
- if (current === null) {
- const currentTransitions = getPendingTransitions();
- if (currentTransitions !== null) {
- const markerInstance: TracingMarkerInstance = {
- tag: TransitionTracingMarker,
- transitions: new Set(currentTransitions),
- pendingBoundaries: null,
- name: workInProgress.pendingProps.name,
- aborts: null,
- };
- workInProgress.stateNode = markerInstance;
-
- // We call the marker complete callback when all child suspense boundaries resolve.
- // We do this in the commit phase on Offscreen. If the marker has no child suspense
- // boundaries, we need to schedule a passive effect to make sure we call the marker
- // complete callback.
- workInProgress.flags |= Passive;
- }
- } else {
- if (__DEV__) {
- if (current.memoizedProps.name !== workInProgress.pendingProps.name) {
- console.error(
- 'Changing the name of a tracing marker after mount is not supported. ' +
- 'To remount the tracing marker, pass it a new key.',
- );
- }
- }
- }
-
- const instance: TracingMarkerInstance | null = workInProgress.stateNode;
- if (instance !== null) {
- pushMarkerInstance(workInProgress, instance);
- }
- const nextChildren = workInProgress.pendingProps.children;
- reconcileChildren(current, workInProgress, nextChildren, renderLanes);
- return workInProgress.child;
-}
-
-function updateFragment(
- current: Fiber | null,
- workInProgress: Fiber,
- renderLanes: Lanes,
-) {
- const nextChildren = workInProgress.pendingProps;
- reconcileChildren(current, workInProgress, nextChildren, renderLanes);
- return workInProgress.child;
-}
-
-function updateMode(
- current: Fiber | null,
- workInProgress: Fiber,
- renderLanes: Lanes,
-) {
- const nextChildren = workInProgress.pendingProps.children;
- reconcileChildren(current, workInProgress, nextChildren, renderLanes);
- return workInProgress.child;
-}
-
-function updateProfiler(
- current: Fiber | null,
- workInProgress: Fiber,
- renderLanes: Lanes,
-) {
- if (enableProfilerTimer) {
- workInProgress.flags |= Update;
-
- if (enableProfilerCommitHooks) {
- // Reset effect durations for the next eventual effect phase.
- // These are reset during render to allow the DevTools commit hook a chance to read them,
- const stateNode = workInProgress.stateNode;
- stateNode.effectDuration = 0;
- stateNode.passiveEffectDuration = 0;
- }
- }
- const nextProps = workInProgress.pendingProps;
- const nextChildren = nextProps.children;
- reconcileChildren(current, workInProgress, nextChildren, renderLanes);
- return workInProgress.child;
-}
-
-function markRef(current: Fiber | null, workInProgress: Fiber) {
- const ref = workInProgress.ref;
- if (
- (current === null && ref !== null) ||
- (current !== null && current.ref !== ref)
- ) {
- // Schedule a Ref effect
- workInProgress.flags |= Ref;
- workInProgress.flags |= RefStatic;
- }
-}
-
-function updateFunctionComponent(
- current,
- workInProgress,
- Component,
- nextProps: any,
- renderLanes,
-) {
- if (__DEV__) {
- if (workInProgress.type !== workInProgress.elementType) {
- // Lazy component props can't be validated in createElement
- // because they're only guaranteed to be resolved here.
- const innerPropTypes = Component.propTypes;
- if (innerPropTypes) {
- checkPropTypes(
- innerPropTypes,
- nextProps, // Resolved props
- 'prop',
- getComponentNameFromType(Component),
- );
- }
- }
- }
-
- let context;
- if (!disableLegacyContext) {
- const unmaskedContext = getUnmaskedContext(workInProgress, Component, true);
- context = getMaskedContext(workInProgress, unmaskedContext);
- }
-
- let nextChildren;
- let hasId;
- prepareToReadContext(workInProgress, renderLanes);
- if (enableSchedulingProfiler) {
- markComponentRenderStarted(workInProgress);
- }
- if (__DEV__) {
- ReactCurrentOwner.current = workInProgress;
- setIsRendering(true);
- nextChildren = renderWithHooks(
- current,
- workInProgress,
- Component,
- nextProps,
- context,
- renderLanes,
- );
- hasId = checkDidRenderIdHook();
- setIsRendering(false);
- } else {
- nextChildren = renderWithHooks(
- current,
- workInProgress,
- Component,
- nextProps,
- context,
- renderLanes,
- );
- hasId = checkDidRenderIdHook();
- }
- if (enableSchedulingProfiler) {
- markComponentRenderStopped();
- }
-
- if (current !== null && !didReceiveUpdate) {
- bailoutHooks(current, workInProgress, renderLanes);
- return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
- }
-
- if (getIsHydrating() && hasId) {
- pushMaterializedTreeId(workInProgress);
- }
-
- // React DevTools reads this flag.
- workInProgress.flags |= PerformedWork;
- reconcileChildren(current, workInProgress, nextChildren, renderLanes);
- return workInProgress.child;
-}
-
-export function replayFunctionComponent(
- current: Fiber | null,
- workInProgress: Fiber,
- nextProps: any,
- Component: any,
- renderLanes: Lanes,
-): Fiber | null {
- // This function is used to replay a component that previously suspended,
- // after its data resolves. It's a simplified version of
- // updateFunctionComponent that reuses the hooks from the previous attempt.
-
- let context;
- if (!disableLegacyContext) {
- const unmaskedContext = getUnmaskedContext(workInProgress, Component, true);
- context = getMaskedContext(workInProgress, unmaskedContext);
- }
-
- prepareToReadContext(workInProgress, renderLanes);
- if (enableSchedulingProfiler) {
- markComponentRenderStarted(workInProgress);
- }
- const nextChildren = replaySuspendedComponentWithHooks(
- current,
- workInProgress,
- Component,
- nextProps,
- context,
- );
- const hasId = checkDidRenderIdHook();
- if (enableSchedulingProfiler) {
- markComponentRenderStopped();
- }
-
- if (current !== null && !didReceiveUpdate) {
- bailoutHooks(current, workInProgress, renderLanes);
- return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
- }
-
- if (getIsHydrating() && hasId) {
- pushMaterializedTreeId(workInProgress);
- }
-
- // React DevTools reads this flag.
- workInProgress.flags |= PerformedWork;
- reconcileChildren(current, workInProgress, nextChildren, renderLanes);
- return workInProgress.child;
-}
-
-function updateClassComponent(
- current: Fiber | null,
- workInProgress: Fiber,
- Component: any,
- nextProps: any,
- renderLanes: Lanes,
-) {
- if (__DEV__) {
- // This is used by DevTools to force a boundary to error.
- switch (shouldError(workInProgress)) {
- case false: {
- const instance = workInProgress.stateNode;
- const ctor = workInProgress.type;
- // TODO This way of resetting the error boundary state is a hack.
- // Is there a better way to do this?
- const tempInstance = new ctor(
- workInProgress.memoizedProps,
- instance.context,
- );
- const state = tempInstance.state;
- instance.updater.enqueueSetState(instance, state, null);
- break;
- }
- case true: {
- workInProgress.flags |= DidCapture;
- workInProgress.flags |= ShouldCapture;
- // eslint-disable-next-line react-internal/prod-error-codes
- const error = new Error('Simulated error coming from DevTools');
- const lane = pickArbitraryLane(renderLanes);
- workInProgress.lanes = mergeLanes(workInProgress.lanes, lane);
- // Schedule the error boundary to re-render using updated state
- const update = createClassErrorUpdate(
- workInProgress,
- createCapturedValueAtFiber(error, workInProgress),
- lane,
- );
- enqueueCapturedUpdate(workInProgress, update);
- break;
- }
- }
-
- if (workInProgress.type !== workInProgress.elementType) {
- // Lazy component props can't be validated in createElement
- // because they're only guaranteed to be resolved here.
- const innerPropTypes = Component.propTypes;
- if (innerPropTypes) {
- checkPropTypes(
- innerPropTypes,
- nextProps, // Resolved props
- 'prop',
- getComponentNameFromType(Component),
- );
- }
- }
- }
-
- // Push context providers early to prevent context stack mismatches.
- // During mounting we don't know the child context yet as the instance doesn't exist.
- // We will invalidate the child context in finishClassComponent() right after rendering.
- let hasContext;
- if (isLegacyContextProvider(Component)) {
- hasContext = true;
- pushLegacyContextProvider(workInProgress);
- } else {
- hasContext = false;
- }
- prepareToReadContext(workInProgress, renderLanes);
-
- const instance = workInProgress.stateNode;
- let shouldUpdate;
- if (instance === null) {
- resetSuspendedCurrentOnMountInLegacyMode(current, workInProgress);
-
- // In the initial pass we might need to construct the instance.
- constructClassInstance(workInProgress, Component, nextProps);
- mountClassInstance(workInProgress, Component, nextProps, renderLanes);
- shouldUpdate = true;
- } else if (current === null) {
- // In a resume, we'll already have an instance we can reuse.
- shouldUpdate = resumeMountClassInstance(
- workInProgress,
- Component,
- nextProps,
- renderLanes,
- );
- } else {
- shouldUpdate = updateClassInstance(
- current,
- workInProgress,
- Component,
- nextProps,
- renderLanes,
- );
- }
- const nextUnitOfWork = finishClassComponent(
- current,
- workInProgress,
- Component,
- shouldUpdate,
- hasContext,
- renderLanes,
- );
- if (__DEV__) {
- const inst = workInProgress.stateNode;
- if (shouldUpdate && inst.props !== nextProps) {
- if (!didWarnAboutReassigningProps) {
- console.error(
- 'It looks like %s is reassigning its own `this.props` while rendering. ' +
- 'This is not supported and can lead to confusing bugs.',
- getComponentNameFromFiber(workInProgress) || 'a component',
- );
- }
- didWarnAboutReassigningProps = true;
- }
- }
- return nextUnitOfWork;
-}
-
-function finishClassComponent(
- current: Fiber | null,
- workInProgress: Fiber,
- Component: any,
- shouldUpdate: boolean,
- hasContext: boolean,
- renderLanes: Lanes,
-) {
- // Refs should update even if shouldComponentUpdate returns false
- markRef(current, workInProgress);
-
- const didCaptureError = (workInProgress.flags & DidCapture) !== NoFlags;
-
- if (!shouldUpdate && !didCaptureError) {
- // Context providers should defer to sCU for rendering
- if (hasContext) {
- invalidateContextProvider(workInProgress, Component, false);
- }
-
- return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
- }
-
- const instance = workInProgress.stateNode;
-
- // Rerender
- ReactCurrentOwner.current = workInProgress;
- let nextChildren;
- if (
- didCaptureError &&
- typeof Component.getDerivedStateFromError !== 'function'
- ) {
- // If we captured an error, but getDerivedStateFromError is not defined,
- // unmount all the children. componentDidCatch will schedule an update to
- // re-render a fallback. This is temporary until we migrate everyone to
- // the new API.
- // TODO: Warn in a future release.
- nextChildren = null;
-
- if (enableProfilerTimer) {
- stopProfilerTimerIfRunning(workInProgress);
- }
- } else {
- if (enableSchedulingProfiler) {
- markComponentRenderStarted(workInProgress);
- }
- if (__DEV__) {
- setIsRendering(true);
- nextChildren = instance.render();
- if (
- debugRenderPhaseSideEffectsForStrictMode &&
- workInProgress.mode & StrictLegacyMode
- ) {
- setIsStrictModeForDevtools(true);
- try {
- instance.render();
- } finally {
- setIsStrictModeForDevtools(false);
- }
- }
- setIsRendering(false);
- } else {
- nextChildren = instance.render();
- }
- if (enableSchedulingProfiler) {
- markComponentRenderStopped();
- }
- }
-
- // React DevTools reads this flag.
- workInProgress.flags |= PerformedWork;
- if (current !== null && didCaptureError) {
- // If we're recovering from an error, reconcile without reusing any of
- // the existing children. Conceptually, the normal children and the children
- // that are shown on error are two different sets, so we shouldn't reuse
- // normal children even if their identities match.
- forceUnmountCurrentAndReconcile(
- current,
- workInProgress,
- nextChildren,
- renderLanes,
- );
- } else {
- reconcileChildren(current, workInProgress, nextChildren, renderLanes);
- }
-
- // Memoize state using the values we just used to render.
- // TODO: Restructure so we never read values from the instance.
- workInProgress.memoizedState = instance.state;
-
- // The context might have changed so we need to recalculate it.
- if (hasContext) {
- invalidateContextProvider(workInProgress, Component, true);
- }
-
- return workInProgress.child;
-}
-
-function pushHostRootContext(workInProgress) {
- const root = (workInProgress.stateNode: FiberRoot);
- if (root.pendingContext) {
- pushTopLevelContextObject(
- workInProgress,
- root.pendingContext,
- root.pendingContext !== root.context,
- );
- } else if (root.context) {
- // Should always be set
- pushTopLevelContextObject(workInProgress, root.context, false);
- }
- pushHostContainer(workInProgress, root.containerInfo);
-}
-
-function updateHostRoot(current, workInProgress, renderLanes) {
- pushHostRootContext(workInProgress);
-
- if (current === null) {
- throw new Error('Should have a current fiber. This is a bug in React.');
- }
-
- const nextProps = workInProgress.pendingProps;
- const prevState = workInProgress.memoizedState;
- const prevChildren = prevState.element;
- cloneUpdateQueue(current, workInProgress);
- processUpdateQueue(workInProgress, nextProps, null, renderLanes);
-
- const nextState: RootState = workInProgress.memoizedState;
- const root: FiberRoot = workInProgress.stateNode;
- pushRootTransition(workInProgress, root, renderLanes);
-
- if (enableTransitionTracing) {
- pushRootMarkerInstance(workInProgress);
- }
-
- if (enableCache) {
- const nextCache: Cache = nextState.cache;
- pushCacheProvider(workInProgress, nextCache);
- if (nextCache !== prevState.cache) {
- // The root cache refreshed.
- propagateContextChange(workInProgress, CacheContext, renderLanes);
- }
- }
-
- // Caution: React DevTools currently depends on this property
- // being called "element".
- const nextChildren = nextState.element;
- if (supportsHydration && prevState.isDehydrated) {
- // This is a hydration root whose shell has not yet hydrated. We should
- // attempt to hydrate.
-
- // Flip isDehydrated to false to indicate that when this render
- // finishes, the root will no longer be dehydrated.
- const overrideState: RootState = {
- element: nextChildren,
- isDehydrated: false,
- cache: nextState.cache,
- };
- const updateQueue: UpdateQueue = (workInProgress.updateQueue: any);
- // `baseState` can always be the last state because the root doesn't
- // have reducer functions so it doesn't need rebasing.
- updateQueue.baseState = overrideState;
- workInProgress.memoizedState = overrideState;
-
- if (workInProgress.flags & ForceClientRender) {
- // Something errored during a previous attempt to hydrate the shell, so we
- // forced a client render.
- const recoverableError = createCapturedValueAtFiber(
- new Error(
- 'There was an error while hydrating. Because the error happened outside ' +
- 'of a Suspense boundary, the entire root will switch to ' +
- 'client rendering.',
- ),
- workInProgress,
- );
- return mountHostRootWithoutHydrating(
- current,
- workInProgress,
- nextChildren,
- renderLanes,
- recoverableError,
- );
- } else if (nextChildren !== prevChildren) {
- const recoverableError = createCapturedValueAtFiber(
- new Error(
- 'This root received an early update, before anything was able ' +
- 'hydrate. Switched the entire root to client rendering.',
- ),
- workInProgress,
- );
- return mountHostRootWithoutHydrating(
- current,
- workInProgress,
- nextChildren,
- renderLanes,
- recoverableError,
- );
- } else {
- // The outermost shell has not hydrated yet. Start hydrating.
- enterHydrationState(workInProgress);
- if (enableUseMutableSource) {
- const mutableSourceEagerHydrationData =
- root.mutableSourceEagerHydrationData;
- if (mutableSourceEagerHydrationData != null) {
- for (let i = 0; i < mutableSourceEagerHydrationData.length; i += 2) {
- const mutableSource = ((mutableSourceEagerHydrationData[
- i
- ]: any): MutableSource);
- const version = mutableSourceEagerHydrationData[i + 1];
- setWorkInProgressVersion(mutableSource, version);
- }
- }
- }
-
- const child = mountChildFibers(
- workInProgress,
- null,
- nextChildren,
- renderLanes,
- );
- workInProgress.child = child;
-
- let node = child;
- while (node) {
- // Mark each child as hydrating. This is a fast path to know whether this
- // tree is part of a hydrating tree. This is used to determine if a child
- // node has fully mounted yet, and for scheduling event replaying.
- // Conceptually this is similar to Placement in that a new subtree is
- // inserted into the React tree here. It just happens to not need DOM
- // mutations because it already exists.
- node.flags = (node.flags & ~Placement) | Hydrating;
- node = node.sibling;
- }
- }
- } else {
- // Root is not dehydrated. Either this is a client-only root, or it
- // already hydrated.
- resetHydrationState();
- if (nextChildren === prevChildren) {
- return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
- }
- reconcileChildren(current, workInProgress, nextChildren, renderLanes);
- }
- return workInProgress.child;
-}
-
-function mountHostRootWithoutHydrating(
- current: Fiber,
- workInProgress: Fiber,
- nextChildren: ReactNodeList,
- renderLanes: Lanes,
- recoverableError: CapturedValue,
-) {
- // Revert to client rendering.
- resetHydrationState();
-
- queueHydrationError(recoverableError);
-
- workInProgress.flags |= ForceClientRender;
-
- reconcileChildren(current, workInProgress, nextChildren, renderLanes);
- return workInProgress.child;
-}
-
-function updateHostComponent(
- current: Fiber | null,
- workInProgress: Fiber,
- renderLanes: Lanes,
-) {
- pushHostContext(workInProgress);
-
- if (current === null) {
- tryToClaimNextHydratableInstance(workInProgress);
- }
-
- const type = workInProgress.type;
- const nextProps = workInProgress.pendingProps;
- const prevProps = current !== null ? current.memoizedProps : null;
-
- let nextChildren = nextProps.children;
- const isDirectTextChild = shouldSetTextContent(type, nextProps);
-
- if (isDirectTextChild) {
- // We special case a direct text child of a host node. This is a common
- // case. We won't handle it as a reified child. We will instead handle
- // this in the host environment that also has access to this prop. That
- // avoids allocating another HostText fiber and traversing it.
- nextChildren = null;
- } else if (prevProps !== null && shouldSetTextContent(type, prevProps)) {
- // If we're switching from a direct text child to a normal child, or to
- // empty, we need to schedule the text content to be reset.
- workInProgress.flags |= ContentReset;
- }
-
- markRef(current, workInProgress);
- reconcileChildren(current, workInProgress, nextChildren, renderLanes);
- return workInProgress.child;
-}
-
-function updateHostResource(current, workInProgress, renderLanes) {
- pushHostContext(workInProgress);
- markRef(current, workInProgress);
- const currentProps = current === null ? null : current.memoizedProps;
- workInProgress.memoizedState = getResource(
- workInProgress.type,
- workInProgress.pendingProps,
- currentProps,
- );
- // Resources never have reconciler managed children. It is possible for
- // the host implementation of getResource to consider children in the
- // resource construction but they will otherwise be discarded. In practice
- // this precludes all but the simplest children and Host specific warnings
- // should be implemented to warn when children are passsed when otherwise not
- // expected
- return null;
-}
-
-function updateHostSingleton(
- current: Fiber | null,
- workInProgress: Fiber,
- renderLanes: Lanes,
-) {
- pushHostContext(workInProgress);
-
- if (current === null) {
- claimHydratableSingleton(workInProgress);
- }
-
- const nextChildren = workInProgress.pendingProps.children;
-
- if (current === null && !getIsHydrating()) {
- // Similar to Portals we append Singleton children in the commit phase. So we
- // Track insertions even on mount.
- // TODO: Consider unifying this with how the root works.
- workInProgress.child = reconcileChildFibers(
- workInProgress,
- null,
- nextChildren,
- renderLanes,
- );
- } else {
- reconcileChildren(current, workInProgress, nextChildren, renderLanes);
- }
- markRef(current, workInProgress);
- return workInProgress.child;
-}
-
-function updateHostText(current, workInProgress) {
- if (current === null) {
- tryToClaimNextHydratableInstance(workInProgress);
- }
- // Nothing to do here. This is terminal. We'll do the completion step
- // immediately after.
- return null;
-}
-
-function mountLazyComponent(
- _current,
- workInProgress,
- elementType,
- renderLanes,
-) {
- resetSuspendedCurrentOnMountInLegacyMode(_current, workInProgress);
-
- const props = workInProgress.pendingProps;
- const lazyComponent: LazyComponentType = elementType;
- const payload = lazyComponent._payload;
- const init = lazyComponent._init;
- let Component = init(payload);
- // Store the unwrapped component in the type.
- workInProgress.type = Component;
- const resolvedTag = (workInProgress.tag = resolveLazyComponentTag(Component));
- const resolvedProps = resolveDefaultProps(Component, props);
- let child;
- switch (resolvedTag) {
- case FunctionComponent: {
- if (__DEV__) {
- validateFunctionComponentInDev(workInProgress, Component);
- workInProgress.type = Component = resolveFunctionForHotReloading(
- Component,
- );
- }
- child = updateFunctionComponent(
- null,
- workInProgress,
- Component,
- resolvedProps,
- renderLanes,
- );
- return child;
- }
- case ClassComponent: {
- if (__DEV__) {
- workInProgress.type = Component = resolveClassForHotReloading(
- Component,
- );
- }
- child = updateClassComponent(
- null,
- workInProgress,
- Component,
- resolvedProps,
- renderLanes,
- );
- return child;
- }
- case ForwardRef: {
- if (__DEV__) {
- workInProgress.type = Component = resolveForwardRefForHotReloading(
- Component,
- );
- }
- child = updateForwardRef(
- null,
- workInProgress,
- Component,
- resolvedProps,
- renderLanes,
- );
- return child;
- }
- case MemoComponent: {
- if (__DEV__) {
- if (workInProgress.type !== workInProgress.elementType) {
- const outerPropTypes = Component.propTypes;
- if (outerPropTypes) {
- checkPropTypes(
- outerPropTypes,
- resolvedProps, // Resolved for outer only
- 'prop',
- getComponentNameFromType(Component),
- );
- }
- }
- }
- child = updateMemoComponent(
- null,
- workInProgress,
- Component,
- resolveDefaultProps(Component.type, resolvedProps), // The inner type can have defaults too
- renderLanes,
- );
- return child;
- }
- }
- let hint = '';
- if (__DEV__) {
- if (
- Component !== null &&
- typeof Component === 'object' &&
- Component.$$typeof === REACT_LAZY_TYPE
- ) {
- hint = ' Did you wrap a component in React.lazy() more than once?';
- }
- }
-
- // This message intentionally doesn't mention ForwardRef or MemoComponent
- // because the fact that it's a separate type of work is an
- // implementation detail.
- throw new Error(
- `Element type is invalid. Received a promise that resolves to: ${Component}. ` +
- `Lazy element type must resolve to a class or function.${hint}`,
- );
-}
-
-function mountIncompleteClassComponent(
- _current,
- workInProgress,
- Component,
- nextProps,
- renderLanes,
-) {
- resetSuspendedCurrentOnMountInLegacyMode(_current, workInProgress);
-
- // Promote the fiber to a class and try rendering again.
- workInProgress.tag = ClassComponent;
-
- // The rest of this function is a fork of `updateClassComponent`
-
- // Push context providers early to prevent context stack mismatches.
- // During mounting we don't know the child context yet as the instance doesn't exist.
- // We will invalidate the child context in finishClassComponent() right after rendering.
- let hasContext;
- if (isLegacyContextProvider(Component)) {
- hasContext = true;
- pushLegacyContextProvider(workInProgress);
- } else {
- hasContext = false;
- }
- prepareToReadContext(workInProgress, renderLanes);
-
- constructClassInstance(workInProgress, Component, nextProps);
- mountClassInstance(workInProgress, Component, nextProps, renderLanes);
-
- return finishClassComponent(
- null,
- workInProgress,
- Component,
- true,
- hasContext,
- renderLanes,
- );
-}
-
-function mountIndeterminateComponent(
- _current,
- workInProgress,
- Component,
- renderLanes,
-) {
- resetSuspendedCurrentOnMountInLegacyMode(_current, workInProgress);
-
- const props = workInProgress.pendingProps;
- let context;
- if (!disableLegacyContext) {
- const unmaskedContext = getUnmaskedContext(
- workInProgress,
- Component,
- false,
- );
- context = getMaskedContext(workInProgress, unmaskedContext);
- }
-
- prepareToReadContext(workInProgress, renderLanes);
- let value;
- let hasId;
-
- if (enableSchedulingProfiler) {
- markComponentRenderStarted(workInProgress);
- }
- if (__DEV__) {
- if (
- Component.prototype &&
- typeof Component.prototype.render === 'function'
- ) {
- const componentName = getComponentNameFromType(Component) || 'Unknown';
-
- if (!didWarnAboutBadClass[componentName]) {
- console.error(
- "The <%s /> component appears to have a render method, but doesn't extend React.Component. " +
- 'This is likely to cause errors. Change %s to extend React.Component instead.',
- componentName,
- componentName,
- );
- didWarnAboutBadClass[componentName] = true;
- }
- }
-
- if (workInProgress.mode & StrictLegacyMode) {
- ReactStrictModeWarnings.recordLegacyContextWarning(workInProgress, null);
- }
-
- setIsRendering(true);
- ReactCurrentOwner.current = workInProgress;
- value = renderWithHooks(
- null,
- workInProgress,
- Component,
- props,
- context,
- renderLanes,
- );
- hasId = checkDidRenderIdHook();
- setIsRendering(false);
- } else {
- value = renderWithHooks(
- null,
- workInProgress,
- Component,
- props,
- context,
- renderLanes,
- );
- hasId = checkDidRenderIdHook();
- }
- if (enableSchedulingProfiler) {
- markComponentRenderStopped();
- }
-
- // React DevTools reads this flag.
- workInProgress.flags |= PerformedWork;
-
- if (__DEV__) {
- // Support for module components is deprecated and is removed behind a flag.
- // Whether or not it would crash later, we want to show a good message in DEV first.
- if (
- typeof value === 'object' &&
- value !== null &&
- typeof value.render === 'function' &&
- value.$$typeof === undefined
- ) {
- const componentName = getComponentNameFromType(Component) || 'Unknown';
- if (!didWarnAboutModulePatternComponent[componentName]) {
- console.error(
- 'The <%s /> component appears to be a function component that returns a class instance. ' +
- 'Change %s to a class that extends React.Component instead. ' +
- "If you can't use a class try assigning the prototype on the function as a workaround. " +
- "`%s.prototype = React.Component.prototype`. Don't use an arrow function since it " +
- 'cannot be called with `new` by React.',
- componentName,
- componentName,
- componentName,
- );
- didWarnAboutModulePatternComponent[componentName] = true;
- }
- }
- }
-
- if (
- // Run these checks in production only if the flag is off.
- // Eventually we'll delete this branch altogether.
- !disableModulePatternComponents &&
- typeof value === 'object' &&
- value !== null &&
- typeof value.render === 'function' &&
- value.$$typeof === undefined
- ) {
- if (__DEV__) {
- const componentName = getComponentNameFromType(Component) || 'Unknown';
- if (!didWarnAboutModulePatternComponent[componentName]) {
- console.error(
- 'The <%s /> component appears to be a function component that returns a class instance. ' +
- 'Change %s to a class that extends React.Component instead. ' +
- "If you can't use a class try assigning the prototype on the function as a workaround. " +
- "`%s.prototype = React.Component.prototype`. Don't use an arrow function since it " +
- 'cannot be called with `new` by React.',
- componentName,
- componentName,
- componentName,
- );
- didWarnAboutModulePatternComponent[componentName] = true;
- }
- }
-
- // Proceed under the assumption that this is a class instance
- workInProgress.tag = ClassComponent;
-
- // Throw out any hooks that were used.
- workInProgress.memoizedState = null;
- workInProgress.updateQueue = null;
-
- // Push context providers early to prevent context stack mismatches.
- // During mounting we don't know the child context yet as the instance doesn't exist.
- // We will invalidate the child context in finishClassComponent() right after rendering.
- let hasContext = false;
- if (isLegacyContextProvider(Component)) {
- hasContext = true;
- pushLegacyContextProvider(workInProgress);
- } else {
- hasContext = false;
- }
-
- workInProgress.memoizedState =
- value.state !== null && value.state !== undefined ? value.state : null;
-
- initializeUpdateQueue(workInProgress);
-
- adoptClassInstance(workInProgress, value);
- mountClassInstance(workInProgress, Component, props, renderLanes);
- return finishClassComponent(
- null,
- workInProgress,
- Component,
- true,
- hasContext,
- renderLanes,
- );
- } else {
- // Proceed under the assumption that this is a function component
- workInProgress.tag = FunctionComponent;
- if (__DEV__) {
- if (disableLegacyContext && Component.contextTypes) {
- console.error(
- '%s uses the legacy contextTypes API which is no longer supported. ' +
- 'Use React.createContext() with React.useContext() instead.',
- getComponentNameFromType(Component) || 'Unknown',
- );
- }
- }
-
- if (getIsHydrating() && hasId) {
- pushMaterializedTreeId(workInProgress);
- }
-
- reconcileChildren(null, workInProgress, value, renderLanes);
- if (__DEV__) {
- validateFunctionComponentInDev(workInProgress, Component);
- }
- return workInProgress.child;
- }
-}
-
-function validateFunctionComponentInDev(workInProgress: Fiber, Component: any) {
- if (__DEV__) {
- if (Component) {
- if (Component.childContextTypes) {
- console.error(
- '%s(...): childContextTypes cannot be defined on a function component.',
- Component.displayName || Component.name || 'Component',
- );
- }
- }
- if (workInProgress.ref !== null) {
- let info = '';
- const ownerName = getCurrentFiberOwnerNameInDevOrNull();
- if (ownerName) {
- info += '\n\nCheck the render method of `' + ownerName + '`.';
- }
-
- let warningKey = ownerName || '';
- const debugSource = workInProgress._debugSource;
- if (debugSource) {
- warningKey = debugSource.fileName + ':' + debugSource.lineNumber;
- }
- if (!didWarnAboutFunctionRefs[warningKey]) {
- didWarnAboutFunctionRefs[warningKey] = true;
- console.error(
- 'Function components cannot be given refs. ' +
- 'Attempts to access this ref will fail. ' +
- 'Did you mean to use React.forwardRef()?%s',
- info,
- );
- }
- }
-
- if (
- warnAboutDefaultPropsOnFunctionComponents &&
- Component.defaultProps !== undefined
- ) {
- const componentName = getComponentNameFromType(Component) || 'Unknown';
-
- if (!didWarnAboutDefaultPropsOnFunctionComponent[componentName]) {
- console.error(
- '%s: Support for defaultProps will be removed from function components ' +
- 'in a future major release. Use JavaScript default parameters instead.',
- componentName,
- );
- didWarnAboutDefaultPropsOnFunctionComponent[componentName] = true;
- }
- }
-
- if (typeof Component.getDerivedStateFromProps === 'function') {
- const componentName = getComponentNameFromType(Component) || 'Unknown';
-
- if (!didWarnAboutGetDerivedStateOnFunctionComponent[componentName]) {
- console.error(
- '%s: Function components do not support getDerivedStateFromProps.',
- componentName,
- );
- didWarnAboutGetDerivedStateOnFunctionComponent[componentName] = true;
- }
- }
-
- if (
- typeof Component.contextType === 'object' &&
- Component.contextType !== null
- ) {
- const componentName = getComponentNameFromType(Component) || 'Unknown';
-
- if (!didWarnAboutContextTypeOnFunctionComponent[componentName]) {
- console.error(
- '%s: Function components do not support contextType.',
- componentName,
- );
- didWarnAboutContextTypeOnFunctionComponent[componentName] = true;
- }
- }
- }
-}
-
-const SUSPENDED_MARKER: SuspenseState = {
- dehydrated: null,
- treeContext: null,
- retryLane: NoLane,
-};
-
-function mountSuspenseOffscreenState(renderLanes: Lanes): OffscreenState {
- return {
- baseLanes: renderLanes,
- cachePool: getSuspendedCache(),
- };
-}
-
-function updateSuspenseOffscreenState(
- prevOffscreenState: OffscreenState,
- renderLanes: Lanes,
-): OffscreenState {
- let cachePool: SpawnedCachePool | null = null;
- if (enableCache) {
- const prevCachePool: SpawnedCachePool | null = prevOffscreenState.cachePool;
- if (prevCachePool !== null) {
- const parentCache = isPrimaryRenderer
- ? CacheContext._currentValue
- : CacheContext._currentValue2;
- if (prevCachePool.parent !== parentCache) {
- // Detected a refresh in the parent. This overrides any previously
- // suspended cache.
- cachePool = {
- parent: parentCache,
- pool: parentCache,
- };
- } else {
- // We can reuse the cache from last time. The only thing that would have
- // overridden it is a parent refresh, which we checked for above.
- cachePool = prevCachePool;
- }
- } else {
- // If there's no previous cache pool, grab the current one.
- cachePool = getSuspendedCache();
- }
- }
- return {
- baseLanes: mergeLanes(prevOffscreenState.baseLanes, renderLanes),
- cachePool,
- };
-}
-
-// TODO: Probably should inline this back
-function shouldRemainOnFallback(
- current: null | Fiber,
- workInProgress: Fiber,
- renderLanes: Lanes,
-) {
- // If we're already showing a fallback, there are cases where we need to
- // remain on that fallback regardless of whether the content has resolved.
- // For example, SuspenseList coordinates when nested content appears.
- if (current !== null) {
- const suspenseState: SuspenseState = current.memoizedState;
- if (suspenseState === null) {
- // Currently showing content. Don't hide it, even if ForceSuspenseFallback
- // is true. More precise name might be "ForceRemainSuspenseFallback".
- // Note: This is a factoring smell. Can't remain on a fallback if there's
- // no fallback to remain on.
- return false;
- }
- }
-
- // Not currently showing content. Consult the Suspense context.
- const suspenseContext: SuspenseContext = suspenseStackCursor.current;
- return hasSuspenseListContext(
- suspenseContext,
- (ForceSuspenseFallback: SuspenseContext),
- );
-}
-
-function getRemainingWorkInPrimaryTree(current: Fiber, renderLanes) {
- // TODO: Should not remove render lanes that were pinged during this render
- return removeLanes(current.childLanes, renderLanes);
-}
-
-function updateSuspenseComponent(current, workInProgress, renderLanes) {
- const nextProps = workInProgress.pendingProps;
-
- // This is used by DevTools to force a boundary to suspend.
- if (__DEV__) {
- if (shouldSuspend(workInProgress)) {
- workInProgress.flags |= DidCapture;
- }
- }
-
- let showFallback = false;
- const didSuspend = (workInProgress.flags & DidCapture) !== NoFlags;
- if (
- didSuspend ||
- shouldRemainOnFallback(current, workInProgress, renderLanes)
- ) {
- // Something in this boundary's subtree already suspended. Switch to
- // rendering the fallback children.
- showFallback = true;
- workInProgress.flags &= ~DidCapture;
- }
-
- // OK, the next part is confusing. We're about to reconcile the Suspense
- // boundary's children. This involves some custom reconciliation logic. Two
- // main reasons this is so complicated.
- //
- // First, Legacy Mode has different semantics for backwards compatibility. The
- // primary tree will commit in an inconsistent state, so when we do the
- // second pass to render the fallback, we do some exceedingly, uh, clever
- // hacks to make that not totally break. Like transferring effects and
- // deletions from hidden tree. In Concurrent Mode, it's much simpler,
- // because we bailout on the primary tree completely and leave it in its old
- // state, no effects. Same as what we do for Offscreen (except that
- // Offscreen doesn't have the first render pass).
- //
- // Second is hydration. During hydration, the Suspense fiber has a slightly
- // different layout, where the child points to a dehydrated fragment, which
- // contains the DOM rendered by the server.
- //
- // Third, even if you set all that aside, Suspense is like error boundaries in
- // that we first we try to render one tree, and if that fails, we render again
- // and switch to a different tree. Like a try/catch block. So we have to track
- // which branch we're currently rendering. Ideally we would model this using
- // a stack.
- if (current === null) {
- // Initial mount
-
- // Special path for hydration
- // If we're currently hydrating, try to hydrate this boundary.
- if (getIsHydrating()) {
- // We must push the suspense handler context *before* attempting to
- // hydrate, to avoid a mismatch in case it errors.
- if (showFallback) {
- pushPrimaryTreeSuspenseHandler(workInProgress);
- } else {
- pushFallbackTreeSuspenseHandler(workInProgress);
- }
- tryToClaimNextHydratableInstance(workInProgress);
- // This could've been a dehydrated suspense component.
- const suspenseState: null | SuspenseState = workInProgress.memoizedState;
- if (suspenseState !== null) {
- const dehydrated = suspenseState.dehydrated;
- if (dehydrated !== null) {
- return mountDehydratedSuspenseComponent(
- workInProgress,
- dehydrated,
- renderLanes,
- );
- }
- }
- // If hydration didn't succeed, fall through to the normal Suspense path.
- // To avoid a stack mismatch we need to pop the Suspense handler that we
- // pushed above. This will become less awkward when move the hydration
- // logic to its own fiber.
- popSuspenseHandler(workInProgress);
- }
-
- const nextPrimaryChildren = nextProps.children;
- const nextFallbackChildren = nextProps.fallback;
-
- if (showFallback) {
- pushFallbackTreeSuspenseHandler(workInProgress);
-
- const fallbackFragment = mountSuspenseFallbackChildren(
- workInProgress,
- nextPrimaryChildren,
- nextFallbackChildren,
- renderLanes,
- );
- const primaryChildFragment: Fiber = (workInProgress.child: any);
- primaryChildFragment.memoizedState = mountSuspenseOffscreenState(
- renderLanes,
- );
- workInProgress.memoizedState = SUSPENDED_MARKER;
- if (enableTransitionTracing) {
- const currentTransitions = getPendingTransitions();
- if (currentTransitions !== null) {
- const parentMarkerInstances = getMarkerInstances();
- const offscreenQueue: OffscreenQueue | null = (primaryChildFragment.updateQueue: any);
- if (offscreenQueue === null) {
- const newOffscreenQueue: OffscreenQueue = {
- transitions: currentTransitions,
- markerInstances: parentMarkerInstances,
- wakeables: null,
- };
- primaryChildFragment.updateQueue = newOffscreenQueue;
- } else {
- offscreenQueue.transitions = currentTransitions;
- offscreenQueue.markerInstances = parentMarkerInstances;
- }
- }
- }
-
- return fallbackFragment;
- } else if (
- enableCPUSuspense &&
- typeof nextProps.unstable_expectedLoadTime === 'number'
- ) {
- // This is a CPU-bound tree. Skip this tree and show a placeholder to
- // unblock the surrounding content. Then immediately retry after the
- // initial commit.
- pushFallbackTreeSuspenseHandler(workInProgress);
- const fallbackFragment = mountSuspenseFallbackChildren(
- workInProgress,
- nextPrimaryChildren,
- nextFallbackChildren,
- renderLanes,
- );
- const primaryChildFragment: Fiber = (workInProgress.child: any);
- primaryChildFragment.memoizedState = mountSuspenseOffscreenState(
- renderLanes,
- );
- workInProgress.memoizedState = SUSPENDED_MARKER;
-
- // TODO: Transition Tracing is not yet implemented for CPU Suspense.
-
- // Since nothing actually suspended, there will nothing to ping this to
- // get it started back up to attempt the next item. While in terms of
- // priority this work has the same priority as this current render, it's
- // not part of the same transition once the transition has committed. If
- // it's sync, we still want to yield so that it can be painted.
- // Conceptually, this is really the same as pinging. We can use any
- // RetryLane even if it's the one currently rendering since we're leaving
- // it behind on this node.
- workInProgress.lanes = SomeRetryLane;
- return fallbackFragment;
- } else {
- pushPrimaryTreeSuspenseHandler(workInProgress);
- return mountSuspensePrimaryChildren(
- workInProgress,
- nextPrimaryChildren,
- renderLanes,
- );
- }
- } else {
- // This is an update.
-
- // Special path for hydration
- const prevState: null | SuspenseState = current.memoizedState;
- if (prevState !== null) {
- const dehydrated = prevState.dehydrated;
- if (dehydrated !== null) {
- return updateDehydratedSuspenseComponent(
- current,
- workInProgress,
- didSuspend,
- nextProps,
- dehydrated,
- prevState,
- renderLanes,
- );
- }
- }
-
- if (showFallback) {
- pushFallbackTreeSuspenseHandler(workInProgress);
-
- const nextFallbackChildren = nextProps.fallback;
- const nextPrimaryChildren = nextProps.children;
- const fallbackChildFragment = updateSuspenseFallbackChildren(
- current,
- workInProgress,
- nextPrimaryChildren,
- nextFallbackChildren,
- renderLanes,
- );
- const primaryChildFragment: Fiber = (workInProgress.child: any);
- const prevOffscreenState: OffscreenState | null = (current.child: any)
- .memoizedState;
- primaryChildFragment.memoizedState =
- prevOffscreenState === null
- ? mountSuspenseOffscreenState(renderLanes)
- : updateSuspenseOffscreenState(prevOffscreenState, renderLanes);
- if (enableTransitionTracing) {
- const currentTransitions = getPendingTransitions();
- if (currentTransitions !== null) {
- const parentMarkerInstances = getMarkerInstances();
- const offscreenQueue: OffscreenQueue | null = (primaryChildFragment.updateQueue: any);
- const currentOffscreenQueue: OffscreenQueue | null = (current.updateQueue: any);
- if (offscreenQueue === null) {
- const newOffscreenQueue: OffscreenQueue = {
- transitions: currentTransitions,
- markerInstances: parentMarkerInstances,
- wakeables: null,
- };
- primaryChildFragment.updateQueue = newOffscreenQueue;
- } else if (offscreenQueue === currentOffscreenQueue) {
- // If the work-in-progress queue is the same object as current, we
- // can't modify it without cloning it first.
- const newOffscreenQueue: OffscreenQueue = {
- transitions: currentTransitions,
- markerInstances: parentMarkerInstances,
- wakeables:
- currentOffscreenQueue !== null
- ? currentOffscreenQueue.wakeables
- : null,
- };
- primaryChildFragment.updateQueue = newOffscreenQueue;
- } else {
- offscreenQueue.transitions = currentTransitions;
- offscreenQueue.markerInstances = parentMarkerInstances;
- }
- }
- }
- primaryChildFragment.childLanes = getRemainingWorkInPrimaryTree(
- current,
- renderLanes,
- );
- workInProgress.memoizedState = SUSPENDED_MARKER;
- return fallbackChildFragment;
- } else {
- pushPrimaryTreeSuspenseHandler(workInProgress);
-
- const nextPrimaryChildren = nextProps.children;
- const primaryChildFragment = updateSuspensePrimaryChildren(
- current,
- workInProgress,
- nextPrimaryChildren,
- renderLanes,
- );
- workInProgress.memoizedState = null;
- return primaryChildFragment;
- }
- }
-}
-
-function mountSuspensePrimaryChildren(
- workInProgress,
- primaryChildren,
- renderLanes,
-) {
- const mode = workInProgress.mode;
- const primaryChildProps: OffscreenProps = {
- mode: 'visible',
- children: primaryChildren,
- };
- const primaryChildFragment = mountWorkInProgressOffscreenFiber(
- primaryChildProps,
- mode,
- renderLanes,
- );
- primaryChildFragment.return = workInProgress;
- workInProgress.child = primaryChildFragment;
- return primaryChildFragment;
-}
-
-function mountSuspenseFallbackChildren(
- workInProgress,
- primaryChildren,
- fallbackChildren,
- renderLanes,
-) {
- const mode = workInProgress.mode;
- const progressedPrimaryFragment: Fiber | null = workInProgress.child;
-
- const primaryChildProps: OffscreenProps = {
- mode: 'hidden',
- children: primaryChildren,
- };
-
- let primaryChildFragment;
- let fallbackChildFragment;
- if (
- (mode & ConcurrentMode) === NoMode &&
- progressedPrimaryFragment !== null
- ) {
- // In legacy mode, we commit the primary tree as if it successfully
- // completed, even though it's in an inconsistent state.
- primaryChildFragment = progressedPrimaryFragment;
- primaryChildFragment.childLanes = NoLanes;
- primaryChildFragment.pendingProps = primaryChildProps;
-
- if (enableProfilerTimer && workInProgress.mode & ProfileMode) {
- // Reset the durations from the first pass so they aren't included in the
- // final amounts. This seems counterintuitive, since we're intentionally
- // not measuring part of the render phase, but this makes it match what we
- // do in Concurrent Mode.
- primaryChildFragment.actualDuration = 0;
- primaryChildFragment.actualStartTime = -1;
- primaryChildFragment.selfBaseDuration = 0;
- primaryChildFragment.treeBaseDuration = 0;
- }
-
- fallbackChildFragment = createFiberFromFragment(
- fallbackChildren,
- mode,
- renderLanes,
- null,
- );
- } else {
- primaryChildFragment = mountWorkInProgressOffscreenFiber(
- primaryChildProps,
- mode,
- NoLanes,
- );
- fallbackChildFragment = createFiberFromFragment(
- fallbackChildren,
- mode,
- renderLanes,
- null,
- );
- }
-
- primaryChildFragment.return = workInProgress;
- fallbackChildFragment.return = workInProgress;
- primaryChildFragment.sibling = fallbackChildFragment;
- workInProgress.child = primaryChildFragment;
- return fallbackChildFragment;
-}
-
-function mountWorkInProgressOffscreenFiber(
- offscreenProps: OffscreenProps,
- mode: TypeOfMode,
- renderLanes: Lanes,
-) {
- // The props argument to `createFiberFromOffscreen` is `any` typed, so we use
- // this wrapper function to constrain it.
- return createFiberFromOffscreen(offscreenProps, mode, NoLanes, null);
-}
-
-function updateWorkInProgressOffscreenFiber(
- current: Fiber,
- offscreenProps: OffscreenProps,
-) {
- // The props argument to `createWorkInProgress` is `any` typed, so we use this
- // wrapper function to constrain it.
- return createWorkInProgress(current, offscreenProps);
-}
-
-function updateSuspensePrimaryChildren(
- current,
- workInProgress,
- primaryChildren,
- renderLanes,
-) {
- const currentPrimaryChildFragment: Fiber = (current.child: any);
- const currentFallbackChildFragment: Fiber | null =
- currentPrimaryChildFragment.sibling;
-
- const primaryChildFragment = updateWorkInProgressOffscreenFiber(
- currentPrimaryChildFragment,
- {
- mode: 'visible',
- children: primaryChildren,
- },
- );
- if ((workInProgress.mode & ConcurrentMode) === NoMode) {
- primaryChildFragment.lanes = renderLanes;
- }
- primaryChildFragment.return = workInProgress;
- primaryChildFragment.sibling = null;
- if (currentFallbackChildFragment !== null) {
- // Delete the fallback child fragment
- const deletions = workInProgress.deletions;
- if (deletions === null) {
- workInProgress.deletions = [currentFallbackChildFragment];
- workInProgress.flags |= ChildDeletion;
- } else {
- deletions.push(currentFallbackChildFragment);
- }
- }
-
- workInProgress.child = primaryChildFragment;
- return primaryChildFragment;
-}
-
-function updateSuspenseFallbackChildren(
- current,
- workInProgress,
- primaryChildren,
- fallbackChildren,
- renderLanes,
-) {
- const mode = workInProgress.mode;
- const currentPrimaryChildFragment: Fiber = (current.child: any);
- const currentFallbackChildFragment: Fiber | null =
- currentPrimaryChildFragment.sibling;
-
- const primaryChildProps: OffscreenProps = {
- mode: 'hidden',
- children: primaryChildren,
- };
-
- let primaryChildFragment;
- if (
- // In legacy mode, we commit the primary tree as if it successfully
- // completed, even though it's in an inconsistent state.
- (mode & ConcurrentMode) === NoMode &&
- // Make sure we're on the second pass, i.e. the primary child fragment was
- // already cloned. In legacy mode, the only case where this isn't true is
- // when DevTools forces us to display a fallback; we skip the first render
- // pass entirely and go straight to rendering the fallback. (In Concurrent
- // Mode, SuspenseList can also trigger this scenario, but this is a legacy-
- // only codepath.)
- workInProgress.child !== currentPrimaryChildFragment
- ) {
- const progressedPrimaryFragment: Fiber = (workInProgress.child: any);
- primaryChildFragment = progressedPrimaryFragment;
- primaryChildFragment.childLanes = NoLanes;
- primaryChildFragment.pendingProps = primaryChildProps;
-
- if (enableProfilerTimer && workInProgress.mode & ProfileMode) {
- // Reset the durations from the first pass so they aren't included in the
- // final amounts. This seems counterintuitive, since we're intentionally
- // not measuring part of the render phase, but this makes it match what we
- // do in Concurrent Mode.
- primaryChildFragment.actualDuration = 0;
- primaryChildFragment.actualStartTime = -1;
- primaryChildFragment.selfBaseDuration =
- currentPrimaryChildFragment.selfBaseDuration;
- primaryChildFragment.treeBaseDuration =
- currentPrimaryChildFragment.treeBaseDuration;
- }
-
- // The fallback fiber was added as a deletion during the first pass.
- // However, since we're going to remain on the fallback, we no longer want
- // to delete it.
- workInProgress.deletions = null;
- } else {
- primaryChildFragment = updateWorkInProgressOffscreenFiber(
- currentPrimaryChildFragment,
- primaryChildProps,
- );
- // Since we're reusing a current tree, we need to reuse the flags, too.
- // (We don't do this in legacy mode, because in legacy mode we don't re-use
- // the current tree; see previous branch.)
- primaryChildFragment.subtreeFlags =
- currentPrimaryChildFragment.subtreeFlags & StaticMask;
- }
- let fallbackChildFragment;
- if (currentFallbackChildFragment !== null) {
- fallbackChildFragment = createWorkInProgress(
- currentFallbackChildFragment,
- fallbackChildren,
- );
- } else {
- fallbackChildFragment = createFiberFromFragment(
- fallbackChildren,
- mode,
- renderLanes,
- null,
- );
- // Needs a placement effect because the parent (the Suspense boundary) already
- // mounted but this is a new fiber.
- fallbackChildFragment.flags |= Placement;
- }
-
- fallbackChildFragment.return = workInProgress;
- primaryChildFragment.return = workInProgress;
- primaryChildFragment.sibling = fallbackChildFragment;
- workInProgress.child = primaryChildFragment;
-
- return fallbackChildFragment;
-}
-
-function retrySuspenseComponentWithoutHydrating(
- current: Fiber,
- workInProgress: Fiber,
- renderLanes: Lanes,
- recoverableError: CapturedValue | null,
-) {
- // Falling back to client rendering. Because this has performance
- // implications, it's considered a recoverable error, even though the user
- // likely won't observe anything wrong with the UI.
- //
- // The error is passed in as an argument to enforce that every caller provide
- // a custom message, or explicitly opt out (currently the only path that opts
- // out is legacy mode; every concurrent path provides an error).
- if (recoverableError !== null) {
- queueHydrationError(recoverableError);
- }
-
- // This will add the old fiber to the deletion list
- reconcileChildFibers(workInProgress, current.child, null, renderLanes);
-
- // We're now not suspended nor dehydrated.
- const nextProps = workInProgress.pendingProps;
- const primaryChildren = nextProps.children;
- const primaryChildFragment = mountSuspensePrimaryChildren(
- workInProgress,
- primaryChildren,
- renderLanes,
- );
- // Needs a placement effect because the parent (the Suspense boundary) already
- // mounted but this is a new fiber.
- primaryChildFragment.flags |= Placement;
- workInProgress.memoizedState = null;
-
- return primaryChildFragment;
-}
-
-function mountSuspenseFallbackAfterRetryWithoutHydrating(
- current,
- workInProgress,
- primaryChildren,
- fallbackChildren,
- renderLanes,
-) {
- const fiberMode = workInProgress.mode;
- const primaryChildProps: OffscreenProps = {
- mode: 'visible',
- children: primaryChildren,
- };
- const primaryChildFragment = mountWorkInProgressOffscreenFiber(
- primaryChildProps,
- fiberMode,
- NoLanes,
- );
- const fallbackChildFragment = createFiberFromFragment(
- fallbackChildren,
- fiberMode,
- renderLanes,
- null,
- );
- // Needs a placement effect because the parent (the Suspense
- // boundary) already mounted but this is a new fiber.
- fallbackChildFragment.flags |= Placement;
-
- primaryChildFragment.return = workInProgress;
- fallbackChildFragment.return = workInProgress;
- primaryChildFragment.sibling = fallbackChildFragment;
- workInProgress.child = primaryChildFragment;
-
- if ((workInProgress.mode & ConcurrentMode) !== NoMode) {
- // We will have dropped the effect list which contains the
- // deletion. We need to reconcile to delete the current child.
- reconcileChildFibers(workInProgress, current.child, null, renderLanes);
- }
-
- return fallbackChildFragment;
-}
-
-function mountDehydratedSuspenseComponent(
- workInProgress: Fiber,
- suspenseInstance: SuspenseInstance,
- renderLanes: Lanes,
-): null | Fiber {
- // During the first pass, we'll bail out and not drill into the children.
- // Instead, we'll leave the content in place and try to hydrate it later.
- if ((workInProgress.mode & ConcurrentMode) === NoMode) {
- if (__DEV__) {
- console.error(
- 'Cannot hydrate Suspense in legacy mode. Switch from ' +
- 'ReactDOM.hydrate(element, container) to ' +
- 'ReactDOMClient.hydrateRoot(container, )' +
- '.render(element) or remove the Suspense components from ' +
- 'the server rendered components.',
- );
- }
- workInProgress.lanes = laneToLanes(SyncLane);
- } else if (isSuspenseInstanceFallback(suspenseInstance)) {
- // This is a client-only boundary. Since we won't get any content from the server
- // for this, we need to schedule that at a higher priority based on when it would
- // have timed out. In theory we could render it in this pass but it would have the
- // wrong priority associated with it and will prevent hydration of parent path.
- // Instead, we'll leave work left on it to render it in a separate commit.
-
- // TODO This time should be the time at which the server rendered response that is
- // a parent to this boundary was displayed. However, since we currently don't have
- // a protocol to transfer that time, we'll just estimate it by using the current
- // time. This will mean that Suspense timeouts are slightly shifted to later than
- // they should be.
- // Schedule a normal pri update to render this content.
- workInProgress.lanes = laneToLanes(DefaultHydrationLane);
- } else {
- // We'll continue hydrating the rest at offscreen priority since we'll already
- // be showing the right content coming from the server, it is no rush.
- workInProgress.lanes = laneToLanes(OffscreenLane);
- }
- return null;
-}
-
-function updateDehydratedSuspenseComponent(
- current: Fiber,
- workInProgress: Fiber,
- didSuspend: boolean,
- nextProps: any,
- suspenseInstance: SuspenseInstance,
- suspenseState: SuspenseState,
- renderLanes: Lanes,
-): null | Fiber {
- if (!didSuspend) {
- // This is the first render pass. Attempt to hydrate.
- pushPrimaryTreeSuspenseHandler(workInProgress);
-
- // We should never be hydrating at this point because it is the first pass,
- // but after we've already committed once.
- warnIfHydrating();
-
- if ((workInProgress.mode & ConcurrentMode) === NoMode) {
- return retrySuspenseComponentWithoutHydrating(
- current,
- workInProgress,
- renderLanes,
- null,
- );
- }
-
- if (isSuspenseInstanceFallback(suspenseInstance)) {
- // This boundary is in a permanent fallback state. In this case, we'll never
- // get an update and we'll never be able to hydrate the final content. Let's just try the
- // client side render instead.
- let digest, message, stack;
- if (__DEV__) {
- ({digest, message, stack} = getSuspenseInstanceFallbackErrorDetails(
- suspenseInstance,
- ));
- } else {
- ({digest} = getSuspenseInstanceFallbackErrorDetails(suspenseInstance));
- }
-
- let error;
- if (message) {
- // eslint-disable-next-line react-internal/prod-error-codes
- error = new Error(message);
- } else {
- error = new Error(
- 'The server could not finish this Suspense boundary, likely ' +
- 'due to an error during server rendering. Switched to ' +
- 'client rendering.',
- );
- }
- (error: any).digest = digest;
- const capturedValue = createCapturedValue(error, digest, stack);
- return retrySuspenseComponentWithoutHydrating(
- current,
- workInProgress,
- renderLanes,
- capturedValue,
- );
- }
-
- if (
- enableLazyContextPropagation &&
- // TODO: Factoring is a little weird, since we check this right below, too.
- // But don't want to re-arrange the if-else chain until/unless this
- // feature lands.
- !didReceiveUpdate
- ) {
- // We need to check if any children have context before we decide to bail
- // out, so propagate the changes now.
- lazilyPropagateParentContextChanges(current, workInProgress, renderLanes);
- }
-
- // We use lanes to indicate that a child might depend on context, so if
- // any context has changed, we need to treat is as if the input might have changed.
- const hasContextChanged = includesSomeLane(renderLanes, current.childLanes);
- if (didReceiveUpdate || hasContextChanged) {
- // This boundary has changed since the first render. This means that we are now unable to
- // hydrate it. We might still be able to hydrate it using a higher priority lane.
- const root = getWorkInProgressRoot();
- if (root !== null) {
- const attemptHydrationAtLane = getBumpedLaneForHydration(
- root,
- renderLanes,
- );
- if (
- attemptHydrationAtLane !== NoLane &&
- attemptHydrationAtLane !== suspenseState.retryLane
- ) {
- // Intentionally mutating since this render will get interrupted. This
- // is one of the very rare times where we mutate the current tree
- // during the render phase.
- suspenseState.retryLane = attemptHydrationAtLane;
- // TODO: Ideally this would inherit the event time of the current render
- const eventTime = NoTimestamp;
- enqueueConcurrentRenderForLane(current, attemptHydrationAtLane);
- scheduleUpdateOnFiber(
- root,
- current,
- attemptHydrationAtLane,
- eventTime,
- );
-
- // Throw a special object that signals to the work loop that it should
- // interrupt the current render.
- //
- // Because we're inside a React-only execution stack, we don't
- // strictly need to throw here — we could instead modify some internal
- // work loop state. But using an exception means we don't need to
- // check for this case on every iteration of the work loop. So doing
- // it this way moves the check out of the fast path.
- throw SelectiveHydrationException;
- } else {
- // We have already tried to ping at a higher priority than we're rendering with
- // so if we got here, we must have failed to hydrate at those levels. We must
- // now give up. Instead, we're going to delete the whole subtree and instead inject
- // a new real Suspense boundary to take its place, which may render content
- // or fallback. This might suspend for a while and if it does we might still have
- // an opportunity to hydrate before this pass commits.
- }
- }
-
- // If we did not selectively hydrate, we'll continue rendering without
- // hydrating. Mark this tree as suspended to prevent it from committing
- // outside a transition.
- //
- // This path should only happen if the hydration lane already suspended.
- // Currently, it also happens during sync updates because there is no
- // hydration lane for sync updates.
- // TODO: We should ideally have a sync hydration lane that we can apply to do
- // a pass where we hydrate this subtree in place using the previous Context and then
- // reapply the update afterwards.
- renderDidSuspendDelayIfPossible();
- return retrySuspenseComponentWithoutHydrating(
- current,
- workInProgress,
- renderLanes,
- null,
- );
- } else if (isSuspenseInstancePending(suspenseInstance)) {
- // This component is still pending more data from the server, so we can't hydrate its
- // content. We treat it as if this component suspended itself. It might seem as if
- // we could just try to render it client-side instead. However, this will perform a
- // lot of unnecessary work and is unlikely to complete since it often will suspend
- // on missing data anyway. Additionally, the server might be able to render more
- // than we can on the client yet. In that case we'd end up with more fallback states
- // on the client than if we just leave it alone. If the server times out or errors
- // these should update this boundary to the permanent Fallback state instead.
- // Mark it as having captured (i.e. suspended).
- workInProgress.flags |= DidCapture;
- // Leave the child in place. I.e. the dehydrated fragment.
- workInProgress.child = current.child;
- // Register a callback to retry this boundary once the server has sent the result.
- const retry = retryDehydratedSuspenseBoundary.bind(null, current);
- registerSuspenseInstanceRetry(suspenseInstance, retry);
- return null;
- } else {
- // This is the first attempt.
- reenterHydrationStateFromDehydratedSuspenseInstance(
- workInProgress,
- suspenseInstance,
- suspenseState.treeContext,
- );
- const primaryChildren = nextProps.children;
- const primaryChildFragment = mountSuspensePrimaryChildren(
- workInProgress,
- primaryChildren,
- renderLanes,
- );
- // Mark the children as hydrating. This is a fast path to know whether this
- // tree is part of a hydrating tree. This is used to determine if a child
- // node has fully mounted yet, and for scheduling event replaying.
- // Conceptually this is similar to Placement in that a new subtree is
- // inserted into the React tree here. It just happens to not need DOM
- // mutations because it already exists.
- primaryChildFragment.flags |= Hydrating;
- return primaryChildFragment;
- }
- } else {
- // This is the second render pass. We already attempted to hydrated, but
- // something either suspended or errored.
-
- if (workInProgress.flags & ForceClientRender) {
- // Something errored during hydration. Try again without hydrating.
- pushPrimaryTreeSuspenseHandler(workInProgress);
-
- workInProgress.flags &= ~ForceClientRender;
- const capturedValue = createCapturedValue(
- new Error(
- 'There was an error while hydrating this Suspense boundary. ' +
- 'Switched to client rendering.',
- ),
- );
- return retrySuspenseComponentWithoutHydrating(
- current,
- workInProgress,
- renderLanes,
- capturedValue,
- );
- } else if ((workInProgress.memoizedState: null | SuspenseState) !== null) {
- // Something suspended and we should still be in dehydrated mode.
- // Leave the existing child in place.
-
- // Push to avoid a mismatch
- pushFallbackTreeSuspenseHandler(workInProgress);
-
- workInProgress.child = current.child;
- // The dehydrated completion pass expects this flag to be there
- // but the normal suspense pass doesn't.
- workInProgress.flags |= DidCapture;
- return null;
- } else {
- // Suspended but we should no longer be in dehydrated mode.
- // Therefore we now have to render the fallback.
- pushFallbackTreeSuspenseHandler(workInProgress);
-
- const nextPrimaryChildren = nextProps.children;
- const nextFallbackChildren = nextProps.fallback;
- const fallbackChildFragment = mountSuspenseFallbackAfterRetryWithoutHydrating(
- current,
- workInProgress,
- nextPrimaryChildren,
- nextFallbackChildren,
- renderLanes,
- );
- const primaryChildFragment: Fiber = (workInProgress.child: any);
- primaryChildFragment.memoizedState = mountSuspenseOffscreenState(
- renderLanes,
- );
- workInProgress.memoizedState = SUSPENDED_MARKER;
- return fallbackChildFragment;
- }
- }
-}
-
-function scheduleSuspenseWorkOnFiber(
- fiber: Fiber,
- renderLanes: Lanes,
- propagationRoot: Fiber,
-) {
- fiber.lanes = mergeLanes(fiber.lanes, renderLanes);
- const alternate = fiber.alternate;
- if (alternate !== null) {
- alternate.lanes = mergeLanes(alternate.lanes, renderLanes);
- }
- scheduleContextWorkOnParentPath(fiber.return, renderLanes, propagationRoot);
-}
-
-function propagateSuspenseContextChange(
- workInProgress: Fiber,
- firstChild: null | Fiber,
- renderLanes: Lanes,
-): void {
- // Mark any Suspense boundaries with fallbacks as having work to do.
- // If they were previously forced into fallbacks, they may now be able
- // to unblock.
- let node = firstChild;
- while (node !== null) {
- if (node.tag === SuspenseComponent) {
- const state: SuspenseState | null = node.memoizedState;
- if (state !== null) {
- scheduleSuspenseWorkOnFiber(node, renderLanes, workInProgress);
- }
- } else if (node.tag === SuspenseListComponent) {
- // If the tail is hidden there might not be an Suspense boundaries
- // to schedule work on. In this case we have to schedule it on the
- // list itself.
- // We don't have to traverse to the children of the list since
- // the list will propagate the change when it rerenders.
- scheduleSuspenseWorkOnFiber(node, renderLanes, workInProgress);
- } else if (node.child !== null) {
- node.child.return = node;
- node = node.child;
- continue;
- }
- if (node === workInProgress) {
- return;
- }
- // $FlowFixMe[incompatible-use] found when upgrading Flow
- while (node.sibling === null) {
- // $FlowFixMe[incompatible-use] found when upgrading Flow
- if (node.return === null || node.return === workInProgress) {
- return;
- }
- node = node.return;
- }
- // $FlowFixMe[incompatible-use] found when upgrading Flow
- node.sibling.return = node.return;
- node = node.sibling;
- }
-}
-
-function findLastContentRow(firstChild: null | Fiber): null | Fiber {
- // This is going to find the last row among these children that is already
- // showing content on the screen, as opposed to being in fallback state or
- // new. If a row has multiple Suspense boundaries, any of them being in the
- // fallback state, counts as the whole row being in a fallback state.
- // Note that the "rows" will be workInProgress, but any nested children
- // will still be current since we haven't rendered them yet. The mounted
- // order may not be the same as the new order. We use the new order.
- let row = firstChild;
- let lastContentRow: null | Fiber = null;
- while (row !== null) {
- const currentRow = row.alternate;
- // New rows can't be content rows.
- if (currentRow !== null && findFirstSuspended(currentRow) === null) {
- lastContentRow = row;
- }
- row = row.sibling;
- }
- return lastContentRow;
-}
-
-type SuspenseListRevealOrder = 'forwards' | 'backwards' | 'together' | void;
-
-function validateRevealOrder(revealOrder: SuspenseListRevealOrder) {
- if (__DEV__) {
- if (
- revealOrder !== undefined &&
- revealOrder !== 'forwards' &&
- revealOrder !== 'backwards' &&
- revealOrder !== 'together' &&
- !didWarnAboutRevealOrder[revealOrder]
- ) {
- didWarnAboutRevealOrder[revealOrder] = true;
- if (typeof revealOrder === 'string') {
- switch (revealOrder.toLowerCase()) {
- case 'together':
- case 'forwards':
- case 'backwards': {
- console.error(
- '"%s" is not a valid value for revealOrder on . ' +
- 'Use lowercase "%s" instead.',
- revealOrder,
- revealOrder.toLowerCase(),
- );
- break;
- }
- case 'forward':
- case 'backward': {
- console.error(
- '"%s" is not a valid value for revealOrder on . ' +
- 'React uses the -s suffix in the spelling. Use "%ss" instead.',
- revealOrder,
- revealOrder.toLowerCase(),
- );
- break;
- }
- default:
- console.error(
- '"%s" is not a supported revealOrder on . ' +
- 'Did you mean "together", "forwards" or "backwards"?',
- revealOrder,
- );
- break;
- }
- } else {
- console.error(
- '%s is not a supported value for revealOrder on . ' +
- 'Did you mean "together", "forwards" or "backwards"?',
- revealOrder,
- );
- }
- }
- }
-}
-
-function validateTailOptions(
- tailMode: SuspenseListTailMode,
- revealOrder: SuspenseListRevealOrder,
-) {
- if (__DEV__) {
- if (tailMode !== undefined && !didWarnAboutTailOptions[tailMode]) {
- if (tailMode !== 'collapsed' && tailMode !== 'hidden') {
- didWarnAboutTailOptions[tailMode] = true;
- console.error(
- '"%s" is not a supported value for tail on . ' +
- 'Did you mean "collapsed" or "hidden"?',
- tailMode,
- );
- } else if (revealOrder !== 'forwards' && revealOrder !== 'backwards') {
- didWarnAboutTailOptions[tailMode] = true;
- console.error(
- ' is only valid if revealOrder is ' +
- '"forwards" or "backwards". ' +
- 'Did you mean to specify revealOrder="forwards"?',
- tailMode,
- );
- }
- }
- }
-}
-
-function validateSuspenseListNestedChild(childSlot: mixed, index: number) {
- if (__DEV__) {
- const isAnArray = isArray(childSlot);
- const isIterable =
- !isAnArray && typeof getIteratorFn(childSlot) === 'function';
- if (isAnArray || isIterable) {
- const type = isAnArray ? 'array' : 'iterable';
- console.error(
- 'A nested %s was passed to row #%s in . Wrap it in ' +
- 'an additional SuspenseList to configure its revealOrder: ' +
- ' ... ' +
- '{%s} ... ' +
- '',
- type,
- index,
- type,
- );
- return false;
- }
- }
- return true;
-}
-
-function validateSuspenseListChildren(
- children: mixed,
- revealOrder: SuspenseListRevealOrder,
-) {
- if (__DEV__) {
- if (
- (revealOrder === 'forwards' || revealOrder === 'backwards') &&
- children !== undefined &&
- children !== null &&
- children !== false
- ) {
- if (isArray(children)) {
- for (let i = 0; i < children.length; i++) {
- if (!validateSuspenseListNestedChild(children[i], i)) {
- return;
- }
- }
- } else {
- const iteratorFn = getIteratorFn(children);
- if (typeof iteratorFn === 'function') {
- const childrenIterator = iteratorFn.call(children);
- if (childrenIterator) {
- let step = childrenIterator.next();
- let i = 0;
- for (; !step.done; step = childrenIterator.next()) {
- if (!validateSuspenseListNestedChild(step.value, i)) {
- return;
- }
- i++;
- }
- }
- } else {
- console.error(
- 'A single row was passed to a . ' +
- 'This is not useful since it needs multiple rows. ' +
- 'Did you mean to pass multiple children or an array?',
- revealOrder,
- );
- }
- }
- }
- }
-}
-
-function initSuspenseListRenderState(
- workInProgress: Fiber,
- isBackwards: boolean,
- tail: null | Fiber,
- lastContentRow: null | Fiber,
- tailMode: SuspenseListTailMode,
-): void {
- const renderState: null | SuspenseListRenderState =
- workInProgress.memoizedState;
- if (renderState === null) {
- workInProgress.memoizedState = ({
- isBackwards: isBackwards,
- rendering: null,
- renderingStartTime: 0,
- last: lastContentRow,
- tail: tail,
- tailMode: tailMode,
- }: SuspenseListRenderState);
- } else {
- // We can reuse the existing object from previous renders.
- renderState.isBackwards = isBackwards;
- renderState.rendering = null;
- renderState.renderingStartTime = 0;
- renderState.last = lastContentRow;
- renderState.tail = tail;
- renderState.tailMode = tailMode;
- }
-}
-
-// This can end up rendering this component multiple passes.
-// The first pass splits the children fibers into two sets. A head and tail.
-// We first render the head. If anything is in fallback state, we do another
-// pass through beginWork to rerender all children (including the tail) with
-// the force suspend context. If the first render didn't have anything in
-// in fallback state. Then we render each row in the tail one-by-one.
-// That happens in the completeWork phase without going back to beginWork.
-function updateSuspenseListComponent(
- current: Fiber | null,
- workInProgress: Fiber,
- renderLanes: Lanes,
-) {
- const nextProps = workInProgress.pendingProps;
- const revealOrder: SuspenseListRevealOrder = nextProps.revealOrder;
- const tailMode: SuspenseListTailMode = nextProps.tail;
- const newChildren = nextProps.children;
-
- validateRevealOrder(revealOrder);
- validateTailOptions(tailMode, revealOrder);
- validateSuspenseListChildren(newChildren, revealOrder);
-
- reconcileChildren(current, workInProgress, newChildren, renderLanes);
-
- let suspenseContext: SuspenseContext = suspenseStackCursor.current;
-
- const shouldForceFallback = hasSuspenseListContext(
- suspenseContext,
- (ForceSuspenseFallback: SuspenseContext),
- );
- if (shouldForceFallback) {
- suspenseContext = setShallowSuspenseListContext(
- suspenseContext,
- ForceSuspenseFallback,
- );
- workInProgress.flags |= DidCapture;
- } else {
- const didSuspendBefore =
- current !== null && (current.flags & DidCapture) !== NoFlags;
- if (didSuspendBefore) {
- // If we previously forced a fallback, we need to schedule work
- // on any nested boundaries to let them know to try to render
- // again. This is the same as context updating.
- propagateSuspenseContextChange(
- workInProgress,
- workInProgress.child,
- renderLanes,
- );
- }
- suspenseContext = setDefaultShallowSuspenseListContext(suspenseContext);
- }
- pushSuspenseListContext(workInProgress, suspenseContext);
-
- if ((workInProgress.mode & ConcurrentMode) === NoMode) {
- // In legacy mode, SuspenseList doesn't work so we just
- // use make it a noop by treating it as the default revealOrder.
- workInProgress.memoizedState = null;
- } else {
- switch (revealOrder) {
- case 'forwards': {
- const lastContentRow = findLastContentRow(workInProgress.child);
- let tail;
- if (lastContentRow === null) {
- // The whole list is part of the tail.
- // TODO: We could fast path by just rendering the tail now.
- tail = workInProgress.child;
- workInProgress.child = null;
- } else {
- // Disconnect the tail rows after the content row.
- // We're going to render them separately later.
- tail = lastContentRow.sibling;
- lastContentRow.sibling = null;
- }
- initSuspenseListRenderState(
- workInProgress,
- false, // isBackwards
- tail,
- lastContentRow,
- tailMode,
- );
- break;
- }
- case 'backwards': {
- // We're going to find the first row that has existing content.
- // At the same time we're going to reverse the list of everything
- // we pass in the meantime. That's going to be our tail in reverse
- // order.
- let tail = null;
- let row = workInProgress.child;
- workInProgress.child = null;
- while (row !== null) {
- const currentRow = row.alternate;
- // New rows can't be content rows.
- if (currentRow !== null && findFirstSuspended(currentRow) === null) {
- // This is the beginning of the main content.
- workInProgress.child = row;
- break;
- }
- const nextRow = row.sibling;
- row.sibling = tail;
- tail = row;
- row = nextRow;
- }
- // TODO: If workInProgress.child is null, we can continue on the tail immediately.
- initSuspenseListRenderState(
- workInProgress,
- true, // isBackwards
- tail,
- null, // last
- tailMode,
- );
- break;
- }
- case 'together': {
- initSuspenseListRenderState(
- workInProgress,
- false, // isBackwards
- null, // tail
- null, // last
- undefined,
- );
- break;
- }
- default: {
- // The default reveal order is the same as not having
- // a boundary.
- workInProgress.memoizedState = null;
- }
- }
- }
- return workInProgress.child;
-}
-
-function updatePortalComponent(
- current: Fiber | null,
- workInProgress: Fiber,
- renderLanes: Lanes,
-) {
- pushHostContainer(workInProgress, workInProgress.stateNode.containerInfo);
- const nextChildren = workInProgress.pendingProps;
- if (current === null) {
- // Portals are special because we don't append the children during mount
- // but at commit. Therefore we need to track insertions which the normal
- // flow doesn't do during mount. This doesn't happen at the root because
- // the root always starts with a "current" with a null child.
- // TODO: Consider unifying this with how the root works.
- workInProgress.child = reconcileChildFibers(
- workInProgress,
- null,
- nextChildren,
- renderLanes,
- );
- } else {
- reconcileChildren(current, workInProgress, nextChildren, renderLanes);
- }
- return workInProgress.child;
-}
-
-let hasWarnedAboutUsingNoValuePropOnContextProvider = false;
-
-function updateContextProvider(
- current: Fiber | null,
- workInProgress: Fiber,
- renderLanes: Lanes,
-) {
- const providerType: ReactProviderType = workInProgress.type;
- const context: ReactContext = providerType._context;
-
- const newProps = workInProgress.pendingProps;
- const oldProps = workInProgress.memoizedProps;
-
- const newValue = newProps.value;
-
- if (__DEV__) {
- if (!('value' in newProps)) {
- if (!hasWarnedAboutUsingNoValuePropOnContextProvider) {
- hasWarnedAboutUsingNoValuePropOnContextProvider = true;
- console.error(
- 'The `value` prop is required for the ``. Did you misspell it or forget to pass it?',
- );
- }
- }
- const providerPropTypes = workInProgress.type.propTypes;
-
- if (providerPropTypes) {
- checkPropTypes(providerPropTypes, newProps, 'prop', 'Context.Provider');
- }
- }
-
- pushProvider(workInProgress, context, newValue);
-
- if (enableLazyContextPropagation) {
- // In the lazy propagation implementation, we don't scan for matching
- // consumers until something bails out, because until something bails out
- // we're going to visit those nodes, anyway. The trade-off is that it shifts
- // responsibility to the consumer to track whether something has changed.
- } else {
- if (oldProps !== null) {
- const oldValue = oldProps.value;
- if (is(oldValue, newValue)) {
- // No change. Bailout early if children are the same.
- if (
- oldProps.children === newProps.children &&
- !hasLegacyContextChanged()
- ) {
- return bailoutOnAlreadyFinishedWork(
- current,
- workInProgress,
- renderLanes,
- );
- }
- } else {
- // The context value changed. Search for matching consumers and schedule
- // them to update.
- propagateContextChange(workInProgress, context, renderLanes);
- }
- }
- }
-
- const newChildren = newProps.children;
- reconcileChildren(current, workInProgress, newChildren, renderLanes);
- return workInProgress.child;
-}
-
-let hasWarnedAboutUsingContextAsConsumer = false;
-
-function updateContextConsumer(
- current: Fiber | null,
- workInProgress: Fiber,
- renderLanes: Lanes,
-) {
- let context: ReactContext = workInProgress.type;
- // The logic below for Context differs depending on PROD or DEV mode. In
- // DEV mode, we create a separate object for Context.Consumer that acts
- // like a proxy to Context. This proxy object adds unnecessary code in PROD
- // so we use the old behaviour (Context.Consumer references Context) to
- // reduce size and overhead. The separate object references context via
- // a property called "_context", which also gives us the ability to check
- // in DEV mode if this property exists or not and warn if it does not.
- if (__DEV__) {
- if ((context: any)._context === undefined) {
- // This may be because it's a Context (rather than a Consumer).
- // Or it may be because it's older React where they're the same thing.
- // We only want to warn if we're sure it's a new React.
- if (context !== context.Consumer) {
- if (!hasWarnedAboutUsingContextAsConsumer) {
- hasWarnedAboutUsingContextAsConsumer = true;
- console.error(
- 'Rendering directly is not supported and will be removed in ' +
- 'a future major release. Did you mean to render instead?',
- );
- }
- }
- } else {
- context = (context: any)._context;
- }
- }
- const newProps = workInProgress.pendingProps;
- const render = newProps.children;
-
- if (__DEV__) {
- if (typeof render !== 'function') {
- console.error(
- 'A context consumer was rendered with multiple children, or a child ' +
- "that isn't a function. A context consumer expects a single child " +
- 'that is a function. If you did pass a function, make sure there ' +
- 'is no trailing or leading whitespace around it.',
- );
- }
- }
-
- prepareToReadContext(workInProgress, renderLanes);
- const newValue = readContext(context);
- if (enableSchedulingProfiler) {
- markComponentRenderStarted(workInProgress);
- }
- let newChildren;
- if (__DEV__) {
- ReactCurrentOwner.current = workInProgress;
- setIsRendering(true);
- newChildren = render(newValue);
- setIsRendering(false);
- } else {
- newChildren = render(newValue);
- }
- if (enableSchedulingProfiler) {
- markComponentRenderStopped();
- }
-
- // React DevTools reads this flag.
- workInProgress.flags |= PerformedWork;
- reconcileChildren(current, workInProgress, newChildren, renderLanes);
- return workInProgress.child;
-}
-
-function updateScopeComponent(current, workInProgress, renderLanes) {
- const nextProps = workInProgress.pendingProps;
- const nextChildren = nextProps.children;
-
- reconcileChildren(current, workInProgress, nextChildren, renderLanes);
- return workInProgress.child;
-}
-
-export function markWorkInProgressReceivedUpdate() {
- didReceiveUpdate = true;
-}
-
-export function checkIfWorkInProgressReceivedUpdate(): boolean {
- return didReceiveUpdate;
-}
-
-function resetSuspendedCurrentOnMountInLegacyMode(current, workInProgress) {
- if ((workInProgress.mode & ConcurrentMode) === NoMode) {
- if (current !== null) {
- // A lazy component only mounts if it suspended inside a non-
- // concurrent tree, in an inconsistent state. We want to treat it like
- // a new mount, even though an empty version of it already committed.
- // Disconnect the alternate pointers.
- current.alternate = null;
- workInProgress.alternate = null;
- // Since this is conceptually a new fiber, schedule a Placement effect
- workInProgress.flags |= Placement;
- }
- }
-}
-
-function bailoutOnAlreadyFinishedWork(
- current: Fiber | null,
- workInProgress: Fiber,
- renderLanes: Lanes,
-): Fiber | null {
- if (current !== null) {
- // Reuse previous dependencies
- workInProgress.dependencies = current.dependencies;
- }
-
- if (enableProfilerTimer) {
- // Don't update "base" render times for bailouts.
- stopProfilerTimerIfRunning(workInProgress);
- }
-
- markSkippedUpdateLanes(workInProgress.lanes);
-
- // Check if the children have any pending work.
- if (!includesSomeLane(renderLanes, workInProgress.childLanes)) {
- // The children don't have any work either. We can skip them.
- // TODO: Once we add back resuming, we should check if the children are
- // a work-in-progress set. If so, we need to transfer their effects.
-
- if (enableLazyContextPropagation && current !== null) {
- // Before bailing out, check if there are any context changes in
- // the children.
- lazilyPropagateParentContextChanges(current, workInProgress, renderLanes);
- if (!includesSomeLane(renderLanes, workInProgress.childLanes)) {
- return null;
- }
- } else {
- return null;
- }
- }
-
- // This fiber doesn't have work, but its subtree does. Clone the child
- // fibers and continue.
- cloneChildFibers(current, workInProgress);
- return workInProgress.child;
-}
-
-function remountFiber(
- current: Fiber,
- oldWorkInProgress: Fiber,
- newWorkInProgress: Fiber,
-): Fiber | null {
- if (__DEV__) {
- const returnFiber = oldWorkInProgress.return;
- if (returnFiber === null) {
- // eslint-disable-next-line react-internal/prod-error-codes
- throw new Error('Cannot swap the root fiber.');
- }
-
- // Disconnect from the old current.
- // It will get deleted.
- current.alternate = null;
- oldWorkInProgress.alternate = null;
-
- // Connect to the new tree.
- newWorkInProgress.index = oldWorkInProgress.index;
- newWorkInProgress.sibling = oldWorkInProgress.sibling;
- newWorkInProgress.return = oldWorkInProgress.return;
- newWorkInProgress.ref = oldWorkInProgress.ref;
-
- // Replace the child/sibling pointers above it.
- if (oldWorkInProgress === returnFiber.child) {
- returnFiber.child = newWorkInProgress;
- } else {
- let prevSibling = returnFiber.child;
- if (prevSibling === null) {
- // eslint-disable-next-line react-internal/prod-error-codes
- throw new Error('Expected parent to have a child.');
- }
- // $FlowFixMe[incompatible-use] found when upgrading Flow
- while (prevSibling.sibling !== oldWorkInProgress) {
- // $FlowFixMe[incompatible-use] found when upgrading Flow
- prevSibling = prevSibling.sibling;
- if (prevSibling === null) {
- // eslint-disable-next-line react-internal/prod-error-codes
- throw new Error('Expected to find the previous sibling.');
- }
- }
- // $FlowFixMe[incompatible-use] found when upgrading Flow
- prevSibling.sibling = newWorkInProgress;
- }
-
- // Delete the old fiber and place the new one.
- // Since the old fiber is disconnected, we have to schedule it manually.
- const deletions = returnFiber.deletions;
- if (deletions === null) {
- returnFiber.deletions = [current];
- returnFiber.flags |= ChildDeletion;
- } else {
- deletions.push(current);
- }
-
- newWorkInProgress.flags |= Placement;
-
- // Restart work from the new fiber.
- return newWorkInProgress;
- } else {
- throw new Error(
- 'Did not expect this call in production. ' +
- 'This is a bug in React. Please file an issue.',
- );
- }
-}
-
-function checkScheduledUpdateOrContext(
- current: Fiber,
- renderLanes: Lanes,
-): boolean {
- // Before performing an early bailout, we must check if there are pending
- // updates or context.
- const updateLanes = current.lanes;
- if (includesSomeLane(updateLanes, renderLanes)) {
- return true;
- }
- // No pending update, but because context is propagated lazily, we need
- // to check for a context change before we bail out.
- if (enableLazyContextPropagation) {
- const dependencies = current.dependencies;
- if (dependencies !== null && checkIfContextChanged(dependencies)) {
- return true;
- }
- }
- return false;
-}
-
-function attemptEarlyBailoutIfNoScheduledUpdate(
- current: Fiber,
- workInProgress: Fiber,
- renderLanes: Lanes,
-) {
- // This fiber does not have any pending work. Bailout without entering
- // the begin phase. There's still some bookkeeping we that needs to be done
- // in this optimized path, mostly pushing stuff onto the stack.
- switch (workInProgress.tag) {
- case HostRoot:
- pushHostRootContext(workInProgress);
- const root: FiberRoot = workInProgress.stateNode;
- pushRootTransition(workInProgress, root, renderLanes);
-
- if (enableTransitionTracing) {
- pushRootMarkerInstance(workInProgress);
- }
-
- if (enableCache) {
- const cache: Cache = current.memoizedState.cache;
- pushCacheProvider(workInProgress, cache);
- }
- resetHydrationState();
- break;
- case HostResource:
- case HostSingleton:
- case HostComponent:
- pushHostContext(workInProgress);
- break;
- case ClassComponent: {
- const Component = workInProgress.type;
- if (isLegacyContextProvider(Component)) {
- pushLegacyContextProvider(workInProgress);
- }
- break;
- }
- case HostPortal:
- pushHostContainer(workInProgress, workInProgress.stateNode.containerInfo);
- break;
- case ContextProvider: {
- const newValue = workInProgress.memoizedProps.value;
- const context: ReactContext = workInProgress.type._context;
- pushProvider(workInProgress, context, newValue);
- break;
- }
- case Profiler:
- if (enableProfilerTimer) {
- // Profiler should only call onRender when one of its descendants actually rendered.
- const hasChildWork = includesSomeLane(
- renderLanes,
- workInProgress.childLanes,
- );
- if (hasChildWork) {
- workInProgress.flags |= Update;
- }
-
- if (enableProfilerCommitHooks) {
- // Reset effect durations for the next eventual effect phase.
- // These are reset during render to allow the DevTools commit hook a chance to read them,
- const stateNode = workInProgress.stateNode;
- stateNode.effectDuration = 0;
- stateNode.passiveEffectDuration = 0;
- }
- }
- break;
- case SuspenseComponent: {
- const state: SuspenseState | null = workInProgress.memoizedState;
- if (state !== null) {
- if (state.dehydrated !== null) {
- // We're not going to render the children, so this is just to maintain
- // push/pop symmetry
- pushPrimaryTreeSuspenseHandler(workInProgress);
- // We know that this component will suspend again because if it has
- // been unsuspended it has committed as a resolved Suspense component.
- // If it needs to be retried, it should have work scheduled on it.
- workInProgress.flags |= DidCapture;
- // We should never render the children of a dehydrated boundary until we
- // upgrade it. We return null instead of bailoutOnAlreadyFinishedWork.
- return null;
- }
-
- // If this boundary is currently timed out, we need to decide
- // whether to retry the primary children, or to skip over it and
- // go straight to the fallback. Check the priority of the primary
- // child fragment.
- const primaryChildFragment: Fiber = (workInProgress.child: any);
- const primaryChildLanes = primaryChildFragment.childLanes;
- if (includesSomeLane(renderLanes, primaryChildLanes)) {
- // The primary children have pending work. Use the normal path
- // to attempt to render the primary children again.
- return updateSuspenseComponent(current, workInProgress, renderLanes);
- } else {
- // The primary child fragment does not have pending work marked
- // on it
- pushPrimaryTreeSuspenseHandler(workInProgress);
- // The primary children do not have pending work with sufficient
- // priority. Bailout.
- const child = bailoutOnAlreadyFinishedWork(
- current,
- workInProgress,
- renderLanes,
- );
- if (child !== null) {
- // The fallback children have pending work. Skip over the
- // primary children and work on the fallback.
- return child.sibling;
- } else {
- // Note: We can return `null` here because we already checked
- // whether there were nested context consumers, via the call to
- // `bailoutOnAlreadyFinishedWork` above.
- return null;
- }
- }
- } else {
- pushPrimaryTreeSuspenseHandler(workInProgress);
- }
- break;
- }
- case SuspenseListComponent: {
- const didSuspendBefore = (current.flags & DidCapture) !== NoFlags;
-
- let hasChildWork = includesSomeLane(
- renderLanes,
- workInProgress.childLanes,
- );
-
- if (enableLazyContextPropagation && !hasChildWork) {
- // Context changes may not have been propagated yet. We need to do
- // that now, before we can decide whether to bail out.
- // TODO: We use `childLanes` as a heuristic for whether there is
- // remaining work in a few places, including
- // `bailoutOnAlreadyFinishedWork` and
- // `updateDehydratedSuspenseComponent`. We should maybe extract this
- // into a dedicated function.
- lazilyPropagateParentContextChanges(
- current,
- workInProgress,
- renderLanes,
- );
- hasChildWork = includesSomeLane(renderLanes, workInProgress.childLanes);
- }
-
- if (didSuspendBefore) {
- if (hasChildWork) {
- // If something was in fallback state last time, and we have all the
- // same children then we're still in progressive loading state.
- // Something might get unblocked by state updates or retries in the
- // tree which will affect the tail. So we need to use the normal
- // path to compute the correct tail.
- return updateSuspenseListComponent(
- current,
- workInProgress,
- renderLanes,
- );
- }
- // If none of the children had any work, that means that none of
- // them got retried so they'll still be blocked in the same way
- // as before. We can fast bail out.
- workInProgress.flags |= DidCapture;
- }
-
- // If nothing suspended before and we're rendering the same children,
- // then the tail doesn't matter. Anything new that suspends will work
- // in the "together" mode, so we can continue from the state we had.
- const renderState = workInProgress.memoizedState;
- if (renderState !== null) {
- // Reset to the "together" mode in case we've started a different
- // update in the past but didn't complete it.
- renderState.rendering = null;
- renderState.tail = null;
- renderState.lastEffect = null;
- }
- pushSuspenseListContext(workInProgress, suspenseStackCursor.current);
-
- if (hasChildWork) {
- break;
- } else {
- // If none of the children had any work, that means that none of
- // them got retried so they'll still be blocked in the same way
- // as before. We can fast bail out.
- return null;
- }
- }
- case OffscreenComponent:
- case LegacyHiddenComponent: {
- // Need to check if the tree still needs to be deferred. This is
- // almost identical to the logic used in the normal update path,
- // so we'll just enter that. The only difference is we'll bail out
- // at the next level instead of this one, because the child props
- // have not changed. Which is fine.
- // TODO: Probably should refactor `beginWork` to split the bailout
- // path from the normal path. I'm tempted to do a labeled break here
- // but I won't :)
- workInProgress.lanes = NoLanes;
- return updateOffscreenComponent(current, workInProgress, renderLanes);
- }
- case CacheComponent: {
- if (enableCache) {
- const cache: Cache = current.memoizedState.cache;
- pushCacheProvider(workInProgress, cache);
- }
- break;
- }
- case TracingMarkerComponent: {
- if (enableTransitionTracing) {
- const instance: TracingMarkerInstance | null = workInProgress.stateNode;
- if (instance !== null) {
- pushMarkerInstance(workInProgress, instance);
- }
- }
- }
- }
- return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
-}
-
-function beginWork(
- current: Fiber | null,
- workInProgress: Fiber,
- renderLanes: Lanes,
-): Fiber | null {
- if (__DEV__) {
- if (workInProgress._debugNeedsRemount && current !== null) {
- // This will restart the begin phase with a new fiber.
- return remountFiber(
- current,
- workInProgress,
- createFiberFromTypeAndProps(
- workInProgress.type,
- workInProgress.key,
- workInProgress.pendingProps,
- workInProgress._debugOwner || null,
- workInProgress.mode,
- workInProgress.lanes,
- ),
- );
- }
- }
-
- if (current !== null) {
- const oldProps = current.memoizedProps;
- const newProps = workInProgress.pendingProps;
-
- if (
- oldProps !== newProps ||
- hasLegacyContextChanged() ||
- // Force a re-render if the implementation changed due to hot reload:
- (__DEV__ ? workInProgress.type !== current.type : false)
- ) {
- // If props or context changed, mark the fiber as having performed work.
- // This may be unset if the props are determined to be equal later (memo).
- didReceiveUpdate = true;
- } else {
- // Neither props nor legacy context changes. Check if there's a pending
- // update or context change.
- const hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(
- current,
- renderLanes,
- );
- if (
- !hasScheduledUpdateOrContext &&
- // If this is the second pass of an error or suspense boundary, there
- // may not be work scheduled on `current`, so we check for this flag.
- (workInProgress.flags & DidCapture) === NoFlags
- ) {
- // No pending updates or context. Bail out now.
- didReceiveUpdate = false;
- return attemptEarlyBailoutIfNoScheduledUpdate(
- current,
- workInProgress,
- renderLanes,
- );
- }
- if ((current.flags & ForceUpdateForLegacySuspense) !== NoFlags) {
- // This is a special case that only exists for legacy mode.
- // See https://github.com/facebook/react/pull/19216.
- didReceiveUpdate = true;
- } else {
- // An update was scheduled on this fiber, but there are no new props
- // nor legacy context. Set this to false. If an update queue or context
- // consumer produces a changed value, it will set this to true. Otherwise,
- // the component will assume the children have not changed and bail out.
- didReceiveUpdate = false;
- }
- }
- } else {
- didReceiveUpdate = false;
-
- if (getIsHydrating() && isForkedChild(workInProgress)) {
- // Check if this child belongs to a list of muliple children in
- // its parent.
- //
- // In a true multi-threaded implementation, we would render children on
- // parallel threads. This would represent the beginning of a new render
- // thread for this subtree.
- //
- // We only use this for id generation during hydration, which is why the
- // logic is located in this special branch.
- const slotIndex = workInProgress.index;
- const numberOfForks = getForksAtLevel(workInProgress);
- pushTreeId(workInProgress, numberOfForks, slotIndex);
- }
- }
-
- // Before entering the begin phase, clear pending update priority.
- // TODO: This assumes that we're about to evaluate the component and process
- // the update queue. However, there's an exception: SimpleMemoComponent
- // sometimes bails out later in the begin phase. This indicates that we should
- // move this assignment out of the common path and into each branch.
- workInProgress.lanes = NoLanes;
-
- switch (workInProgress.tag) {
- case IndeterminateComponent: {
- return mountIndeterminateComponent(
- current,
- workInProgress,
- workInProgress.type,
- renderLanes,
- );
- }
- case LazyComponent: {
- const elementType = workInProgress.elementType;
- return mountLazyComponent(
- current,
- workInProgress,
- elementType,
- renderLanes,
- );
- }
- case FunctionComponent: {
- const Component = workInProgress.type;
- const unresolvedProps = workInProgress.pendingProps;
- const resolvedProps =
- workInProgress.elementType === Component
- ? unresolvedProps
- : resolveDefaultProps(Component, unresolvedProps);
- return updateFunctionComponent(
- current,
- workInProgress,
- Component,
- resolvedProps,
- renderLanes,
- );
- }
- case ClassComponent: {
- const Component = workInProgress.type;
- const unresolvedProps = workInProgress.pendingProps;
- const resolvedProps =
- workInProgress.elementType === Component
- ? unresolvedProps
- : resolveDefaultProps(Component, unresolvedProps);
- return updateClassComponent(
- current,
- workInProgress,
- Component,
- resolvedProps,
- renderLanes,
- );
- }
- case HostRoot:
- return updateHostRoot(current, workInProgress, renderLanes);
- case HostResource:
- if (enableFloat && supportsResources) {
- return updateHostResource(current, workInProgress, renderLanes);
- }
- // eslint-disable-next-line no-fallthrough
- case HostSingleton:
- if (enableHostSingletons && supportsSingletons) {
- return updateHostSingleton(current, workInProgress, renderLanes);
- }
- // eslint-disable-next-line no-fallthrough
- case HostComponent:
- return updateHostComponent(current, workInProgress, renderLanes);
- case HostText:
- return updateHostText(current, workInProgress);
- case SuspenseComponent:
- return updateSuspenseComponent(current, workInProgress, renderLanes);
- case HostPortal:
- return updatePortalComponent(current, workInProgress, renderLanes);
- case ForwardRef: {
- const type = workInProgress.type;
- const unresolvedProps = workInProgress.pendingProps;
- const resolvedProps =
- workInProgress.elementType === type
- ? unresolvedProps
- : resolveDefaultProps(type, unresolvedProps);
- return updateForwardRef(
- current,
- workInProgress,
- type,
- resolvedProps,
- renderLanes,
- );
- }
- case Fragment:
- return updateFragment(current, workInProgress, renderLanes);
- case Mode:
- return updateMode(current, workInProgress, renderLanes);
- case Profiler:
- return updateProfiler(current, workInProgress, renderLanes);
- case ContextProvider:
- return updateContextProvider(current, workInProgress, renderLanes);
- case ContextConsumer:
- return updateContextConsumer(current, workInProgress, renderLanes);
- case MemoComponent: {
- const type = workInProgress.type;
- const unresolvedProps = workInProgress.pendingProps;
- // Resolve outer props first, then resolve inner props.
- let resolvedProps = resolveDefaultProps(type, unresolvedProps);
- if (__DEV__) {
- if (workInProgress.type !== workInProgress.elementType) {
- const outerPropTypes = type.propTypes;
- if (outerPropTypes) {
- checkPropTypes(
- outerPropTypes,
- resolvedProps, // Resolved for outer only
- 'prop',
- getComponentNameFromType(type),
- );
- }
- }
- }
- resolvedProps = resolveDefaultProps(type.type, resolvedProps);
- return updateMemoComponent(
- current,
- workInProgress,
- type,
- resolvedProps,
- renderLanes,
- );
- }
- case SimpleMemoComponent: {
- return updateSimpleMemoComponent(
- current,
- workInProgress,
- workInProgress.type,
- workInProgress.pendingProps,
- renderLanes,
- );
- }
- case IncompleteClassComponent: {
- const Component = workInProgress.type;
- const unresolvedProps = workInProgress.pendingProps;
- const resolvedProps =
- workInProgress.elementType === Component
- ? unresolvedProps
- : resolveDefaultProps(Component, unresolvedProps);
- return mountIncompleteClassComponent(
- current,
- workInProgress,
- Component,
- resolvedProps,
- renderLanes,
- );
- }
- case SuspenseListComponent: {
- return updateSuspenseListComponent(current, workInProgress, renderLanes);
- }
- case ScopeComponent: {
- if (enableScopeAPI) {
- return updateScopeComponent(current, workInProgress, renderLanes);
- }
- break;
- }
- case OffscreenComponent: {
- return updateOffscreenComponent(current, workInProgress, renderLanes);
- }
- case LegacyHiddenComponent: {
- if (enableLegacyHidden) {
- return updateLegacyHiddenComponent(
- current,
- workInProgress,
- renderLanes,
- );
- }
- break;
- }
- case CacheComponent: {
- if (enableCache) {
- return updateCacheComponent(current, workInProgress, renderLanes);
- }
- break;
- }
- case TracingMarkerComponent: {
- if (enableTransitionTracing) {
- return updateTracingMarkerComponent(
- current,
- workInProgress,
- renderLanes,
- );
- }
- break;
- }
- }
-
- throw new Error(
- `Unknown unit of work tag (${workInProgress.tag}). This error is likely caused by a bug in ` +
- 'React. Please file an issue.',
- );
-}
-
-export {beginWork};
diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.old.js b/packages/react-reconciler/src/ReactFiberBeginWork.old.js
index 1f22a6e18bcf5..6a017a83343a2 100644
--- a/packages/react-reconciler/src/ReactFiberBeginWork.old.js
+++ b/packages/react-reconciler/src/ReactFiberBeginWork.old.js
@@ -170,7 +170,7 @@ import {
getResource,
} from './ReactFiberHostConfig';
import type {SuspenseInstance} from './ReactFiberHostConfig';
-import {shouldError, shouldSuspend} from './ReactFiberReconciler';
+import {shouldError, shouldSuspend} from './ReactFiberReconciler.old';
import {pushHostContext, pushHostContainer} from './ReactFiberHostContext.old';
import {
suspenseStackCursor,
diff --git a/packages/react-reconciler/src/ReactFiberCache.new.js b/packages/react-reconciler/src/ReactFiberCache.new.js
deleted file mode 100644
index 2d73d6a57d03c..0000000000000
--- a/packages/react-reconciler/src/ReactFiberCache.new.js
+++ /dev/null
@@ -1,41 +0,0 @@
-/**
- * Copyright (c) Meta Platforms, Inc. and affiliates.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- *
- * @flow
- */
-
-import type {CacheDispatcher} from './ReactInternalTypes';
-import type {Cache} from './ReactFiberCacheComponent.new';
-
-import {enableCache} from 'shared/ReactFeatureFlags';
-import {readContext} from './ReactFiberNewContext.new';
-import {CacheContext} from './ReactFiberCacheComponent.new';
-
-function getCacheSignal(): AbortSignal {
- if (!enableCache) {
- throw new Error('Not implemented.');
- }
- const cache: Cache = readContext(CacheContext);
- return cache.controller.signal;
-}
-
-function getCacheForType(resourceType: () => T): T {
- if (!enableCache) {
- throw new Error('Not implemented.');
- }
- const cache: Cache = readContext(CacheContext);
- let cacheForType: T | void = (cache.data.get(resourceType): any);
- if (cacheForType === undefined) {
- cacheForType = resourceType();
- cache.data.set(resourceType, cacheForType);
- }
- return cacheForType;
-}
-
-export const DefaultCacheDispatcher: CacheDispatcher = {
- getCacheSignal,
- getCacheForType,
-};
diff --git a/packages/react-reconciler/src/ReactFiberCacheComponent.new.js b/packages/react-reconciler/src/ReactFiberCacheComponent.new.js
deleted file mode 100644
index 56b2025b46a41..0000000000000
--- a/packages/react-reconciler/src/ReactFiberCacheComponent.new.js
+++ /dev/null
@@ -1,147 +0,0 @@
-/**
- * Copyright (c) Meta Platforms, Inc. and affiliates.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- *
- * @flow
- */
-
-import type {ReactContext} from 'shared/ReactTypes';
-import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
-
-import {enableCache} from 'shared/ReactFeatureFlags';
-import {REACT_CONTEXT_TYPE} from 'shared/ReactSymbols';
-
-import {pushProvider, popProvider} from './ReactFiberNewContext.new';
-import * as Scheduler from 'scheduler';
-
-// In environments without AbortController (e.g. tests)
-// replace it with a lightweight shim that only has the features we use.
-const AbortControllerLocal: typeof AbortController = enableCache
- ? typeof AbortController !== 'undefined'
- ? AbortController
- : (function AbortControllerShim() {
- const listeners = [];
- const signal = (this.signal = {
- aborted: false,
- addEventListener: (type, listener) => {
- listeners.push(listener);
- },
- });
-
- this.abort = () => {
- signal.aborted = true;
- listeners.forEach(listener => listener());
- };
- }: AbortController)
- : (null: any);
-
-export type Cache = {
- controller: AbortControllerLocal,
- data: Map<() => mixed, mixed>,
- refCount: number,
-};
-
-export type CacheComponentState = {
- +parent: Cache,
- +cache: Cache,
-};
-
-export type SpawnedCachePool = {
- +parent: Cache,
- +pool: Cache,
-};
-
-// Intentionally not named imports because Rollup would
-// use dynamic dispatch for CommonJS interop named imports.
-const {
- unstable_scheduleCallback: scheduleCallback,
- unstable_NormalPriority: NormalPriority,
-} = Scheduler;
-
-export const CacheContext: ReactContext = enableCache
- ? {
- $$typeof: REACT_CONTEXT_TYPE,
- // We don't use Consumer/Provider for Cache components. So we'll cheat.
- Consumer: (null: any),
- Provider: (null: any),
- // We'll initialize these at the root.
- _currentValue: (null: any),
- _currentValue2: (null: any),
- _threadCount: 0,
- _defaultValue: (null: any),
- _globalName: (null: any),
- }
- : (null: any);
-
-if (__DEV__ && enableCache) {
- CacheContext._currentRenderer = null;
- CacheContext._currentRenderer2 = null;
-}
-
-// Creates a new empty Cache instance with a ref-count of 0. The caller is responsible
-// for retaining the cache once it is in use (retainCache), and releasing the cache
-// once it is no longer needed (releaseCache).
-export function createCache(): Cache {
- if (!enableCache) {
- return (null: any);
- }
- const cache: Cache = {
- controller: new AbortControllerLocal(),
- data: new Map(),
- refCount: 0,
- };
-
- return cache;
-}
-
-export function retainCache(cache: Cache) {
- if (!enableCache) {
- return;
- }
- if (__DEV__) {
- if (cache.controller.signal.aborted) {
- console.warn(
- 'A cache instance was retained after it was already freed. ' +
- 'This likely indicates a bug in React.',
- );
- }
- }
- cache.refCount++;
-}
-
-// Cleanup a cache instance, potentially freeing it if there are no more references
-export function releaseCache(cache: Cache) {
- if (!enableCache) {
- return;
- }
- cache.refCount--;
- if (__DEV__) {
- if (cache.refCount < 0) {
- console.warn(
- 'A cache instance was released after it was already freed. ' +
- 'This likely indicates a bug in React.',
- );
- }
- }
- if (cache.refCount === 0) {
- scheduleCallback(NormalPriority, () => {
- cache.controller.abort();
- });
- }
-}
-
-export function pushCacheProvider(workInProgress: Fiber, cache: Cache) {
- if (!enableCache) {
- return;
- }
- pushProvider(workInProgress, CacheContext, cache);
-}
-
-export function popCacheProvider(workInProgress: Fiber, cache: Cache) {
- if (!enableCache) {
- return;
- }
- popProvider(CacheContext, workInProgress);
-}
diff --git a/packages/react-reconciler/src/ReactFiberClassComponent.new.js b/packages/react-reconciler/src/ReactFiberClassComponent.new.js
deleted file mode 100644
index 14524865749d8..0000000000000
--- a/packages/react-reconciler/src/ReactFiberClassComponent.new.js
+++ /dev/null
@@ -1,1248 +0,0 @@
-/**
- * Copyright (c) Meta Platforms, Inc. and affiliates.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- *
- * @flow
- */
-
-import type {Fiber} from './ReactInternalTypes';
-import type {Lanes} from './ReactFiberLane.new';
-import type {UpdateQueue} from './ReactFiberClassUpdateQueue.new';
-import type {Flags} from './ReactFiberFlags';
-
-import {
- LayoutStatic,
- Update,
- Snapshot,
- MountLayoutDev,
-} from './ReactFiberFlags';
-import {
- debugRenderPhaseSideEffectsForStrictMode,
- disableLegacyContext,
- enableDebugTracing,
- enableSchedulingProfiler,
- warnAboutDeprecatedLifecycles,
- enableLazyContextPropagation,
-} from 'shared/ReactFeatureFlags';
-import ReactStrictModeWarnings from './ReactStrictModeWarnings.new';
-import {isMounted} from './ReactFiberTreeReflection';
-import {get as getInstance, set as setInstance} from 'shared/ReactInstanceMap';
-import shallowEqual from 'shared/shallowEqual';
-import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
-import getComponentNameFromType from 'shared/getComponentNameFromType';
-import assign from 'shared/assign';
-import isArray from 'shared/isArray';
-import {REACT_CONTEXT_TYPE, REACT_PROVIDER_TYPE} from 'shared/ReactSymbols';
-
-import {resolveDefaultProps} from './ReactFiberLazyComponent.new';
-import {
- DebugTracingMode,
- NoMode,
- StrictLegacyMode,
- StrictEffectsMode,
-} from './ReactTypeOfMode';
-
-import {
- enqueueUpdate,
- entangleTransitions,
- processUpdateQueue,
- checkHasForceUpdateAfterProcessing,
- resetHasForceUpdateBeforeProcessing,
- createUpdate,
- ReplaceState,
- ForceUpdate,
- initializeUpdateQueue,
- cloneUpdateQueue,
-} from './ReactFiberClassUpdateQueue.new';
-import {NoLanes} from './ReactFiberLane.new';
-import {
- cacheContext,
- getMaskedContext,
- getUnmaskedContext,
- hasContextChanged,
- emptyContextObject,
-} from './ReactFiberContext.new';
-import {readContext, checkIfContextChanged} from './ReactFiberNewContext.new';
-import {
- requestEventTime,
- requestUpdateLane,
- scheduleUpdateOnFiber,
-} from './ReactFiberWorkLoop.new';
-import {logForceUpdateScheduled, logStateUpdateScheduled} from './DebugTracing';
-import {
- markForceUpdateScheduled,
- markStateUpdateScheduled,
- setIsStrictModeForDevtools,
-} from './ReactFiberDevToolsHook.new';
-
-const fakeInternalInstance = {};
-
-let didWarnAboutStateAssignmentForComponent;
-let didWarnAboutUninitializedState;
-let didWarnAboutGetSnapshotBeforeUpdateWithoutDidUpdate;
-let didWarnAboutLegacyLifecyclesAndDerivedState;
-let didWarnAboutUndefinedDerivedState;
-let warnOnUndefinedDerivedState;
-let warnOnInvalidCallback;
-let didWarnAboutDirectlyAssigningPropsToState;
-let didWarnAboutContextTypeAndContextTypes;
-let didWarnAboutInvalidateContextType;
-
-if (__DEV__) {
- didWarnAboutStateAssignmentForComponent = new Set();
- didWarnAboutUninitializedState = new Set();
- didWarnAboutGetSnapshotBeforeUpdateWithoutDidUpdate = new Set();
- didWarnAboutLegacyLifecyclesAndDerivedState = new Set();
- didWarnAboutDirectlyAssigningPropsToState = new Set();
- didWarnAboutUndefinedDerivedState = new Set();
- didWarnAboutContextTypeAndContextTypes = new Set();
- didWarnAboutInvalidateContextType = new Set();
-
- const didWarnOnInvalidCallback = new Set();
-
- warnOnInvalidCallback = function(callback: mixed, callerName: string) {
- if (callback === null || typeof callback === 'function') {
- return;
- }
- const key = callerName + '_' + (callback: any);
- if (!didWarnOnInvalidCallback.has(key)) {
- didWarnOnInvalidCallback.add(key);
- console.error(
- '%s(...): Expected the last optional `callback` argument to be a ' +
- 'function. Instead received: %s.',
- callerName,
- callback,
- );
- }
- };
-
- warnOnUndefinedDerivedState = function(type, partialState) {
- if (partialState === undefined) {
- const componentName = getComponentNameFromType(type) || 'Component';
- if (!didWarnAboutUndefinedDerivedState.has(componentName)) {
- didWarnAboutUndefinedDerivedState.add(componentName);
- console.error(
- '%s.getDerivedStateFromProps(): A valid state object (or null) must be returned. ' +
- 'You have returned undefined.',
- componentName,
- );
- }
- }
- };
-
- // This is so gross but it's at least non-critical and can be removed if
- // it causes problems. This is meant to give a nicer error message for
- // ReactDOM15.unstable_renderSubtreeIntoContainer(reactDOM16Component,
- // ...)) which otherwise throws a "_processChildContext is not a function"
- // exception.
- Object.defineProperty(fakeInternalInstance, '_processChildContext', {
- enumerable: false,
- value: function() {
- throw new Error(
- '_processChildContext is not available in React 16+. This likely ' +
- 'means you have multiple copies of React and are attempting to nest ' +
- 'a React 15 tree inside a React 16 tree using ' +
- "unstable_renderSubtreeIntoContainer, which isn't supported. Try " +
- 'to make sure you have only one copy of React (and ideally, switch ' +
- 'to ReactDOM.createPortal).',
- );
- },
- });
- Object.freeze(fakeInternalInstance);
-}
-
-function applyDerivedStateFromProps(
- workInProgress: Fiber,
- ctor: any,
- getDerivedStateFromProps: (props: any, state: any) => any,
- nextProps: any,
-) {
- const prevState = workInProgress.memoizedState;
- let partialState = getDerivedStateFromProps(nextProps, prevState);
- if (__DEV__) {
- if (
- debugRenderPhaseSideEffectsForStrictMode &&
- workInProgress.mode & StrictLegacyMode
- ) {
- setIsStrictModeForDevtools(true);
- try {
- // Invoke the function an extra time to help detect side-effects.
- partialState = getDerivedStateFromProps(nextProps, prevState);
- } finally {
- setIsStrictModeForDevtools(false);
- }
- }
- warnOnUndefinedDerivedState(ctor, partialState);
- }
- // Merge the partial state and the previous state.
- const memoizedState =
- partialState === null || partialState === undefined
- ? prevState
- : assign({}, prevState, partialState);
- workInProgress.memoizedState = memoizedState;
-
- // Once the update queue is empty, persist the derived state onto the
- // base state.
- if (workInProgress.lanes === NoLanes) {
- // Queue is always non-null for classes
- const updateQueue: UpdateQueue = (workInProgress.updateQueue: any);
- updateQueue.baseState = memoizedState;
- }
-}
-
-const classComponentUpdater = {
- isMounted,
- enqueueSetState(inst, payload, callback) {
- const fiber = getInstance(inst);
- const eventTime = requestEventTime();
- const lane = requestUpdateLane(fiber);
-
- const update = createUpdate(eventTime, lane);
- update.payload = payload;
- if (callback !== undefined && callback !== null) {
- if (__DEV__) {
- warnOnInvalidCallback(callback, 'setState');
- }
- update.callback = callback;
- }
-
- const root = enqueueUpdate(fiber, update, lane);
- if (root !== null) {
- scheduleUpdateOnFiber(root, fiber, lane, eventTime);
- entangleTransitions(root, fiber, lane);
- }
-
- if (__DEV__) {
- if (enableDebugTracing) {
- if (fiber.mode & DebugTracingMode) {
- const name = getComponentNameFromFiber(fiber) || 'Unknown';
- logStateUpdateScheduled(name, lane, payload);
- }
- }
- }
-
- if (enableSchedulingProfiler) {
- markStateUpdateScheduled(fiber, lane);
- }
- },
- enqueueReplaceState(inst, payload, callback) {
- const fiber = getInstance(inst);
- const eventTime = requestEventTime();
- const lane = requestUpdateLane(fiber);
-
- const update = createUpdate(eventTime, lane);
- update.tag = ReplaceState;
- update.payload = payload;
-
- if (callback !== undefined && callback !== null) {
- if (__DEV__) {
- warnOnInvalidCallback(callback, 'replaceState');
- }
- update.callback = callback;
- }
-
- const root = enqueueUpdate(fiber, update, lane);
- if (root !== null) {
- scheduleUpdateOnFiber(root, fiber, lane, eventTime);
- entangleTransitions(root, fiber, lane);
- }
-
- if (__DEV__) {
- if (enableDebugTracing) {
- if (fiber.mode & DebugTracingMode) {
- const name = getComponentNameFromFiber(fiber) || 'Unknown';
- logStateUpdateScheduled(name, lane, payload);
- }
- }
- }
-
- if (enableSchedulingProfiler) {
- markStateUpdateScheduled(fiber, lane);
- }
- },
- enqueueForceUpdate(inst, callback) {
- const fiber = getInstance(inst);
- const eventTime = requestEventTime();
- const lane = requestUpdateLane(fiber);
-
- const update = createUpdate(eventTime, lane);
- update.tag = ForceUpdate;
-
- if (callback !== undefined && callback !== null) {
- if (__DEV__) {
- warnOnInvalidCallback(callback, 'forceUpdate');
- }
- update.callback = callback;
- }
-
- const root = enqueueUpdate(fiber, update, lane);
- if (root !== null) {
- scheduleUpdateOnFiber(root, fiber, lane, eventTime);
- entangleTransitions(root, fiber, lane);
- }
-
- if (__DEV__) {
- if (enableDebugTracing) {
- if (fiber.mode & DebugTracingMode) {
- const name = getComponentNameFromFiber(fiber) || 'Unknown';
- logForceUpdateScheduled(name, lane);
- }
- }
- }
-
- if (enableSchedulingProfiler) {
- markForceUpdateScheduled(fiber, lane);
- }
- },
-};
-
-function checkShouldComponentUpdate(
- workInProgress,
- ctor,
- oldProps,
- newProps,
- oldState,
- newState,
- nextContext,
-) {
- const instance = workInProgress.stateNode;
- if (typeof instance.shouldComponentUpdate === 'function') {
- let shouldUpdate = instance.shouldComponentUpdate(
- newProps,
- newState,
- nextContext,
- );
- if (__DEV__) {
- if (
- debugRenderPhaseSideEffectsForStrictMode &&
- workInProgress.mode & StrictLegacyMode
- ) {
- setIsStrictModeForDevtools(true);
- try {
- // Invoke the function an extra time to help detect side-effects.
- shouldUpdate = instance.shouldComponentUpdate(
- newProps,
- newState,
- nextContext,
- );
- } finally {
- setIsStrictModeForDevtools(false);
- }
- }
- if (shouldUpdate === undefined) {
- console.error(
- '%s.shouldComponentUpdate(): Returned undefined instead of a ' +
- 'boolean value. Make sure to return true or false.',
- getComponentNameFromType(ctor) || 'Component',
- );
- }
- }
-
- return shouldUpdate;
- }
-
- if (ctor.prototype && ctor.prototype.isPureReactComponent) {
- return (
- !shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState)
- );
- }
-
- return true;
-}
-
-function checkClassInstance(workInProgress: Fiber, ctor: any, newProps: any) {
- const instance = workInProgress.stateNode;
- if (__DEV__) {
- const name = getComponentNameFromType(ctor) || 'Component';
- const renderPresent = instance.render;
-
- if (!renderPresent) {
- if (ctor.prototype && typeof ctor.prototype.render === 'function') {
- console.error(
- '%s(...): No `render` method found on the returned component ' +
- 'instance: did you accidentally return an object from the constructor?',
- name,
- );
- } else {
- console.error(
- '%s(...): No `render` method found on the returned component ' +
- 'instance: you may have forgotten to define `render`.',
- name,
- );
- }
- }
-
- if (
- instance.getInitialState &&
- !instance.getInitialState.isReactClassApproved &&
- !instance.state
- ) {
- console.error(
- 'getInitialState was defined on %s, a plain JavaScript class. ' +
- 'This is only supported for classes created using React.createClass. ' +
- 'Did you mean to define a state property instead?',
- name,
- );
- }
- if (
- instance.getDefaultProps &&
- !instance.getDefaultProps.isReactClassApproved
- ) {
- console.error(
- 'getDefaultProps was defined on %s, a plain JavaScript class. ' +
- 'This is only supported for classes created using React.createClass. ' +
- 'Use a static property to define defaultProps instead.',
- name,
- );
- }
- if (instance.propTypes) {
- console.error(
- 'propTypes was defined as an instance property on %s. Use a static ' +
- 'property to define propTypes instead.',
- name,
- );
- }
- if (instance.contextType) {
- console.error(
- 'contextType was defined as an instance property on %s. Use a static ' +
- 'property to define contextType instead.',
- name,
- );
- }
-
- if (disableLegacyContext) {
- if (ctor.childContextTypes) {
- console.error(
- '%s uses the legacy childContextTypes API which is no longer supported. ' +
- 'Use React.createContext() instead.',
- name,
- );
- }
- if (ctor.contextTypes) {
- console.error(
- '%s uses the legacy contextTypes API which is no longer supported. ' +
- 'Use React.createContext() with static contextType instead.',
- name,
- );
- }
- } else {
- if (instance.contextTypes) {
- console.error(
- 'contextTypes was defined as an instance property on %s. Use a static ' +
- 'property to define contextTypes instead.',
- name,
- );
- }
-
- if (
- ctor.contextType &&
- ctor.contextTypes &&
- !didWarnAboutContextTypeAndContextTypes.has(ctor)
- ) {
- didWarnAboutContextTypeAndContextTypes.add(ctor);
- console.error(
- '%s declares both contextTypes and contextType static properties. ' +
- 'The legacy contextTypes property will be ignored.',
- name,
- );
- }
- }
-
- if (typeof instance.componentShouldUpdate === 'function') {
- console.error(
- '%s has a method called ' +
- 'componentShouldUpdate(). Did you mean shouldComponentUpdate()? ' +
- 'The name is phrased as a question because the function is ' +
- 'expected to return a value.',
- name,
- );
- }
- if (
- ctor.prototype &&
- ctor.prototype.isPureReactComponent &&
- typeof instance.shouldComponentUpdate !== 'undefined'
- ) {
- console.error(
- '%s has a method called shouldComponentUpdate(). ' +
- 'shouldComponentUpdate should not be used when extending React.PureComponent. ' +
- 'Please extend React.Component if shouldComponentUpdate is used.',
- getComponentNameFromType(ctor) || 'A pure component',
- );
- }
- if (typeof instance.componentDidUnmount === 'function') {
- console.error(
- '%s has a method called ' +
- 'componentDidUnmount(). But there is no such lifecycle method. ' +
- 'Did you mean componentWillUnmount()?',
- name,
- );
- }
- if (typeof instance.componentDidReceiveProps === 'function') {
- console.error(
- '%s has a method called ' +
- 'componentDidReceiveProps(). But there is no such lifecycle method. ' +
- 'If you meant to update the state in response to changing props, ' +
- 'use componentWillReceiveProps(). If you meant to fetch data or ' +
- 'run side-effects or mutations after React has updated the UI, use componentDidUpdate().',
- name,
- );
- }
- if (typeof instance.componentWillRecieveProps === 'function') {
- console.error(
- '%s has a method called ' +
- 'componentWillRecieveProps(). Did you mean componentWillReceiveProps()?',
- name,
- );
- }
- if (typeof instance.UNSAFE_componentWillRecieveProps === 'function') {
- console.error(
- '%s has a method called ' +
- 'UNSAFE_componentWillRecieveProps(). Did you mean UNSAFE_componentWillReceiveProps()?',
- name,
- );
- }
- const hasMutatedProps = instance.props !== newProps;
- if (instance.props !== undefined && hasMutatedProps) {
- console.error(
- '%s(...): When calling super() in `%s`, make sure to pass ' +
- "up the same props that your component's constructor was passed.",
- name,
- name,
- );
- }
- if (instance.defaultProps) {
- console.error(
- 'Setting defaultProps as an instance property on %s is not supported and will be ignored.' +
- ' Instead, define defaultProps as a static property on %s.',
- name,
- name,
- );
- }
-
- if (
- typeof instance.getSnapshotBeforeUpdate === 'function' &&
- typeof instance.componentDidUpdate !== 'function' &&
- !didWarnAboutGetSnapshotBeforeUpdateWithoutDidUpdate.has(ctor)
- ) {
- didWarnAboutGetSnapshotBeforeUpdateWithoutDidUpdate.add(ctor);
- console.error(
- '%s: getSnapshotBeforeUpdate() should be used with componentDidUpdate(). ' +
- 'This component defines getSnapshotBeforeUpdate() only.',
- getComponentNameFromType(ctor),
- );
- }
-
- if (typeof instance.getDerivedStateFromProps === 'function') {
- console.error(
- '%s: getDerivedStateFromProps() is defined as an instance method ' +
- 'and will be ignored. Instead, declare it as a static method.',
- name,
- );
- }
- if (typeof instance.getDerivedStateFromError === 'function') {
- console.error(
- '%s: getDerivedStateFromError() is defined as an instance method ' +
- 'and will be ignored. Instead, declare it as a static method.',
- name,
- );
- }
- if (typeof ctor.getSnapshotBeforeUpdate === 'function') {
- console.error(
- '%s: getSnapshotBeforeUpdate() is defined as a static method ' +
- 'and will be ignored. Instead, declare it as an instance method.',
- name,
- );
- }
- const state = instance.state;
- if (state && (typeof state !== 'object' || isArray(state))) {
- console.error('%s.state: must be set to an object or null', name);
- }
- if (
- typeof instance.getChildContext === 'function' &&
- typeof ctor.childContextTypes !== 'object'
- ) {
- console.error(
- '%s.getChildContext(): childContextTypes must be defined in order to ' +
- 'use getChildContext().',
- name,
- );
- }
- }
-}
-
-function adoptClassInstance(workInProgress: Fiber, instance: any): void {
- instance.updater = classComponentUpdater;
- workInProgress.stateNode = instance;
- // The instance needs access to the fiber so that it can schedule updates
- setInstance(instance, workInProgress);
- if (__DEV__) {
- instance._reactInternalInstance = fakeInternalInstance;
- }
-}
-
-function constructClassInstance(
- workInProgress: Fiber,
- ctor: any,
- props: any,
-): any {
- let isLegacyContextConsumer = false;
- let unmaskedContext = emptyContextObject;
- let context = emptyContextObject;
- const contextType = ctor.contextType;
-
- if (__DEV__) {
- if ('contextType' in ctor) {
- const isValid =
- // Allow null for conditional declaration
- contextType === null ||
- (contextType !== undefined &&
- contextType.$$typeof === REACT_CONTEXT_TYPE &&
- contextType._context === undefined); // Not a
-
- if (!isValid && !didWarnAboutInvalidateContextType.has(ctor)) {
- didWarnAboutInvalidateContextType.add(ctor);
-
- let addendum = '';
- if (contextType === undefined) {
- addendum =
- ' However, it is set to undefined. ' +
- 'This can be caused by a typo or by mixing up named and default imports. ' +
- 'This can also happen due to a circular dependency, so ' +
- 'try moving the createContext() call to a separate file.';
- } else if (typeof contextType !== 'object') {
- addendum = ' However, it is set to a ' + typeof contextType + '.';
- } else if (contextType.$$typeof === REACT_PROVIDER_TYPE) {
- addendum = ' Did you accidentally pass the Context.Provider instead?';
- } else if (contextType._context !== undefined) {
- //
- addendum = ' Did you accidentally pass the Context.Consumer instead?';
- } else {
- addendum =
- ' However, it is set to an object with keys {' +
- Object.keys(contextType).join(', ') +
- '}.';
- }
- console.error(
- '%s defines an invalid contextType. ' +
- 'contextType should point to the Context object returned by React.createContext().%s',
- getComponentNameFromType(ctor) || 'Component',
- addendum,
- );
- }
- }
- }
-
- if (typeof contextType === 'object' && contextType !== null) {
- context = readContext((contextType: any));
- } else if (!disableLegacyContext) {
- unmaskedContext = getUnmaskedContext(workInProgress, ctor, true);
- const contextTypes = ctor.contextTypes;
- isLegacyContextConsumer =
- contextTypes !== null && contextTypes !== undefined;
- context = isLegacyContextConsumer
- ? getMaskedContext(workInProgress, unmaskedContext)
- : emptyContextObject;
- }
-
- let instance = new ctor(props, context);
- // Instantiate twice to help detect side-effects.
- if (__DEV__) {
- if (
- debugRenderPhaseSideEffectsForStrictMode &&
- workInProgress.mode & StrictLegacyMode
- ) {
- setIsStrictModeForDevtools(true);
- try {
- instance = new ctor(props, context); // eslint-disable-line no-new
- } finally {
- setIsStrictModeForDevtools(false);
- }
- }
- }
-
- const state = (workInProgress.memoizedState =
- instance.state !== null && instance.state !== undefined
- ? instance.state
- : null);
- adoptClassInstance(workInProgress, instance);
-
- if (__DEV__) {
- if (typeof ctor.getDerivedStateFromProps === 'function' && state === null) {
- const componentName = getComponentNameFromType(ctor) || 'Component';
- if (!didWarnAboutUninitializedState.has(componentName)) {
- didWarnAboutUninitializedState.add(componentName);
- console.error(
- '`%s` uses `getDerivedStateFromProps` but its initial state is ' +
- '%s. This is not recommended. Instead, define the initial state by ' +
- 'assigning an object to `this.state` in the constructor of `%s`. ' +
- 'This ensures that `getDerivedStateFromProps` arguments have a consistent shape.',
- componentName,
- instance.state === null ? 'null' : 'undefined',
- componentName,
- );
- }
- }
-
- // If new component APIs are defined, "unsafe" lifecycles won't be called.
- // Warn about these lifecycles if they are present.
- // Don't warn about react-lifecycles-compat polyfilled methods though.
- if (
- typeof ctor.getDerivedStateFromProps === 'function' ||
- typeof instance.getSnapshotBeforeUpdate === 'function'
- ) {
- let foundWillMountName = null;
- let foundWillReceivePropsName = null;
- let foundWillUpdateName = null;
- if (
- typeof instance.componentWillMount === 'function' &&
- instance.componentWillMount.__suppressDeprecationWarning !== true
- ) {
- foundWillMountName = 'componentWillMount';
- } else if (typeof instance.UNSAFE_componentWillMount === 'function') {
- foundWillMountName = 'UNSAFE_componentWillMount';
- }
- if (
- typeof instance.componentWillReceiveProps === 'function' &&
- instance.componentWillReceiveProps.__suppressDeprecationWarning !== true
- ) {
- foundWillReceivePropsName = 'componentWillReceiveProps';
- } else if (
- typeof instance.UNSAFE_componentWillReceiveProps === 'function'
- ) {
- foundWillReceivePropsName = 'UNSAFE_componentWillReceiveProps';
- }
- if (
- typeof instance.componentWillUpdate === 'function' &&
- instance.componentWillUpdate.__suppressDeprecationWarning !== true
- ) {
- foundWillUpdateName = 'componentWillUpdate';
- } else if (typeof instance.UNSAFE_componentWillUpdate === 'function') {
- foundWillUpdateName = 'UNSAFE_componentWillUpdate';
- }
- if (
- foundWillMountName !== null ||
- foundWillReceivePropsName !== null ||
- foundWillUpdateName !== null
- ) {
- const componentName = getComponentNameFromType(ctor) || 'Component';
- const newApiName =
- typeof ctor.getDerivedStateFromProps === 'function'
- ? 'getDerivedStateFromProps()'
- : 'getSnapshotBeforeUpdate()';
- if (!didWarnAboutLegacyLifecyclesAndDerivedState.has(componentName)) {
- didWarnAboutLegacyLifecyclesAndDerivedState.add(componentName);
- console.error(
- 'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' +
- '%s uses %s but also contains the following legacy lifecycles:%s%s%s\n\n' +
- 'The above lifecycles should be removed. Learn more about this warning here:\n' +
- 'https://reactjs.org/link/unsafe-component-lifecycles',
- componentName,
- newApiName,
- foundWillMountName !== null ? `\n ${foundWillMountName}` : '',
- foundWillReceivePropsName !== null
- ? `\n ${foundWillReceivePropsName}`
- : '',
- foundWillUpdateName !== null ? `\n ${foundWillUpdateName}` : '',
- );
- }
- }
- }
- }
-
- // Cache unmasked context so we can avoid recreating masked context unless necessary.
- // ReactFiberContext usually updates this cache but can't for newly-created instances.
- if (isLegacyContextConsumer) {
- cacheContext(workInProgress, unmaskedContext, context);
- }
-
- return instance;
-}
-
-function callComponentWillMount(workInProgress, instance) {
- const oldState = instance.state;
-
- if (typeof instance.componentWillMount === 'function') {
- instance.componentWillMount();
- }
- if (typeof instance.UNSAFE_componentWillMount === 'function') {
- instance.UNSAFE_componentWillMount();
- }
-
- if (oldState !== instance.state) {
- if (__DEV__) {
- console.error(
- '%s.componentWillMount(): Assigning directly to this.state is ' +
- "deprecated (except inside a component's " +
- 'constructor). Use setState instead.',
- getComponentNameFromFiber(workInProgress) || 'Component',
- );
- }
- classComponentUpdater.enqueueReplaceState(instance, instance.state, null);
- }
-}
-
-function callComponentWillReceiveProps(
- workInProgress,
- instance,
- newProps,
- nextContext,
-) {
- const oldState = instance.state;
- if (typeof instance.componentWillReceiveProps === 'function') {
- instance.componentWillReceiveProps(newProps, nextContext);
- }
- if (typeof instance.UNSAFE_componentWillReceiveProps === 'function') {
- instance.UNSAFE_componentWillReceiveProps(newProps, nextContext);
- }
-
- if (instance.state !== oldState) {
- if (__DEV__) {
- const componentName =
- getComponentNameFromFiber(workInProgress) || 'Component';
- if (!didWarnAboutStateAssignmentForComponent.has(componentName)) {
- didWarnAboutStateAssignmentForComponent.add(componentName);
- console.error(
- '%s.componentWillReceiveProps(): Assigning directly to ' +
- "this.state is deprecated (except inside a component's " +
- 'constructor). Use setState instead.',
- componentName,
- );
- }
- }
- classComponentUpdater.enqueueReplaceState(instance, instance.state, null);
- }
-}
-
-// Invokes the mount life-cycles on a previously never rendered instance.
-function mountClassInstance(
- workInProgress: Fiber,
- ctor: any,
- newProps: any,
- renderLanes: Lanes,
-): void {
- if (__DEV__) {
- checkClassInstance(workInProgress, ctor, newProps);
- }
-
- const instance = workInProgress.stateNode;
- instance.props = newProps;
- instance.state = workInProgress.memoizedState;
- instance.refs = {};
-
- initializeUpdateQueue(workInProgress);
-
- const contextType = ctor.contextType;
- if (typeof contextType === 'object' && contextType !== null) {
- instance.context = readContext(contextType);
- } else if (disableLegacyContext) {
- instance.context = emptyContextObject;
- } else {
- const unmaskedContext = getUnmaskedContext(workInProgress, ctor, true);
- instance.context = getMaskedContext(workInProgress, unmaskedContext);
- }
-
- if (__DEV__) {
- if (instance.state === newProps) {
- const componentName = getComponentNameFromType(ctor) || 'Component';
- if (!didWarnAboutDirectlyAssigningPropsToState.has(componentName)) {
- didWarnAboutDirectlyAssigningPropsToState.add(componentName);
- console.error(
- '%s: It is not recommended to assign props directly to state ' +
- "because updates to props won't be reflected in state. " +
- 'In most cases, it is better to use props directly.',
- componentName,
- );
- }
- }
-
- if (workInProgress.mode & StrictLegacyMode) {
- ReactStrictModeWarnings.recordLegacyContextWarning(
- workInProgress,
- instance,
- );
- }
-
- if (warnAboutDeprecatedLifecycles) {
- ReactStrictModeWarnings.recordUnsafeLifecycleWarnings(
- workInProgress,
- instance,
- );
- }
- }
-
- instance.state = workInProgress.memoizedState;
-
- const getDerivedStateFromProps = ctor.getDerivedStateFromProps;
- if (typeof getDerivedStateFromProps === 'function') {
- applyDerivedStateFromProps(
- workInProgress,
- ctor,
- getDerivedStateFromProps,
- newProps,
- );
- instance.state = workInProgress.memoizedState;
- }
-
- // In order to support react-lifecycles-compat polyfilled components,
- // Unsafe lifecycles should not be invoked for components using the new APIs.
- if (
- typeof ctor.getDerivedStateFromProps !== 'function' &&
- typeof instance.getSnapshotBeforeUpdate !== 'function' &&
- (typeof instance.UNSAFE_componentWillMount === 'function' ||
- typeof instance.componentWillMount === 'function')
- ) {
- callComponentWillMount(workInProgress, instance);
- // If we had additional state updates during this life-cycle, let's
- // process them now.
- processUpdateQueue(workInProgress, newProps, instance, renderLanes);
- instance.state = workInProgress.memoizedState;
- }
-
- if (typeof instance.componentDidMount === 'function') {
- let fiberFlags: Flags = Update | LayoutStatic;
- if (__DEV__ && (workInProgress.mode & StrictEffectsMode) !== NoMode) {
- fiberFlags |= MountLayoutDev;
- }
- workInProgress.flags |= fiberFlags;
- }
-}
-
-function resumeMountClassInstance(
- workInProgress: Fiber,
- ctor: any,
- newProps: any,
- renderLanes: Lanes,
-): boolean {
- const instance = workInProgress.stateNode;
-
- const oldProps = workInProgress.memoizedProps;
- instance.props = oldProps;
-
- const oldContext = instance.context;
- const contextType = ctor.contextType;
- let nextContext = emptyContextObject;
- if (typeof contextType === 'object' && contextType !== null) {
- nextContext = readContext(contextType);
- } else if (!disableLegacyContext) {
- const nextLegacyUnmaskedContext = getUnmaskedContext(
- workInProgress,
- ctor,
- true,
- );
- nextContext = getMaskedContext(workInProgress, nextLegacyUnmaskedContext);
- }
-
- const getDerivedStateFromProps = ctor.getDerivedStateFromProps;
- const hasNewLifecycles =
- typeof getDerivedStateFromProps === 'function' ||
- typeof instance.getSnapshotBeforeUpdate === 'function';
-
- // Note: During these life-cycles, instance.props/instance.state are what
- // ever the previously attempted to render - not the "current". However,
- // during componentDidUpdate we pass the "current" props.
-
- // In order to support react-lifecycles-compat polyfilled components,
- // Unsafe lifecycles should not be invoked for components using the new APIs.
- if (
- !hasNewLifecycles &&
- (typeof instance.UNSAFE_componentWillReceiveProps === 'function' ||
- typeof instance.componentWillReceiveProps === 'function')
- ) {
- if (oldProps !== newProps || oldContext !== nextContext) {
- callComponentWillReceiveProps(
- workInProgress,
- instance,
- newProps,
- nextContext,
- );
- }
- }
-
- resetHasForceUpdateBeforeProcessing();
-
- const oldState = workInProgress.memoizedState;
- let newState = (instance.state = oldState);
- processUpdateQueue(workInProgress, newProps, instance, renderLanes);
- newState = workInProgress.memoizedState;
- if (
- oldProps === newProps &&
- oldState === newState &&
- !hasContextChanged() &&
- !checkHasForceUpdateAfterProcessing()
- ) {
- // If an update was already in progress, we should schedule an Update
- // effect even though we're bailing out, so that cWU/cDU are called.
- if (typeof instance.componentDidMount === 'function') {
- let fiberFlags: Flags = Update | LayoutStatic;
- if (__DEV__ && (workInProgress.mode & StrictEffectsMode) !== NoMode) {
- fiberFlags |= MountLayoutDev;
- }
- workInProgress.flags |= fiberFlags;
- }
- return false;
- }
-
- if (typeof getDerivedStateFromProps === 'function') {
- applyDerivedStateFromProps(
- workInProgress,
- ctor,
- getDerivedStateFromProps,
- newProps,
- );
- newState = workInProgress.memoizedState;
- }
-
- const shouldUpdate =
- checkHasForceUpdateAfterProcessing() ||
- checkShouldComponentUpdate(
- workInProgress,
- ctor,
- oldProps,
- newProps,
- oldState,
- newState,
- nextContext,
- );
-
- if (shouldUpdate) {
- // In order to support react-lifecycles-compat polyfilled components,
- // Unsafe lifecycles should not be invoked for components using the new APIs.
- if (
- !hasNewLifecycles &&
- (typeof instance.UNSAFE_componentWillMount === 'function' ||
- typeof instance.componentWillMount === 'function')
- ) {
- if (typeof instance.componentWillMount === 'function') {
- instance.componentWillMount();
- }
- if (typeof instance.UNSAFE_componentWillMount === 'function') {
- instance.UNSAFE_componentWillMount();
- }
- }
- if (typeof instance.componentDidMount === 'function') {
- let fiberFlags: Flags = Update | LayoutStatic;
- if (__DEV__ && (workInProgress.mode & StrictEffectsMode) !== NoMode) {
- fiberFlags |= MountLayoutDev;
- }
- workInProgress.flags |= fiberFlags;
- }
- } else {
- // If an update was already in progress, we should schedule an Update
- // effect even though we're bailing out, so that cWU/cDU are called.
- if (typeof instance.componentDidMount === 'function') {
- let fiberFlags: Flags = Update | LayoutStatic;
- if (__DEV__ && (workInProgress.mode & StrictEffectsMode) !== NoMode) {
- fiberFlags |= MountLayoutDev;
- }
- workInProgress.flags |= fiberFlags;
- }
-
- // If shouldComponentUpdate returned false, we should still update the
- // memoized state to indicate that this work can be reused.
- workInProgress.memoizedProps = newProps;
- workInProgress.memoizedState = newState;
- }
-
- // Update the existing instance's state, props, and context pointers even
- // if shouldComponentUpdate returns false.
- instance.props = newProps;
- instance.state = newState;
- instance.context = nextContext;
-
- return shouldUpdate;
-}
-
-// Invokes the update life-cycles and returns false if it shouldn't rerender.
-function updateClassInstance(
- current: Fiber,
- workInProgress: Fiber,
- ctor: any,
- newProps: any,
- renderLanes: Lanes,
-): boolean {
- const instance = workInProgress.stateNode;
-
- cloneUpdateQueue(current, workInProgress);
-
- const unresolvedOldProps = workInProgress.memoizedProps;
- const oldProps =
- workInProgress.type === workInProgress.elementType
- ? unresolvedOldProps
- : resolveDefaultProps(workInProgress.type, unresolvedOldProps);
- instance.props = oldProps;
- const unresolvedNewProps = workInProgress.pendingProps;
-
- const oldContext = instance.context;
- const contextType = ctor.contextType;
- let nextContext = emptyContextObject;
- if (typeof contextType === 'object' && contextType !== null) {
- nextContext = readContext(contextType);
- } else if (!disableLegacyContext) {
- const nextUnmaskedContext = getUnmaskedContext(workInProgress, ctor, true);
- nextContext = getMaskedContext(workInProgress, nextUnmaskedContext);
- }
-
- const getDerivedStateFromProps = ctor.getDerivedStateFromProps;
- const hasNewLifecycles =
- typeof getDerivedStateFromProps === 'function' ||
- typeof instance.getSnapshotBeforeUpdate === 'function';
-
- // Note: During these life-cycles, instance.props/instance.state are what
- // ever the previously attempted to render - not the "current". However,
- // during componentDidUpdate we pass the "current" props.
-
- // In order to support react-lifecycles-compat polyfilled components,
- // Unsafe lifecycles should not be invoked for components using the new APIs.
- if (
- !hasNewLifecycles &&
- (typeof instance.UNSAFE_componentWillReceiveProps === 'function' ||
- typeof instance.componentWillReceiveProps === 'function')
- ) {
- if (
- unresolvedOldProps !== unresolvedNewProps ||
- oldContext !== nextContext
- ) {
- callComponentWillReceiveProps(
- workInProgress,
- instance,
- newProps,
- nextContext,
- );
- }
- }
-
- resetHasForceUpdateBeforeProcessing();
-
- const oldState = workInProgress.memoizedState;
- let newState = (instance.state = oldState);
- processUpdateQueue(workInProgress, newProps, instance, renderLanes);
- newState = workInProgress.memoizedState;
-
- if (
- unresolvedOldProps === unresolvedNewProps &&
- oldState === newState &&
- !hasContextChanged() &&
- !checkHasForceUpdateAfterProcessing() &&
- !(
- enableLazyContextPropagation &&
- current !== null &&
- current.dependencies !== null &&
- checkIfContextChanged(current.dependencies)
- )
- ) {
- // If an update was already in progress, we should schedule an Update
- // effect even though we're bailing out, so that cWU/cDU are called.
- if (typeof instance.componentDidUpdate === 'function') {
- if (
- unresolvedOldProps !== current.memoizedProps ||
- oldState !== current.memoizedState
- ) {
- workInProgress.flags |= Update;
- }
- }
- if (typeof instance.getSnapshotBeforeUpdate === 'function') {
- if (
- unresolvedOldProps !== current.memoizedProps ||
- oldState !== current.memoizedState
- ) {
- workInProgress.flags |= Snapshot;
- }
- }
- return false;
- }
-
- if (typeof getDerivedStateFromProps === 'function') {
- applyDerivedStateFromProps(
- workInProgress,
- ctor,
- getDerivedStateFromProps,
- newProps,
- );
- newState = workInProgress.memoizedState;
- }
-
- const shouldUpdate =
- checkHasForceUpdateAfterProcessing() ||
- checkShouldComponentUpdate(
- workInProgress,
- ctor,
- oldProps,
- newProps,
- oldState,
- newState,
- nextContext,
- ) ||
- // TODO: In some cases, we'll end up checking if context has changed twice,
- // both before and after `shouldComponentUpdate` has been called. Not ideal,
- // but I'm loath to refactor this function. This only happens for memoized
- // components so it's not that common.
- (enableLazyContextPropagation &&
- current !== null &&
- current.dependencies !== null &&
- checkIfContextChanged(current.dependencies));
-
- if (shouldUpdate) {
- // In order to support react-lifecycles-compat polyfilled components,
- // Unsafe lifecycles should not be invoked for components using the new APIs.
- if (
- !hasNewLifecycles &&
- (typeof instance.UNSAFE_componentWillUpdate === 'function' ||
- typeof instance.componentWillUpdate === 'function')
- ) {
- if (typeof instance.componentWillUpdate === 'function') {
- instance.componentWillUpdate(newProps, newState, nextContext);
- }
- if (typeof instance.UNSAFE_componentWillUpdate === 'function') {
- instance.UNSAFE_componentWillUpdate(newProps, newState, nextContext);
- }
- }
- if (typeof instance.componentDidUpdate === 'function') {
- workInProgress.flags |= Update;
- }
- if (typeof instance.getSnapshotBeforeUpdate === 'function') {
- workInProgress.flags |= Snapshot;
- }
- } else {
- // If an update was already in progress, we should schedule an Update
- // effect even though we're bailing out, so that cWU/cDU are called.
- if (typeof instance.componentDidUpdate === 'function') {
- if (
- unresolvedOldProps !== current.memoizedProps ||
- oldState !== current.memoizedState
- ) {
- workInProgress.flags |= Update;
- }
- }
- if (typeof instance.getSnapshotBeforeUpdate === 'function') {
- if (
- unresolvedOldProps !== current.memoizedProps ||
- oldState !== current.memoizedState
- ) {
- workInProgress.flags |= Snapshot;
- }
- }
-
- // If shouldComponentUpdate returned false, we should still update the
- // memoized props/state to indicate that this work can be reused.
- workInProgress.memoizedProps = newProps;
- workInProgress.memoizedState = newState;
- }
-
- // Update the existing instance's state, props, and context pointers even
- // if shouldComponentUpdate returns false.
- instance.props = newProps;
- instance.state = newState;
- instance.context = nextContext;
-
- return shouldUpdate;
-}
-
-export {
- adoptClassInstance,
- constructClassInstance,
- mountClassInstance,
- resumeMountClassInstance,
- updateClassInstance,
-};
diff --git a/packages/react-reconciler/src/ReactFiberClassUpdateQueue.new.js b/packages/react-reconciler/src/ReactFiberClassUpdateQueue.new.js
deleted file mode 100644
index 6ad50d8130571..0000000000000
--- a/packages/react-reconciler/src/ReactFiberClassUpdateQueue.new.js
+++ /dev/null
@@ -1,742 +0,0 @@
-/**
- * Copyright (c) Meta Platforms, Inc. and affiliates.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- *
- * @flow
- */
-
-// UpdateQueue is a linked list of prioritized updates.
-//
-// Like fibers, update queues come in pairs: a current queue, which represents
-// the visible state of the screen, and a work-in-progress queue, which can be
-// mutated and processed asynchronously before it is committed — a form of
-// double buffering. If a work-in-progress render is discarded before finishing,
-// we create a new work-in-progress by cloning the current queue.
-//
-// Both queues share a persistent, singly-linked list structure. To schedule an
-// update, we append it to the end of both queues. Each queue maintains a
-// pointer to first update in the persistent list that hasn't been processed.
-// The work-in-progress pointer always has a position equal to or greater than
-// the current queue, since we always work on that one. The current queue's
-// pointer is only updated during the commit phase, when we swap in the
-// work-in-progress.
-//
-// For example:
-//
-// Current pointer: A - B - C - D - E - F
-// Work-in-progress pointer: D - E - F
-// ^
-// The work-in-progress queue has
-// processed more updates than current.
-//
-// The reason we append to both queues is because otherwise we might drop
-// updates without ever processing them. For example, if we only add updates to
-// the work-in-progress queue, some updates could be lost whenever a work-in
-// -progress render restarts by cloning from current. Similarly, if we only add
-// updates to the current queue, the updates will be lost whenever an already
-// in-progress queue commits and swaps with the current queue. However, by
-// adding to both queues, we guarantee that the update will be part of the next
-// work-in-progress. (And because the work-in-progress queue becomes the
-// current queue once it commits, there's no danger of applying the same
-// update twice.)
-//
-// Prioritization
-// --------------
-//
-// Updates are not sorted by priority, but by insertion; new updates are always
-// appended to the end of the list.
-//
-// The priority is still important, though. When processing the update queue
-// during the render phase, only the updates with sufficient priority are
-// included in the result. If we skip an update because it has insufficient
-// priority, it remains in the queue to be processed later, during a lower
-// priority render. Crucially, all updates subsequent to a skipped update also
-// remain in the queue *regardless of their priority*. That means high priority
-// updates are sometimes processed twice, at two separate priorities. We also
-// keep track of a base state, that represents the state before the first
-// update in the queue is applied.
-//
-// For example:
-//
-// Given a base state of '', and the following queue of updates
-//
-// A1 - B2 - C1 - D2
-//
-// where the number indicates the priority, and the update is applied to the
-// previous state by appending a letter, React will process these updates as
-// two separate renders, one per distinct priority level:
-//
-// First render, at priority 1:
-// Base state: ''
-// Updates: [A1, C1]
-// Result state: 'AC'
-//
-// Second render, at priority 2:
-// Base state: 'A' <- The base state does not include C1,
-// because B2 was skipped.
-// Updates: [B2, C1, D2] <- C1 was rebased on top of B2
-// Result state: 'ABCD'
-//
-// Because we process updates in insertion order, and rebase high priority
-// updates when preceding updates are skipped, the final result is deterministic
-// regardless of priority. Intermediate state may vary according to system
-// resources, but the final state is always the same.
-
-import type {Fiber, FiberRoot} from './ReactInternalTypes';
-import type {Lanes, Lane} from './ReactFiberLane.new';
-
-import {
- NoLane,
- NoLanes,
- OffscreenLane,
- isSubsetOfLanes,
- mergeLanes,
- removeLanes,
- isTransitionLane,
- intersectLanes,
- markRootEntangled,
-} from './ReactFiberLane.new';
-import {
- enterDisallowedContextReadInDEV,
- exitDisallowedContextReadInDEV,
-} from './ReactFiberNewContext.new';
-import {
- Callback,
- Visibility,
- ShouldCapture,
- DidCapture,
-} from './ReactFiberFlags';
-
-import {debugRenderPhaseSideEffectsForStrictMode} from 'shared/ReactFeatureFlags';
-
-import {StrictLegacyMode} from './ReactTypeOfMode';
-import {
- markSkippedUpdateLanes,
- isUnsafeClassRenderPhaseUpdate,
- getWorkInProgressRootRenderLanes,
-} from './ReactFiberWorkLoop.new';
-import {
- enqueueConcurrentClassUpdate,
- unsafe_markUpdateLaneFromFiberToRoot,
-} from './ReactFiberConcurrentUpdates.new';
-import {setIsStrictModeForDevtools} from './ReactFiberDevToolsHook.new';
-
-import assign from 'shared/assign';
-
-export type Update = {
- // TODO: Temporary field. Will remove this by storing a map of
- // transition -> event time on the root.
- eventTime: number,
- lane: Lane,
-
- tag: 0 | 1 | 2 | 3,
- payload: any,
- callback: (() => mixed) | null,
-
- next: Update | null,
-};
-
-export type SharedQueue = {
- pending: Update | null,
- lanes: Lanes,
- hiddenCallbacks: Array<() => mixed> | null,
-};
-
-export type UpdateQueue = {
- baseState: State,
- firstBaseUpdate: Update | null,
- lastBaseUpdate: Update | null,
- shared: SharedQueue,
- callbacks: Array<() => mixed> | null,
-};
-
-export const UpdateState = 0;
-export const ReplaceState = 1;
-export const ForceUpdate = 2;
-export const CaptureUpdate = 3;
-
-// Global state that is reset at the beginning of calling `processUpdateQueue`.
-// It should only be read right after calling `processUpdateQueue`, via
-// `checkHasForceUpdateAfterProcessing`.
-let hasForceUpdate = false;
-
-let didWarnUpdateInsideUpdate;
-let currentlyProcessingQueue;
-export let resetCurrentlyProcessingQueue: () => void;
-if (__DEV__) {
- didWarnUpdateInsideUpdate = false;
- currentlyProcessingQueue = null;
- resetCurrentlyProcessingQueue = () => {
- currentlyProcessingQueue = null;
- };
-}
-
-export function initializeUpdateQueue(fiber: Fiber): void {
- const queue: UpdateQueue = {
- baseState: fiber.memoizedState,
- firstBaseUpdate: null,
- lastBaseUpdate: null,
- shared: {
- pending: null,
- lanes: NoLanes,
- hiddenCallbacks: null,
- },
- callbacks: null,
- };
- fiber.updateQueue = queue;
-}
-
-export function cloneUpdateQueue(
- current: Fiber,
- workInProgress: Fiber,
-): void {
- // Clone the update queue from current. Unless it's already a clone.
- const queue: UpdateQueue = (workInProgress.updateQueue: any);
- const currentQueue: UpdateQueue = (current.updateQueue: any);
- if (queue === currentQueue) {
- const clone: UpdateQueue = {
- baseState: currentQueue.baseState,
- firstBaseUpdate: currentQueue.firstBaseUpdate,
- lastBaseUpdate: currentQueue.lastBaseUpdate,
- shared: currentQueue.shared,
- callbacks: null,
- };
- workInProgress.updateQueue = clone;
- }
-}
-
-export function createUpdate(eventTime: number, lane: Lane): Update {
- const update: Update = {
- eventTime,
- lane,
-
- tag: UpdateState,
- payload: null,
- callback: null,
-
- next: null,
- };
- return update;
-}
-
-export function enqueueUpdate(
- fiber: Fiber,
- update: Update,
- lane: Lane,
-): FiberRoot | null {
- const updateQueue = fiber.updateQueue;
- if (updateQueue === null) {
- // Only occurs if the fiber has been unmounted.
- return null;
- }
-
- const sharedQueue: SharedQueue = (updateQueue: any).shared;
-
- if (__DEV__) {
- if (
- currentlyProcessingQueue === sharedQueue &&
- !didWarnUpdateInsideUpdate
- ) {
- console.error(
- 'An update (setState, replaceState, or forceUpdate) was scheduled ' +
- 'from inside an update function. Update functions should be pure, ' +
- 'with zero side-effects. Consider using componentDidUpdate or a ' +
- 'callback.',
- );
- didWarnUpdateInsideUpdate = true;
- }
- }
-
- if (isUnsafeClassRenderPhaseUpdate(fiber)) {
- // This is an unsafe render phase update. Add directly to the update
- // queue so we can process it immediately during the current render.
- const pending = sharedQueue.pending;
- if (pending === null) {
- // This is the first update. Create a circular list.
- update.next = update;
- } else {
- update.next = pending.next;
- pending.next = update;
- }
- sharedQueue.pending = update;
-
- // Update the childLanes even though we're most likely already rendering
- // this fiber. This is for backwards compatibility in the case where you
- // update a different component during render phase than the one that is
- // currently renderings (a pattern that is accompanied by a warning).
- return unsafe_markUpdateLaneFromFiberToRoot(fiber, lane);
- } else {
- return enqueueConcurrentClassUpdate(fiber, sharedQueue, update, lane);
- }
-}
-
-export function entangleTransitions(root: FiberRoot, fiber: Fiber, lane: Lane) {
- const updateQueue = fiber.updateQueue;
- if (updateQueue === null) {
- // Only occurs if the fiber has been unmounted.
- return;
- }
-
- const sharedQueue: SharedQueue = (updateQueue: any).shared;
- if (isTransitionLane(lane)) {
- let queueLanes = sharedQueue.lanes;
-
- // If any entangled lanes are no longer pending on the root, then they must
- // have finished. We can remove them from the shared queue, which represents
- // a superset of the actually pending lanes. In some cases we may entangle
- // more than we need to, but that's OK. In fact it's worse if we *don't*
- // entangle when we should.
- queueLanes = intersectLanes(queueLanes, root.pendingLanes);
-
- // Entangle the new transition lane with the other transition lanes.
- const newQueueLanes = mergeLanes(queueLanes, lane);
- sharedQueue.lanes = newQueueLanes;
- // Even if queue.lanes already include lane, we don't know for certain if
- // the lane finished since the last time we entangled it. So we need to
- // entangle it again, just to be sure.
- markRootEntangled(root, newQueueLanes);
- }
-}
-
-export function enqueueCapturedUpdate(
- workInProgress: Fiber,
- capturedUpdate: Update,
-) {
- // Captured updates are updates that are thrown by a child during the render
- // phase. They should be discarded if the render is aborted. Therefore,
- // we should only put them on the work-in-progress queue, not the current one.
- let queue: UpdateQueue = (workInProgress.updateQueue: any);
-
- // Check if the work-in-progress queue is a clone.
- const current = workInProgress.alternate;
- if (current !== null) {
- const currentQueue: UpdateQueue = (current.updateQueue: any);
- if (queue === currentQueue) {
- // The work-in-progress queue is the same as current. This happens when
- // we bail out on a parent fiber that then captures an error thrown by
- // a child. Since we want to append the update only to the work-in
- // -progress queue, we need to clone the updates. We usually clone during
- // processUpdateQueue, but that didn't happen in this case because we
- // skipped over the parent when we bailed out.
- let newFirst = null;
- let newLast = null;
- const firstBaseUpdate = queue.firstBaseUpdate;
- if (firstBaseUpdate !== null) {
- // Loop through the updates and clone them.
- let update: Update = firstBaseUpdate;
- do {
- const clone: Update = {
- eventTime: update.eventTime,
- lane: update.lane,
-
- tag: update.tag,
- payload: update.payload,
- // When this update is rebased, we should not fire its
- // callback again.
- callback: null,
-
- next: null,
- };
- if (newLast === null) {
- newFirst = newLast = clone;
- } else {
- newLast.next = clone;
- newLast = clone;
- }
- // $FlowFixMe[incompatible-type] we bail out when we get a null
- update = update.next;
- } while (update !== null);
-
- // Append the captured update the end of the cloned list.
- if (newLast === null) {
- newFirst = newLast = capturedUpdate;
- } else {
- newLast.next = capturedUpdate;
- newLast = capturedUpdate;
- }
- } else {
- // There are no base updates.
- newFirst = newLast = capturedUpdate;
- }
- queue = {
- baseState: currentQueue.baseState,
- firstBaseUpdate: newFirst,
- lastBaseUpdate: newLast,
- shared: currentQueue.shared,
- callbacks: currentQueue.callbacks,
- };
- workInProgress.updateQueue = queue;
- return;
- }
- }
-
- // Append the update to the end of the list.
- const lastBaseUpdate = queue.lastBaseUpdate;
- if (lastBaseUpdate === null) {
- queue.firstBaseUpdate = capturedUpdate;
- } else {
- lastBaseUpdate.next = capturedUpdate;
- }
- queue.lastBaseUpdate = capturedUpdate;
-}
-
-function getStateFromUpdate(
- workInProgress: Fiber,
- queue: UpdateQueue,
- update: Update,
- prevState: State,
- nextProps: any,
- instance: any,
-): any {
- switch (update.tag) {
- case ReplaceState: {
- const payload = update.payload;
- if (typeof payload === 'function') {
- // Updater function
- if (__DEV__) {
- enterDisallowedContextReadInDEV();
- }
- const nextState = payload.call(instance, prevState, nextProps);
- if (__DEV__) {
- if (
- debugRenderPhaseSideEffectsForStrictMode &&
- workInProgress.mode & StrictLegacyMode
- ) {
- setIsStrictModeForDevtools(true);
- try {
- payload.call(instance, prevState, nextProps);
- } finally {
- setIsStrictModeForDevtools(false);
- }
- }
- exitDisallowedContextReadInDEV();
- }
- return nextState;
- }
- // State object
- return payload;
- }
- case CaptureUpdate: {
- workInProgress.flags =
- (workInProgress.flags & ~ShouldCapture) | DidCapture;
- }
- // Intentional fallthrough
- case UpdateState: {
- const payload = update.payload;
- let partialState;
- if (typeof payload === 'function') {
- // Updater function
- if (__DEV__) {
- enterDisallowedContextReadInDEV();
- }
- partialState = payload.call(instance, prevState, nextProps);
- if (__DEV__) {
- if (
- debugRenderPhaseSideEffectsForStrictMode &&
- workInProgress.mode & StrictLegacyMode
- ) {
- setIsStrictModeForDevtools(true);
- try {
- payload.call(instance, prevState, nextProps);
- } finally {
- setIsStrictModeForDevtools(false);
- }
- }
- exitDisallowedContextReadInDEV();
- }
- } else {
- // Partial state object
- partialState = payload;
- }
- if (partialState === null || partialState === undefined) {
- // Null and undefined are treated as no-ops.
- return prevState;
- }
- // Merge the partial state and the previous state.
- return assign({}, prevState, partialState);
- }
- case ForceUpdate: {
- hasForceUpdate = true;
- return prevState;
- }
- }
- return prevState;
-}
-
-export function processUpdateQueue(
- workInProgress: Fiber,
- props: any,
- instance: any,
- renderLanes: Lanes,
-): void {
- // This is always non-null on a ClassComponent or HostRoot
- const queue: UpdateQueue = (workInProgress.updateQueue: any);
-
- hasForceUpdate = false;
-
- if (__DEV__) {
- // $FlowFixMe[escaped-generic] discovered when updating Flow
- currentlyProcessingQueue = queue.shared;
- }
-
- let firstBaseUpdate = queue.firstBaseUpdate;
- let lastBaseUpdate = queue.lastBaseUpdate;
-
- // Check if there are pending updates. If so, transfer them to the base queue.
- let pendingQueue = queue.shared.pending;
- if (pendingQueue !== null) {
- queue.shared.pending = null;
-
- // The pending queue is circular. Disconnect the pointer between first
- // and last so that it's non-circular.
- const lastPendingUpdate = pendingQueue;
- const firstPendingUpdate = lastPendingUpdate.next;
- lastPendingUpdate.next = null;
- // Append pending updates to base queue
- if (lastBaseUpdate === null) {
- firstBaseUpdate = firstPendingUpdate;
- } else {
- lastBaseUpdate.next = firstPendingUpdate;
- }
- lastBaseUpdate = lastPendingUpdate;
-
- // If there's a current queue, and it's different from the base queue, then
- // we need to transfer the updates to that queue, too. Because the base
- // queue is a singly-linked list with no cycles, we can append to both
- // lists and take advantage of structural sharing.
- // TODO: Pass `current` as argument
- const current = workInProgress.alternate;
- if (current !== null) {
- // This is always non-null on a ClassComponent or HostRoot
- const currentQueue: UpdateQueue = (current.updateQueue: any);
- const currentLastBaseUpdate = currentQueue.lastBaseUpdate;
- if (currentLastBaseUpdate !== lastBaseUpdate) {
- if (currentLastBaseUpdate === null) {
- currentQueue.firstBaseUpdate = firstPendingUpdate;
- } else {
- currentLastBaseUpdate.next = firstPendingUpdate;
- }
- currentQueue.lastBaseUpdate = lastPendingUpdate;
- }
- }
- }
-
- // These values may change as we process the queue.
- if (firstBaseUpdate !== null) {
- // Iterate through the list of updates to compute the result.
- let newState = queue.baseState;
- // TODO: Don't need to accumulate this. Instead, we can remove renderLanes
- // from the original lanes.
- let newLanes = NoLanes;
-
- let newBaseState = null;
- let newFirstBaseUpdate = null;
- let newLastBaseUpdate = null;
-
- let update: Update = firstBaseUpdate;
- do {
- // TODO: Don't need this field anymore
- const updateEventTime = update.eventTime;
-
- // An extra OffscreenLane bit is added to updates that were made to
- // a hidden tree, so that we can distinguish them from updates that were
- // already there when the tree was hidden.
- const updateLane = removeLanes(update.lane, OffscreenLane);
- const isHiddenUpdate = updateLane !== update.lane;
-
- // Check if this update was made while the tree was hidden. If so, then
- // it's not a "base" update and we should disregard the extra base lanes
- // that were added to renderLanes when we entered the Offscreen tree.
- const shouldSkipUpdate = isHiddenUpdate
- ? !isSubsetOfLanes(getWorkInProgressRootRenderLanes(), updateLane)
- : !isSubsetOfLanes(renderLanes, updateLane);
-
- if (shouldSkipUpdate) {
- // Priority is insufficient. Skip this update. If this is the first
- // skipped update, the previous update/state is the new base
- // update/state.
- const clone: Update = {
- eventTime: updateEventTime,
- lane: updateLane,
-
- tag: update.tag,
- payload: update.payload,
- callback: update.callback,
-
- next: null,
- };
- if (newLastBaseUpdate === null) {
- newFirstBaseUpdate = newLastBaseUpdate = clone;
- newBaseState = newState;
- } else {
- newLastBaseUpdate = newLastBaseUpdate.next = clone;
- }
- // Update the remaining priority in the queue.
- newLanes = mergeLanes(newLanes, updateLane);
- } else {
- // This update does have sufficient priority.
-
- if (newLastBaseUpdate !== null) {
- const clone: Update = {
- eventTime: updateEventTime,
- // This update is going to be committed so we never want uncommit
- // it. Using NoLane works because 0 is a subset of all bitmasks, so
- // this will never be skipped by the check above.
- lane: NoLane,
-
- tag: update.tag,
- payload: update.payload,
-
- // When this update is rebased, we should not fire its
- // callback again.
- callback: null,
-
- next: null,
- };
- newLastBaseUpdate = newLastBaseUpdate.next = clone;
- }
-
- // Process this update.
- newState = getStateFromUpdate(
- workInProgress,
- queue,
- update,
- newState,
- props,
- instance,
- );
- const callback = update.callback;
- if (callback !== null) {
- workInProgress.flags |= Callback;
- if (isHiddenUpdate) {
- workInProgress.flags |= Visibility;
- }
- const callbacks = queue.callbacks;
- if (callbacks === null) {
- queue.callbacks = [callback];
- } else {
- callbacks.push(callback);
- }
- }
- }
- // $FlowFixMe[incompatible-type] we bail out when we get a null
- update = update.next;
- if (update === null) {
- pendingQueue = queue.shared.pending;
- if (pendingQueue === null) {
- break;
- } else {
- // An update was scheduled from inside a reducer. Add the new
- // pending updates to the end of the list and keep processing.
- const lastPendingUpdate = pendingQueue;
- // Intentionally unsound. Pending updates form a circular list, but we
- // unravel them when transferring them to the base queue.
- const firstPendingUpdate = ((lastPendingUpdate.next: any): Update);
- lastPendingUpdate.next = null;
- update = firstPendingUpdate;
- queue.lastBaseUpdate = lastPendingUpdate;
- queue.shared.pending = null;
- }
- }
- } while (true);
-
- if (newLastBaseUpdate === null) {
- newBaseState = newState;
- }
-
- queue.baseState = ((newBaseState: any): State);
- queue.firstBaseUpdate = newFirstBaseUpdate;
- queue.lastBaseUpdate = newLastBaseUpdate;
-
- if (firstBaseUpdate === null) {
- // `queue.lanes` is used for entangling transitions. We can set it back to
- // zero once the queue is empty.
- queue.shared.lanes = NoLanes;
- }
-
- // Set the remaining expiration time to be whatever is remaining in the queue.
- // This should be fine because the only two other things that contribute to
- // expiration time are props and context. We're already in the middle of the
- // begin phase by the time we start processing the queue, so we've already
- // dealt with the props. Context in components that specify
- // shouldComponentUpdate is tricky; but we'll have to account for
- // that regardless.
- markSkippedUpdateLanes(newLanes);
- workInProgress.lanes = newLanes;
- workInProgress.memoizedState = newState;
- }
-
- if (__DEV__) {
- currentlyProcessingQueue = null;
- }
-}
-
-function callCallback(callback, context) {
- if (typeof callback !== 'function') {
- throw new Error(
- 'Invalid argument passed as callback. Expected a function. Instead ' +
- `received: ${callback}`,
- );
- }
-
- callback.call(context);
-}
-
-export function resetHasForceUpdateBeforeProcessing() {
- hasForceUpdate = false;
-}
-
-export function checkHasForceUpdateAfterProcessing(): boolean {
- return hasForceUpdate;
-}
-
-export function deferHiddenCallbacks(
- updateQueue: UpdateQueue,
-): void {
- // When an update finishes on a hidden component, its callback should not
- // be fired until/unless the component is made visible again. Stash the
- // callback on the shared queue object so it can be fired later.
- const newHiddenCallbacks = updateQueue.callbacks;
- if (newHiddenCallbacks !== null) {
- const existingHiddenCallbacks = updateQueue.shared.hiddenCallbacks;
- if (existingHiddenCallbacks === null) {
- updateQueue.shared.hiddenCallbacks = newHiddenCallbacks;
- } else {
- updateQueue.shared.hiddenCallbacks = existingHiddenCallbacks.concat(
- newHiddenCallbacks,
- );
- }
- }
-}
-
-export function commitHiddenCallbacks(
- updateQueue: UpdateQueue,
- context: any,
-): void {
- // This component is switching from hidden -> visible. Commit any callbacks
- // that were previously deferred.
- const hiddenCallbacks = updateQueue.shared.hiddenCallbacks;
- if (hiddenCallbacks !== null) {
- updateQueue.shared.hiddenCallbacks = null;
- for (let i = 0; i < hiddenCallbacks.length; i++) {
- const callback = hiddenCallbacks[i];
- callCallback(callback, context);
- }
- }
-}
-
-export function commitCallbacks(
- updateQueue: UpdateQueue,
- context: any,
-): void {
- const callbacks = updateQueue.callbacks;
- if (callbacks !== null) {
- updateQueue.callbacks = null;
- for (let i = 0; i < callbacks.length; i++) {
- const callback = callbacks[i];
- callCallback(callback, context);
- }
- }
-}
diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.new.js b/packages/react-reconciler/src/ReactFiberCommitWork.new.js
deleted file mode 100644
index ae9337a01647f..0000000000000
--- a/packages/react-reconciler/src/ReactFiberCommitWork.new.js
+++ /dev/null
@@ -1,4450 +0,0 @@
-/**
- * Copyright (c) Meta Platforms, Inc. and affiliates.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- *
- * @flow
- */
-
-import type {
- Instance,
- TextInstance,
- SuspenseInstance,
- Container,
- ChildSet,
- UpdatePayload,
-} from './ReactFiberHostConfig';
-import type {Fiber, FiberRoot} from './ReactInternalTypes';
-import type {Lanes} from './ReactFiberLane.new';
-import type {SuspenseState} from './ReactFiberSuspenseComponent.new';
-import type {UpdateQueue} from './ReactFiberClassUpdateQueue.new';
-import type {FunctionComponentUpdateQueue} from './ReactFiberHooks.new';
-import type {Wakeable} from 'shared/ReactTypes';
-import {isOffscreenManual} from './ReactFiberOffscreenComponent';
-import type {
- OffscreenState,
- OffscreenInstance,
- OffscreenQueue,
- OffscreenProps,
-} from './ReactFiberOffscreenComponent';
-import type {HookFlags} from './ReactHookEffectTags';
-import type {Cache} from './ReactFiberCacheComponent.new';
-import type {RootState} from './ReactFiberRoot.new';
-import type {
- Transition,
- TracingMarkerInstance,
- TransitionAbort,
-} from './ReactFiberTracingMarkerComponent.new';
-
-import {
- enableCreateEventHandleAPI,
- enableProfilerTimer,
- enableProfilerCommitHooks,
- enableProfilerNestedUpdatePhase,
- enableSchedulingProfiler,
- enableSuspenseCallback,
- enableScopeAPI,
- deletedTreeCleanUpLevel,
- enableUpdaterTracking,
- enableCache,
- enableTransitionTracing,
- enableUseEventHook,
- enableFloat,
- enableLegacyHidden,
- enableHostSingletons,
-} from 'shared/ReactFeatureFlags';
-import {
- FunctionComponent,
- ForwardRef,
- ClassComponent,
- HostRoot,
- HostComponent,
- HostResource,
- HostSingleton,
- HostText,
- HostPortal,
- Profiler,
- SuspenseComponent,
- DehydratedFragment,
- IncompleteClassComponent,
- MemoComponent,
- SimpleMemoComponent,
- SuspenseListComponent,
- ScopeComponent,
- OffscreenComponent,
- LegacyHiddenComponent,
- CacheComponent,
- TracingMarkerComponent,
-} from './ReactWorkTags';
-import {
- NoFlags,
- ContentReset,
- Placement,
- ChildDeletion,
- Snapshot,
- Update,
- Callback,
- Ref,
- Hydrating,
- Passive,
- BeforeMutationMask,
- MutationMask,
- LayoutMask,
- PassiveMask,
- Visibility,
-} from './ReactFiberFlags';
-import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
-import {
- resetCurrentFiber as resetCurrentDebugFiberInDEV,
- setCurrentFiber as setCurrentDebugFiberInDEV,
- getCurrentFiber as getCurrentDebugFiberInDEV,
-} from './ReactCurrentFiber';
-import {resolveDefaultProps} from './ReactFiberLazyComponent.new';
-import {
- isCurrentUpdateNested,
- getCommitTime,
- recordLayoutEffectDuration,
- startLayoutEffectTimer,
- recordPassiveEffectDuration,
- startPassiveEffectTimer,
-} from './ReactProfilerTimer.new';
-import {ConcurrentMode, NoMode, ProfileMode} from './ReactTypeOfMode';
-import {
- deferHiddenCallbacks,
- commitHiddenCallbacks,
- commitCallbacks,
-} from './ReactFiberClassUpdateQueue.new';
-import {
- getPublicInstance,
- supportsMutation,
- supportsPersistence,
- supportsHydration,
- supportsResources,
- supportsSingletons,
- commitMount,
- commitUpdate,
- resetTextContent,
- commitTextUpdate,
- appendChild,
- appendChildToContainer,
- insertBefore,
- insertInContainerBefore,
- removeChild,
- removeChildFromContainer,
- clearSuspenseBoundary,
- clearSuspenseBoundaryFromContainer,
- replaceContainerChildren,
- createContainerChildSet,
- hideInstance,
- hideTextInstance,
- unhideInstance,
- unhideTextInstance,
- commitHydratedContainer,
- commitHydratedSuspenseInstance,
- clearContainer,
- prepareScopeUpdate,
- prepareForCommit,
- beforeActiveInstanceBlur,
- detachDeletedInstance,
- acquireResource,
- releaseResource,
- clearSingleton,
- acquireSingletonInstance,
- releaseSingletonInstance,
- scheduleMicrotask,
-} from './ReactFiberHostConfig';
-import {
- captureCommitPhaseError,
- resolveRetryWakeable,
- markCommitTimeOfFallback,
- enqueuePendingPassiveProfilerEffect,
- restorePendingUpdaters,
- addTransitionStartCallbackToPendingTransition,
- addTransitionProgressCallbackToPendingTransition,
- addTransitionCompleteCallbackToPendingTransition,
- addMarkerProgressCallbackToPendingTransition,
- addMarkerIncompleteCallbackToPendingTransition,
- addMarkerCompleteCallbackToPendingTransition,
- setIsRunningInsertionEffect,
- getExecutionContext,
- CommitContext,
- RenderContext,
- NoContext,
-} from './ReactFiberWorkLoop.new';
-import {
- NoFlags as NoHookEffect,
- HasEffect as HookHasEffect,
- Layout as HookLayout,
- Insertion as HookInsertion,
- Passive as HookPassive,
-} from './ReactHookEffectTags';
-import {didWarnAboutReassigningProps} from './ReactFiberBeginWork.new';
-import {doesFiberContain} from './ReactFiberTreeReflection';
-import {invokeGuardedCallback, clearCaughtError} from 'shared/ReactErrorUtils';
-import {
- isDevToolsPresent,
- markComponentPassiveEffectMountStarted,
- markComponentPassiveEffectMountStopped,
- markComponentPassiveEffectUnmountStarted,
- markComponentPassiveEffectUnmountStopped,
- markComponentLayoutEffectMountStarted,
- markComponentLayoutEffectMountStopped,
- markComponentLayoutEffectUnmountStarted,
- markComponentLayoutEffectUnmountStopped,
- onCommitUnmount,
-} from './ReactFiberDevToolsHook.new';
-import {releaseCache, retainCache} from './ReactFiberCacheComponent.new';
-import {clearTransitionsForLanes} from './ReactFiberLane.new';
-import {
- OffscreenVisible,
- OffscreenDetached,
- OffscreenPassiveEffectsConnected,
-} from './ReactFiberOffscreenComponent';
-import {
- TransitionRoot,
- TransitionTracingMarker,
-} from './ReactFiberTracingMarkerComponent.new';
-
-let didWarnAboutUndefinedSnapshotBeforeUpdate: Set | null = null;
-if (__DEV__) {
- didWarnAboutUndefinedSnapshotBeforeUpdate = new Set();
-}
-
-// Used during the commit phase to track the state of the Offscreen component stack.
-// Allows us to avoid traversing the return path to find the nearest Offscreen ancestor.
-let offscreenSubtreeIsHidden: boolean = false;
-let offscreenSubtreeWasHidden: boolean = false;
-
-const PossiblyWeakSet = typeof WeakSet === 'function' ? WeakSet : Set;
-
-let nextEffect: Fiber | null = null;
-
-// Used for Profiling builds to track updaters.
-let inProgressLanes: Lanes | null = null;
-let inProgressRoot: FiberRoot | null = null;
-
-function shouldProfile(current: Fiber): boolean {
- return (
- enableProfilerTimer &&
- enableProfilerCommitHooks &&
- (current.mode & ProfileMode) !== NoMode &&
- (getExecutionContext() & CommitContext) !== NoContext
- );
-}
-
-export function reportUncaughtErrorInDEV(error: mixed) {
- // Wrapping each small part of the commit phase into a guarded
- // callback is a bit too slow (https://github.com/facebook/react/pull/21666).
- // But we rely on it to surface errors to DEV tools like overlays
- // (https://github.com/facebook/react/issues/21712).
- // As a compromise, rethrow only caught errors in a guard.
- if (__DEV__) {
- invokeGuardedCallback(null, () => {
- throw error;
- });
- clearCaughtError();
- }
-}
-
-const callComponentWillUnmountWithTimer = function(current, instance) {
- instance.props = current.memoizedProps;
- instance.state = current.memoizedState;
- if (shouldProfile(current)) {
- try {
- startLayoutEffectTimer();
- instance.componentWillUnmount();
- } finally {
- recordLayoutEffectDuration(current);
- }
- } else {
- instance.componentWillUnmount();
- }
-};
-
-// Capture errors so they don't interrupt unmounting.
-function safelyCallComponentWillUnmount(
- current: Fiber,
- nearestMountedAncestor: Fiber | null,
- instance: any,
-) {
- try {
- callComponentWillUnmountWithTimer(current, instance);
- } catch (error) {
- captureCommitPhaseError(current, nearestMountedAncestor, error);
- }
-}
-
-// Capture errors so they don't interrupt mounting.
-function safelyAttachRef(current: Fiber, nearestMountedAncestor: Fiber | null) {
- try {
- commitAttachRef(current);
- } catch (error) {
- captureCommitPhaseError(current, nearestMountedAncestor, error);
- }
-}
-
-function safelyDetachRef(current: Fiber, nearestMountedAncestor: Fiber | null) {
- const ref = current.ref;
- const refCleanup = current.refCleanup;
-
- if (ref !== null) {
- if (typeof refCleanup === 'function') {
- try {
- if (shouldProfile(current)) {
- try {
- startLayoutEffectTimer();
- refCleanup();
- } finally {
- recordLayoutEffectDuration(current);
- }
- } else {
- refCleanup();
- }
- } catch (error) {
- captureCommitPhaseError(current, nearestMountedAncestor, error);
- } finally {
- // `refCleanup` has been called. Nullify all references to it to prevent double invocation.
- current.refCleanup = null;
- const finishedWork = current.alternate;
- if (finishedWork != null) {
- finishedWork.refCleanup = null;
- }
- }
- } else if (typeof ref === 'function') {
- let retVal;
- try {
- if (shouldProfile(current)) {
- try {
- startLayoutEffectTimer();
- retVal = ref(null);
- } finally {
- recordLayoutEffectDuration(current);
- }
- } else {
- retVal = ref(null);
- }
- } catch (error) {
- captureCommitPhaseError(current, nearestMountedAncestor, error);
- }
- if (__DEV__) {
- if (typeof retVal === 'function') {
- console.error(
- 'Unexpected return value from a callback ref in %s. ' +
- 'A callback ref should not return a function.',
- getComponentNameFromFiber(current),
- );
- }
- }
- } else {
- // $FlowFixMe unable to narrow type to RefObject
- ref.current = null;
- }
- }
-}
-
-function safelyCallDestroy(
- current: Fiber,
- nearestMountedAncestor: Fiber | null,
- destroy: () => void,
-) {
- try {
- destroy();
- } catch (error) {
- captureCommitPhaseError(current, nearestMountedAncestor, error);
- }
-}
-
-let focusedInstanceHandle: null | Fiber = null;
-let shouldFireAfterActiveInstanceBlur: boolean = false;
-
-export function commitBeforeMutationEffects(
- root: FiberRoot,
- firstChild: Fiber,
-): boolean {
- focusedInstanceHandle = prepareForCommit(root.containerInfo);
-
- nextEffect = firstChild;
- commitBeforeMutationEffects_begin();
-
- // We no longer need to track the active instance fiber
- const shouldFire = shouldFireAfterActiveInstanceBlur;
- shouldFireAfterActiveInstanceBlur = false;
- focusedInstanceHandle = null;
-
- return shouldFire;
-}
-
-function commitBeforeMutationEffects_begin() {
- while (nextEffect !== null) {
- const fiber = nextEffect;
-
- // This phase is only used for beforeActiveInstanceBlur.
- // Let's skip the whole loop if it's off.
- if (enableCreateEventHandleAPI) {
- // TODO: Should wrap this in flags check, too, as optimization
- const deletions = fiber.deletions;
- if (deletions !== null) {
- for (let i = 0; i < deletions.length; i++) {
- const deletion = deletions[i];
- commitBeforeMutationEffectsDeletion(deletion);
- }
- }
- }
-
- const child = fiber.child;
- if (
- (fiber.subtreeFlags & BeforeMutationMask) !== NoFlags &&
- child !== null
- ) {
- child.return = fiber;
- nextEffect = child;
- } else {
- commitBeforeMutationEffects_complete();
- }
- }
-}
-
-function commitBeforeMutationEffects_complete() {
- while (nextEffect !== null) {
- const fiber = nextEffect;
- setCurrentDebugFiberInDEV(fiber);
- try {
- commitBeforeMutationEffectsOnFiber(fiber);
- } catch (error) {
- captureCommitPhaseError(fiber, fiber.return, error);
- }
- resetCurrentDebugFiberInDEV();
-
- const sibling = fiber.sibling;
- if (sibling !== null) {
- sibling.return = fiber.return;
- nextEffect = sibling;
- return;
- }
-
- nextEffect = fiber.return;
- }
-}
-
-function commitBeforeMutationEffectsOnFiber(finishedWork: Fiber) {
- const current = finishedWork.alternate;
- const flags = finishedWork.flags;
-
- if (enableCreateEventHandleAPI) {
- if (!shouldFireAfterActiveInstanceBlur && focusedInstanceHandle !== null) {
- // Check to see if the focused element was inside of a hidden (Suspense) subtree.
- // TODO: Move this out of the hot path using a dedicated effect tag.
- if (
- finishedWork.tag === SuspenseComponent &&
- isSuspenseBoundaryBeingHidden(current, finishedWork) &&
- // $FlowFixMe[incompatible-call] found when upgrading Flow
- doesFiberContain(finishedWork, focusedInstanceHandle)
- ) {
- shouldFireAfterActiveInstanceBlur = true;
- beforeActiveInstanceBlur(finishedWork);
- }
- }
- }
-
- if ((flags & Snapshot) !== NoFlags) {
- setCurrentDebugFiberInDEV(finishedWork);
- }
-
- switch (finishedWork.tag) {
- case FunctionComponent: {
- if (enableUseEventHook) {
- if ((flags & Update) !== NoFlags) {
- commitUseEventMount(finishedWork);
- }
- }
- break;
- }
- case ForwardRef:
- case SimpleMemoComponent: {
- break;
- }
- case ClassComponent: {
- if ((flags & Snapshot) !== NoFlags) {
- if (current !== null) {
- const prevProps = current.memoizedProps;
- const prevState = current.memoizedState;
- const instance = finishedWork.stateNode;
- // We could update instance props and state here,
- // but instead we rely on them being set during last render.
- // TODO: revisit this when we implement resuming.
- if (__DEV__) {
- if (
- finishedWork.type === finishedWork.elementType &&
- !didWarnAboutReassigningProps
- ) {
- if (instance.props !== finishedWork.memoizedProps) {
- console.error(
- 'Expected %s props to match memoized props before ' +
- 'getSnapshotBeforeUpdate. ' +
- 'This might either be because of a bug in React, or because ' +
- 'a component reassigns its own `this.props`. ' +
- 'Please file an issue.',
- getComponentNameFromFiber(finishedWork) || 'instance',
- );
- }
- if (instance.state !== finishedWork.memoizedState) {
- console.error(
- 'Expected %s state to match memoized state before ' +
- 'getSnapshotBeforeUpdate. ' +
- 'This might either be because of a bug in React, or because ' +
- 'a component reassigns its own `this.state`. ' +
- 'Please file an issue.',
- getComponentNameFromFiber(finishedWork) || 'instance',
- );
- }
- }
- }
- const snapshot = instance.getSnapshotBeforeUpdate(
- finishedWork.elementType === finishedWork.type
- ? prevProps
- : resolveDefaultProps(finishedWork.type, prevProps),
- prevState,
- );
- if (__DEV__) {
- const didWarnSet = ((didWarnAboutUndefinedSnapshotBeforeUpdate: any): Set);
- if (snapshot === undefined && !didWarnSet.has(finishedWork.type)) {
- didWarnSet.add(finishedWork.type);
- console.error(
- '%s.getSnapshotBeforeUpdate(): A snapshot value (or null) ' +
- 'must be returned. You have returned undefined.',
- getComponentNameFromFiber(finishedWork),
- );
- }
- }
- instance.__reactInternalSnapshotBeforeUpdate = snapshot;
- }
- }
- break;
- }
- case HostRoot: {
- if ((flags & Snapshot) !== NoFlags) {
- if (supportsMutation) {
- const root = finishedWork.stateNode;
- clearContainer(root.containerInfo);
- }
- }
- break;
- }
- case HostComponent:
- case HostResource:
- case HostSingleton:
- case HostText:
- case HostPortal:
- case IncompleteClassComponent:
- // Nothing to do for these component types
- break;
- default: {
- if ((flags & Snapshot) !== NoFlags) {
- throw new Error(
- 'This unit of work tag should not have side-effects. This error is ' +
- 'likely caused by a bug in React. Please file an issue.',
- );
- }
- }
- }
-
- if ((flags & Snapshot) !== NoFlags) {
- resetCurrentDebugFiberInDEV();
- }
-}
-
-function commitBeforeMutationEffectsDeletion(deletion: Fiber) {
- if (enableCreateEventHandleAPI) {
- // TODO (effects) It would be nice to avoid calling doesFiberContain()
- // Maybe we can repurpose one of the subtreeFlags positions for this instead?
- // Use it to store which part of the tree the focused instance is in?
- // This assumes we can safely determine that instance during the "render" phase.
- if (doesFiberContain(deletion, ((focusedInstanceHandle: any): Fiber))) {
- shouldFireAfterActiveInstanceBlur = true;
- beforeActiveInstanceBlur(deletion);
- }
- }
-}
-
-function commitHookEffectListUnmount(
- flags: HookFlags,
- finishedWork: Fiber,
- nearestMountedAncestor: Fiber | null,
-) {
- const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
- const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
- if (lastEffect !== null) {
- const firstEffect = lastEffect.next;
- let effect = firstEffect;
- do {
- if ((effect.tag & flags) === flags) {
- // Unmount
- const destroy = effect.destroy;
- effect.destroy = undefined;
- if (destroy !== undefined) {
- if (enableSchedulingProfiler) {
- if ((flags & HookPassive) !== NoHookEffect) {
- markComponentPassiveEffectUnmountStarted(finishedWork);
- } else if ((flags & HookLayout) !== NoHookEffect) {
- markComponentLayoutEffectUnmountStarted(finishedWork);
- }
- }
-
- if (__DEV__) {
- if ((flags & HookInsertion) !== NoHookEffect) {
- setIsRunningInsertionEffect(true);
- }
- }
- safelyCallDestroy(finishedWork, nearestMountedAncestor, destroy);
- if (__DEV__) {
- if ((flags & HookInsertion) !== NoHookEffect) {
- setIsRunningInsertionEffect(false);
- }
- }
-
- if (enableSchedulingProfiler) {
- if ((flags & HookPassive) !== NoHookEffect) {
- markComponentPassiveEffectUnmountStopped();
- } else if ((flags & HookLayout) !== NoHookEffect) {
- markComponentLayoutEffectUnmountStopped();
- }
- }
- }
- }
- effect = effect.next;
- } while (effect !== firstEffect);
- }
-}
-
-function commitHookEffectListMount(flags: HookFlags, finishedWork: Fiber) {
- const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
- const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
- if (lastEffect !== null) {
- const firstEffect = lastEffect.next;
- let effect = firstEffect;
- do {
- if ((effect.tag & flags) === flags) {
- if (enableSchedulingProfiler) {
- if ((flags & HookPassive) !== NoHookEffect) {
- markComponentPassiveEffectMountStarted(finishedWork);
- } else if ((flags & HookLayout) !== NoHookEffect) {
- markComponentLayoutEffectMountStarted(finishedWork);
- }
- }
-
- // Mount
- const create = effect.create;
- if (__DEV__) {
- if ((flags & HookInsertion) !== NoHookEffect) {
- setIsRunningInsertionEffect(true);
- }
- }
- effect.destroy = create();
- if (__DEV__) {
- if ((flags & HookInsertion) !== NoHookEffect) {
- setIsRunningInsertionEffect(false);
- }
- }
-
- if (enableSchedulingProfiler) {
- if ((flags & HookPassive) !== NoHookEffect) {
- markComponentPassiveEffectMountStopped();
- } else if ((flags & HookLayout) !== NoHookEffect) {
- markComponentLayoutEffectMountStopped();
- }
- }
-
- if (__DEV__) {
- const destroy = effect.destroy;
- if (destroy !== undefined && typeof destroy !== 'function') {
- let hookName;
- if ((effect.tag & HookLayout) !== NoFlags) {
- hookName = 'useLayoutEffect';
- } else if ((effect.tag & HookInsertion) !== NoFlags) {
- hookName = 'useInsertionEffect';
- } else {
- hookName = 'useEffect';
- }
- let addendum;
- if (destroy === null) {
- addendum =
- ' You returned null. If your effect does not require clean ' +
- 'up, return undefined (or nothing).';
- } else if (typeof destroy.then === 'function') {
- addendum =
- '\n\nIt looks like you wrote ' +
- hookName +
- '(async () => ...) or returned a Promise. ' +
- 'Instead, write the async function inside your effect ' +
- 'and call it immediately:\n\n' +
- hookName +
- '(() => {\n' +
- ' async function fetchData() {\n' +
- ' // You can await here\n' +
- ' const response = await MyAPI.getData(someId);\n' +
- ' // ...\n' +
- ' }\n' +
- ' fetchData();\n' +
- `}, [someId]); // Or [] if effect doesn't need props or state\n\n` +
- 'Learn more about data fetching with Hooks: https://reactjs.org/link/hooks-data-fetching';
- } else {
- addendum = ' You returned: ' + destroy;
- }
- console.error(
- '%s must not return anything besides a function, ' +
- 'which is used for clean-up.%s',
- hookName,
- addendum,
- );
- }
- }
- }
- effect = effect.next;
- } while (effect !== firstEffect);
- }
-}
-
-function commitUseEventMount(finishedWork: Fiber) {
- const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
- const eventPayloads = updateQueue !== null ? updateQueue.events : null;
- if (eventPayloads !== null) {
- for (let ii = 0; ii < eventPayloads.length; ii++) {
- const {ref, nextImpl} = eventPayloads[ii];
- ref.impl = nextImpl;
- }
- }
-}
-
-export function commitPassiveEffectDurations(
- finishedRoot: FiberRoot,
- finishedWork: Fiber,
-): void {
- if (
- enableProfilerTimer &&
- enableProfilerCommitHooks &&
- getExecutionContext() & CommitContext
- ) {
- // Only Profilers with work in their subtree will have an Update effect scheduled.
- if ((finishedWork.flags & Update) !== NoFlags) {
- switch (finishedWork.tag) {
- case Profiler: {
- const {passiveEffectDuration} = finishedWork.stateNode;
- const {id, onPostCommit} = finishedWork.memoizedProps;
-
- // This value will still reflect the previous commit phase.
- // It does not get reset until the start of the next commit phase.
- const commitTime = getCommitTime();
-
- let phase = finishedWork.alternate === null ? 'mount' : 'update';
- if (enableProfilerNestedUpdatePhase) {
- if (isCurrentUpdateNested()) {
- phase = 'nested-update';
- }
- }
-
- if (typeof onPostCommit === 'function') {
- onPostCommit(id, phase, passiveEffectDuration, commitTime);
- }
-
- // Bubble times to the next nearest ancestor Profiler.
- // After we process that Profiler, we'll bubble further up.
- let parentFiber = finishedWork.return;
- outer: while (parentFiber !== null) {
- switch (parentFiber.tag) {
- case HostRoot:
- const root = parentFiber.stateNode;
- root.passiveEffectDuration += passiveEffectDuration;
- break outer;
- case Profiler:
- const parentStateNode = parentFiber.stateNode;
- parentStateNode.passiveEffectDuration += passiveEffectDuration;
- break outer;
- }
- parentFiber = parentFiber.return;
- }
- break;
- }
- default:
- break;
- }
- }
- }
-}
-
-function commitHookLayoutEffects(finishedWork: Fiber, hookFlags: HookFlags) {
- // At this point layout effects have already been destroyed (during mutation phase).
- // This is done to prevent sibling component effects from interfering with each other,
- // e.g. a destroy function in one component should never override a ref set
- // by a create function in another component during the same commit.
- if (shouldProfile(finishedWork)) {
- try {
- startLayoutEffectTimer();
- commitHookEffectListMount(hookFlags, finishedWork);
- } catch (error) {
- captureCommitPhaseError(finishedWork, finishedWork.return, error);
- }
- recordLayoutEffectDuration(finishedWork);
- } else {
- try {
- commitHookEffectListMount(hookFlags, finishedWork);
- } catch (error) {
- captureCommitPhaseError(finishedWork, finishedWork.return, error);
- }
- }
-}
-
-function commitClassLayoutLifecycles(
- finishedWork: Fiber,
- current: Fiber | null,
-) {
- const instance = finishedWork.stateNode;
- if (current === null) {
- // We could update instance props and state here,
- // but instead we rely on them being set during last render.
- // TODO: revisit this when we implement resuming.
- if (__DEV__) {
- if (
- finishedWork.type === finishedWork.elementType &&
- !didWarnAboutReassigningProps
- ) {
- if (instance.props !== finishedWork.memoizedProps) {
- console.error(
- 'Expected %s props to match memoized props before ' +
- 'componentDidMount. ' +
- 'This might either be because of a bug in React, or because ' +
- 'a component reassigns its own `this.props`. ' +
- 'Please file an issue.',
- getComponentNameFromFiber(finishedWork) || 'instance',
- );
- }
- if (instance.state !== finishedWork.memoizedState) {
- console.error(
- 'Expected %s state to match memoized state before ' +
- 'componentDidMount. ' +
- 'This might either be because of a bug in React, or because ' +
- 'a component reassigns its own `this.state`. ' +
- 'Please file an issue.',
- getComponentNameFromFiber(finishedWork) || 'instance',
- );
- }
- }
- }
- if (shouldProfile(finishedWork)) {
- try {
- startLayoutEffectTimer();
- instance.componentDidMount();
- } catch (error) {
- captureCommitPhaseError(finishedWork, finishedWork.return, error);
- }
- recordLayoutEffectDuration(finishedWork);
- } else {
- try {
- instance.componentDidMount();
- } catch (error) {
- captureCommitPhaseError(finishedWork, finishedWork.return, error);
- }
- }
- } else {
- const prevProps =
- finishedWork.elementType === finishedWork.type
- ? current.memoizedProps
- : resolveDefaultProps(finishedWork.type, current.memoizedProps);
- const prevState = current.memoizedState;
- // We could update instance props and state here,
- // but instead we rely on them being set during last render.
- // TODO: revisit this when we implement resuming.
- if (__DEV__) {
- if (
- finishedWork.type === finishedWork.elementType &&
- !didWarnAboutReassigningProps
- ) {
- if (instance.props !== finishedWork.memoizedProps) {
- console.error(
- 'Expected %s props to match memoized props before ' +
- 'componentDidUpdate. ' +
- 'This might either be because of a bug in React, or because ' +
- 'a component reassigns its own `this.props`. ' +
- 'Please file an issue.',
- getComponentNameFromFiber(finishedWork) || 'instance',
- );
- }
- if (instance.state !== finishedWork.memoizedState) {
- console.error(
- 'Expected %s state to match memoized state before ' +
- 'componentDidUpdate. ' +
- 'This might either be because of a bug in React, or because ' +
- 'a component reassigns its own `this.state`. ' +
- 'Please file an issue.',
- getComponentNameFromFiber(finishedWork) || 'instance',
- );
- }
- }
- }
- if (shouldProfile(finishedWork)) {
- try {
- startLayoutEffectTimer();
- instance.componentDidUpdate(
- prevProps,
- prevState,
- instance.__reactInternalSnapshotBeforeUpdate,
- );
- } catch (error) {
- captureCommitPhaseError(finishedWork, finishedWork.return, error);
- }
- recordLayoutEffectDuration(finishedWork);
- } else {
- try {
- instance.componentDidUpdate(
- prevProps,
- prevState,
- instance.__reactInternalSnapshotBeforeUpdate,
- );
- } catch (error) {
- captureCommitPhaseError(finishedWork, finishedWork.return, error);
- }
- }
- }
-}
-
-function commitClassCallbacks(finishedWork: Fiber) {
- // TODO: I think this is now always non-null by the time it reaches the
- // commit phase. Consider removing the type check.
- const updateQueue: UpdateQueue | null = (finishedWork.updateQueue: any);
- if (updateQueue !== null) {
- const instance = finishedWork.stateNode;
- if (__DEV__) {
- if (
- finishedWork.type === finishedWork.elementType &&
- !didWarnAboutReassigningProps
- ) {
- if (instance.props !== finishedWork.memoizedProps) {
- console.error(
- 'Expected %s props to match memoized props before ' +
- 'processing the update queue. ' +
- 'This might either be because of a bug in React, or because ' +
- 'a component reassigns its own `this.props`. ' +
- 'Please file an issue.',
- getComponentNameFromFiber(finishedWork) || 'instance',
- );
- }
- if (instance.state !== finishedWork.memoizedState) {
- console.error(
- 'Expected %s state to match memoized state before ' +
- 'processing the update queue. ' +
- 'This might either be because of a bug in React, or because ' +
- 'a component reassigns its own `this.state`. ' +
- 'Please file an issue.',
- getComponentNameFromFiber(finishedWork) || 'instance',
- );
- }
- }
- }
- // We could update instance props and state here,
- // but instead we rely on them being set during last render.
- // TODO: revisit this when we implement resuming.
- try {
- commitCallbacks(updateQueue, instance);
- } catch (error) {
- captureCommitPhaseError(finishedWork, finishedWork.return, error);
- }
- }
-}
-
-function commitHostComponentMount(finishedWork: Fiber) {
- const type = finishedWork.type;
- const props = finishedWork.memoizedProps;
- const instance: Instance = finishedWork.stateNode;
- try {
- commitMount(instance, type, props, finishedWork);
- } catch (error) {
- captureCommitPhaseError(finishedWork, finishedWork.return, error);
- }
-}
-
-function commitProfilerUpdate(finishedWork: Fiber, current: Fiber | null) {
- if (enableProfilerTimer && getExecutionContext() & CommitContext) {
- try {
- const {onCommit, onRender} = finishedWork.memoizedProps;
- const {effectDuration} = finishedWork.stateNode;
-
- const commitTime = getCommitTime();
-
- let phase = current === null ? 'mount' : 'update';
- if (enableProfilerNestedUpdatePhase) {
- if (isCurrentUpdateNested()) {
- phase = 'nested-update';
- }
- }
-
- if (typeof onRender === 'function') {
- onRender(
- finishedWork.memoizedProps.id,
- phase,
- finishedWork.actualDuration,
- finishedWork.treeBaseDuration,
- finishedWork.actualStartTime,
- commitTime,
- );
- }
-
- if (enableProfilerCommitHooks) {
- if (typeof onCommit === 'function') {
- onCommit(
- finishedWork.memoizedProps.id,
- phase,
- effectDuration,
- commitTime,
- );
- }
-
- // Schedule a passive effect for this Profiler to call onPostCommit hooks.
- // This effect should be scheduled even if there is no onPostCommit callback for this Profiler,
- // because the effect is also where times bubble to parent Profilers.
- enqueuePendingPassiveProfilerEffect(finishedWork);
-
- // Propagate layout effect durations to the next nearest Profiler ancestor.
- // Do not reset these values until the next render so DevTools has a chance to read them first.
- let parentFiber = finishedWork.return;
- outer: while (parentFiber !== null) {
- switch (parentFiber.tag) {
- case HostRoot:
- const root = parentFiber.stateNode;
- root.effectDuration += effectDuration;
- break outer;
- case Profiler:
- const parentStateNode = parentFiber.stateNode;
- parentStateNode.effectDuration += effectDuration;
- break outer;
- }
- parentFiber = parentFiber.return;
- }
- }
- } catch (error) {
- captureCommitPhaseError(finishedWork, finishedWork.return, error);
- }
- }
-}
-
-function commitLayoutEffectOnFiber(
- finishedRoot: FiberRoot,
- current: Fiber | null,
- finishedWork: Fiber,
- committedLanes: Lanes,
-): void {
- // When updating this function, also update reappearLayoutEffects, which does
- // most of the same things when an offscreen tree goes from hidden -> visible.
- const flags = finishedWork.flags;
- switch (finishedWork.tag) {
- case FunctionComponent:
- case ForwardRef:
- case SimpleMemoComponent: {
- recursivelyTraverseLayoutEffects(
- finishedRoot,
- finishedWork,
- committedLanes,
- );
- if (flags & Update) {
- commitHookLayoutEffects(finishedWork, HookLayout | HookHasEffect);
- }
- break;
- }
- case ClassComponent: {
- recursivelyTraverseLayoutEffects(
- finishedRoot,
- finishedWork,
- committedLanes,
- );
- if (flags & Update) {
- commitClassLayoutLifecycles(finishedWork, current);
- }
-
- if (flags & Callback) {
- commitClassCallbacks(finishedWork);
- }
-
- if (flags & Ref) {
- safelyAttachRef(finishedWork, finishedWork.return);
- }
- break;
- }
- case HostRoot: {
- recursivelyTraverseLayoutEffects(
- finishedRoot,
- finishedWork,
- committedLanes,
- );
- if (flags & Callback) {
- // TODO: I think this is now always non-null by the time it reaches the
- // commit phase. Consider removing the type check.
- const updateQueue: UpdateQueue | null = (finishedWork.updateQueue: any);
- if (updateQueue !== null) {
- let instance = null;
- if (finishedWork.child !== null) {
- switch (finishedWork.child.tag) {
- case HostSingleton:
- case HostComponent:
- instance = getPublicInstance(finishedWork.child.stateNode);
- break;
- case ClassComponent:
- instance = finishedWork.child.stateNode;
- break;
- }
- }
- try {
- commitCallbacks(updateQueue, instance);
- } catch (error) {
- captureCommitPhaseError(finishedWork, finishedWork.return, error);
- }
- }
- }
- break;
- }
- case HostResource: {
- if (enableFloat && supportsResources) {
- recursivelyTraverseLayoutEffects(
- finishedRoot,
- finishedWork,
- committedLanes,
- );
-
- if (flags & Ref) {
- safelyAttachRef(finishedWork, finishedWork.return);
- }
- break;
- }
- }
- // eslint-disable-next-line-no-fallthrough
- case HostSingleton:
- case HostComponent: {
- recursivelyTraverseLayoutEffects(
- finishedRoot,
- finishedWork,
- committedLanes,
- );
-
- // Renderers may schedule work to be done after host components are mounted
- // (eg DOM renderer may schedule auto-focus for inputs and form controls).
- // These effects should only be committed when components are first mounted,
- // aka when there is no current/alternate.
- if (current === null && flags & Update) {
- commitHostComponentMount(finishedWork);
- }
-
- if (flags & Ref) {
- safelyAttachRef(finishedWork, finishedWork.return);
- }
- break;
- }
- case Profiler: {
- recursivelyTraverseLayoutEffects(
- finishedRoot,
- finishedWork,
- committedLanes,
- );
- // TODO: Should this fire inside an offscreen tree? Or should it wait to
- // fire when the tree becomes visible again.
- if (flags & Update) {
- commitProfilerUpdate(finishedWork, current);
- }
- break;
- }
- case SuspenseComponent: {
- recursivelyTraverseLayoutEffects(
- finishedRoot,
- finishedWork,
- committedLanes,
- );
- if (flags & Update) {
- commitSuspenseHydrationCallbacks(finishedRoot, finishedWork);
- }
- break;
- }
- case OffscreenComponent: {
- const isModernRoot = (finishedWork.mode & ConcurrentMode) !== NoMode;
- if (isModernRoot) {
- const isHidden = finishedWork.memoizedState !== null;
- const newOffscreenSubtreeIsHidden =
- isHidden || offscreenSubtreeIsHidden;
- if (newOffscreenSubtreeIsHidden) {
- // The Offscreen tree is hidden. Skip over its layout effects.
- } else {
- // The Offscreen tree is visible.
-
- const wasHidden = current !== null && current.memoizedState !== null;
- const newOffscreenSubtreeWasHidden =
- wasHidden || offscreenSubtreeWasHidden;
- const prevOffscreenSubtreeIsHidden = offscreenSubtreeIsHidden;
- const prevOffscreenSubtreeWasHidden = offscreenSubtreeWasHidden;
- offscreenSubtreeIsHidden = newOffscreenSubtreeIsHidden;
- offscreenSubtreeWasHidden = newOffscreenSubtreeWasHidden;
-
- if (offscreenSubtreeWasHidden && !prevOffscreenSubtreeWasHidden) {
- // This is the root of a reappearing boundary. As we continue
- // traversing the layout effects, we must also re-mount layout
- // effects that were unmounted when the Offscreen subtree was
- // hidden. So this is a superset of the normal commitLayoutEffects.
- const includeWorkInProgressEffects =
- (finishedWork.subtreeFlags & LayoutMask) !== NoFlags;
- recursivelyTraverseReappearLayoutEffects(
- finishedRoot,
- finishedWork,
- includeWorkInProgressEffects,
- );
- } else {
- recursivelyTraverseLayoutEffects(
- finishedRoot,
- finishedWork,
- committedLanes,
- );
- }
- offscreenSubtreeIsHidden = prevOffscreenSubtreeIsHidden;
- offscreenSubtreeWasHidden = prevOffscreenSubtreeWasHidden;
- }
- } else {
- recursivelyTraverseLayoutEffects(
- finishedRoot,
- finishedWork,
- committedLanes,
- );
- }
- if (flags & Ref) {
- const props: OffscreenProps = finishedWork.memoizedProps;
- if (props.mode === 'manual') {
- safelyAttachRef(finishedWork, finishedWork.return);
- } else {
- safelyDetachRef(finishedWork, finishedWork.return);
- }
- }
- break;
- }
- default: {
- recursivelyTraverseLayoutEffects(
- finishedRoot,
- finishedWork,
- committedLanes,
- );
- break;
- }
- }
-}
-
-function abortRootTransitions(
- root: FiberRoot,
- abort: TransitionAbort,
- deletedTransitions: Set,
- deletedOffscreenInstance: OffscreenInstance | null,
- isInDeletedTree: boolean,
-) {
- if (enableTransitionTracing) {
- const rootTransitions = root.incompleteTransitions;
- deletedTransitions.forEach(transition => {
- if (rootTransitions.has(transition)) {
- const transitionInstance: TracingMarkerInstance = (rootTransitions.get(
- transition,
- ): any);
- if (transitionInstance.aborts === null) {
- transitionInstance.aborts = [];
- }
- transitionInstance.aborts.push(abort);
-
- if (deletedOffscreenInstance !== null) {
- if (
- transitionInstance.pendingBoundaries !== null &&
- transitionInstance.pendingBoundaries.has(deletedOffscreenInstance)
- ) {
- // $FlowFixMe[incompatible-use] found when upgrading Flow
- transitionInstance.pendingBoundaries.delete(
- deletedOffscreenInstance,
- );
- }
- }
- }
- });
- }
-}
-
-function abortTracingMarkerTransitions(
- abortedFiber: Fiber,
- abort: TransitionAbort,
- deletedTransitions: Set,
- deletedOffscreenInstance: OffscreenInstance | null,
- isInDeletedTree: boolean,
-) {
- if (enableTransitionTracing) {
- const markerInstance: TracingMarkerInstance = abortedFiber.stateNode;
- const markerTransitions = markerInstance.transitions;
- const pendingBoundaries = markerInstance.pendingBoundaries;
- if (markerTransitions !== null) {
- // TODO: Refactor this code. Is there a way to move this code to
- // the deletions phase instead of calculating it here while making sure
- // complete is called appropriately?
- deletedTransitions.forEach(transition => {
- // If one of the transitions on the tracing marker is a transition
- // that was in an aborted subtree, we will abort that tracing marker
- if (
- abortedFiber !== null &&
- markerTransitions.has(transition) &&
- (markerInstance.aborts === null ||
- !markerInstance.aborts.includes(abort))
- ) {
- if (markerInstance.transitions !== null) {
- if (markerInstance.aborts === null) {
- markerInstance.aborts = [abort];
- addMarkerIncompleteCallbackToPendingTransition(
- abortedFiber.memoizedProps.name,
- markerInstance.transitions,
- markerInstance.aborts,
- );
- } else {
- markerInstance.aborts.push(abort);
- }
-
- // We only want to call onTransitionProgress when the marker hasn't been
- // deleted
- if (
- deletedOffscreenInstance !== null &&
- !isInDeletedTree &&
- pendingBoundaries !== null &&
- pendingBoundaries.has(deletedOffscreenInstance)
- ) {
- pendingBoundaries.delete(deletedOffscreenInstance);
-
- addMarkerProgressCallbackToPendingTransition(
- abortedFiber.memoizedProps.name,
- deletedTransitions,
- pendingBoundaries,
- );
- }
- }
- }
- });
- }
- }
-}
-
-function abortParentMarkerTransitionsForDeletedFiber(
- abortedFiber: Fiber,
- abort: TransitionAbort,
- deletedTransitions: Set,
- deletedOffscreenInstance: OffscreenInstance | null,
- isInDeletedTree: boolean,
-) {
- if (enableTransitionTracing) {
- // Find all pending markers that are waiting on child suspense boundaries in the
- // aborted subtree and cancels them
- let fiber: null | Fiber = abortedFiber;
- while (fiber !== null) {
- switch (fiber.tag) {
- case TracingMarkerComponent:
- abortTracingMarkerTransitions(
- fiber,
- abort,
- deletedTransitions,
- deletedOffscreenInstance,
- isInDeletedTree,
- );
- break;
- case HostRoot:
- const root = fiber.stateNode;
- abortRootTransitions(
- root,
- abort,
- deletedTransitions,
- deletedOffscreenInstance,
- isInDeletedTree,
- );
-
- break;
- default:
- break;
- }
-
- fiber = fiber.return;
- }
- }
-}
-
-function commitTransitionProgress(offscreenFiber: Fiber) {
- if (enableTransitionTracing) {
- // This function adds suspense boundaries to the root
- // or tracing marker's pendingBoundaries map.
- // When a suspense boundary goes from a resolved to a fallback
- // state we add the boundary to the map, and when it goes from
- // a fallback to a resolved state, we remove the boundary from
- // the map.
-
- // We use stateNode on the Offscreen component as a stable object
- // that doesnt change from render to render. This way we can
- // distinguish between different Offscreen instances (vs. the same
- // Offscreen instance with different fibers)
- const offscreenInstance: OffscreenInstance = offscreenFiber.stateNode;
-
- let prevState: SuspenseState | null = null;
- const previousFiber = offscreenFiber.alternate;
- if (previousFiber !== null && previousFiber.memoizedState !== null) {
- prevState = previousFiber.memoizedState;
- }
- const nextState: SuspenseState | null = offscreenFiber.memoizedState;
-
- const wasHidden = prevState !== null;
- const isHidden = nextState !== null;
-
- const pendingMarkers = offscreenInstance._pendingMarkers;
- // If there is a name on the suspense boundary, store that in
- // the pending boundaries.
- let name = null;
- const parent = offscreenFiber.return;
- if (
- parent !== null &&
- parent.tag === SuspenseComponent &&
- parent.memoizedProps.unstable_name
- ) {
- name = parent.memoizedProps.unstable_name;
- }
-
- if (!wasHidden && isHidden) {
- // The suspense boundaries was just hidden. Add the boundary
- // to the pending boundary set if it's there
- if (pendingMarkers !== null) {
- pendingMarkers.forEach(markerInstance => {
- const pendingBoundaries = markerInstance.pendingBoundaries;
- const transitions = markerInstance.transitions;
- const markerName = markerInstance.name;
- if (
- pendingBoundaries !== null &&
- !pendingBoundaries.has(offscreenInstance)
- ) {
- pendingBoundaries.set(offscreenInstance, {
- name,
- });
- if (transitions !== null) {
- if (
- markerInstance.tag === TransitionTracingMarker &&
- markerName !== null
- ) {
- addMarkerProgressCallbackToPendingTransition(
- markerName,
- transitions,
- pendingBoundaries,
- );
- } else if (markerInstance.tag === TransitionRoot) {
- transitions.forEach(transition => {
- addTransitionProgressCallbackToPendingTransition(
- transition,
- pendingBoundaries,
- );
- });
- }
- }
- }
- });
- }
- } else if (wasHidden && !isHidden) {
- // The suspense boundary went from hidden to visible. Remove
- // the boundary from the pending suspense boundaries set
- // if it's there
- if (pendingMarkers !== null) {
- pendingMarkers.forEach(markerInstance => {
- const pendingBoundaries = markerInstance.pendingBoundaries;
- const transitions = markerInstance.transitions;
- const markerName = markerInstance.name;
- if (
- pendingBoundaries !== null &&
- pendingBoundaries.has(offscreenInstance)
- ) {
- pendingBoundaries.delete(offscreenInstance);
- if (transitions !== null) {
- if (
- markerInstance.tag === TransitionTracingMarker &&
- markerName !== null
- ) {
- addMarkerProgressCallbackToPendingTransition(
- markerName,
- transitions,
- pendingBoundaries,
- );
-
- // If there are no more unresolved suspense boundaries, the interaction
- // is considered finished
- if (pendingBoundaries.size === 0) {
- if (markerInstance.aborts === null) {
- addMarkerCompleteCallbackToPendingTransition(
- markerName,
- transitions,
- );
- }
- markerInstance.transitions = null;
- markerInstance.pendingBoundaries = null;
- markerInstance.aborts = null;
- }
- } else if (markerInstance.tag === TransitionRoot) {
- transitions.forEach(transition => {
- addTransitionProgressCallbackToPendingTransition(
- transition,
- pendingBoundaries,
- );
- });
- }
- }
- }
- });
- }
- }
- }
-}
-
-function hideOrUnhideAllChildren(finishedWork, isHidden) {
- // Only hide or unhide the top-most host nodes.
- let hostSubtreeRoot = null;
-
- if (supportsMutation) {
- // We only have the top Fiber that was inserted but we need to recurse down its
- // children to find all the terminal nodes.
- let node: Fiber = finishedWork;
- while (true) {
- if (
- node.tag === HostComponent ||
- (enableFloat && supportsResources
- ? node.tag === HostResource
- : false) ||
- (enableHostSingletons && supportsSingletons
- ? node.tag === HostSingleton
- : false)
- ) {
- if (hostSubtreeRoot === null) {
- hostSubtreeRoot = node;
- try {
- const instance = node.stateNode;
- if (isHidden) {
- hideInstance(instance);
- } else {
- unhideInstance(node.stateNode, node.memoizedProps);
- }
- } catch (error) {
- captureCommitPhaseError(finishedWork, finishedWork.return, error);
- }
- }
- } else if (node.tag === HostText) {
- if (hostSubtreeRoot === null) {
- try {
- const instance = node.stateNode;
- if (isHidden) {
- hideTextInstance(instance);
- } else {
- unhideTextInstance(instance, node.memoizedProps);
- }
- } catch (error) {
- captureCommitPhaseError(finishedWork, finishedWork.return, error);
- }
- }
- } else if (
- (node.tag === OffscreenComponent ||
- node.tag === LegacyHiddenComponent) &&
- (node.memoizedState: OffscreenState) !== null &&
- node !== finishedWork
- ) {
- // Found a nested Offscreen component that is hidden.
- // Don't search any deeper. This tree should remain hidden.
- } else if (node.child !== null) {
- node.child.return = node;
- node = node.child;
- continue;
- }
-
- if (node === finishedWork) {
- return;
- }
- while (node.sibling === null) {
- if (node.return === null || node.return === finishedWork) {
- return;
- }
-
- if (hostSubtreeRoot === node) {
- hostSubtreeRoot = null;
- }
-
- node = node.return;
- }
-
- if (hostSubtreeRoot === node) {
- hostSubtreeRoot = null;
- }
-
- node.sibling.return = node.return;
- node = node.sibling;
- }
- }
-}
-
-function commitAttachRef(finishedWork: Fiber) {
- const ref = finishedWork.ref;
- if (ref !== null) {
- const instance = finishedWork.stateNode;
- let instanceToUse;
- switch (finishedWork.tag) {
- case HostResource:
- case HostSingleton:
- case HostComponent:
- instanceToUse = getPublicInstance(instance);
- break;
- default:
- instanceToUse = instance;
- }
- // Moved outside to ensure DCE works with this flag
- if (enableScopeAPI && finishedWork.tag === ScopeComponent) {
- instanceToUse = instance;
- }
- if (typeof ref === 'function') {
- if (shouldProfile(finishedWork)) {
- try {
- startLayoutEffectTimer();
- finishedWork.refCleanup = ref(instanceToUse);
- } finally {
- recordLayoutEffectDuration(finishedWork);
- }
- } else {
- finishedWork.refCleanup = ref(instanceToUse);
- }
- } else {
- if (__DEV__) {
- if (!ref.hasOwnProperty('current')) {
- console.error(
- 'Unexpected ref object provided for %s. ' +
- 'Use either a ref-setter function or React.createRef().',
- getComponentNameFromFiber(finishedWork),
- );
- }
- }
-
- // $FlowFixMe unable to narrow type to the non-function case
- ref.current = instanceToUse;
- }
- }
-}
-
-function detachFiberMutation(fiber: Fiber) {
- // Cut off the return pointer to disconnect it from the tree.
- // This enables us to detect and warn against state updates on an unmounted component.
- // It also prevents events from bubbling from within disconnected components.
- //
- // Ideally, we should also clear the child pointer of the parent alternate to let this
- // get GC:ed but we don't know which for sure which parent is the current
- // one so we'll settle for GC:ing the subtree of this child.
- // This child itself will be GC:ed when the parent updates the next time.
- //
- // Note that we can't clear child or sibling pointers yet.
- // They're needed for passive effects and for findDOMNode.
- // We defer those fields, and all other cleanup, to the passive phase (see detachFiberAfterEffects).
- //
- // Don't reset the alternate yet, either. We need that so we can detach the
- // alternate's fields in the passive phase. Clearing the return pointer is
- // sufficient for findDOMNode semantics.
- const alternate = fiber.alternate;
- if (alternate !== null) {
- alternate.return = null;
- }
- fiber.return = null;
-}
-
-function detachFiberAfterEffects(fiber: Fiber) {
- const alternate = fiber.alternate;
- if (alternate !== null) {
- fiber.alternate = null;
- detachFiberAfterEffects(alternate);
- }
-
- // Note: Defensively using negation instead of < in case
- // `deletedTreeCleanUpLevel` is undefined.
- if (!(deletedTreeCleanUpLevel >= 2)) {
- // This is the default branch (level 0).
- fiber.child = null;
- fiber.deletions = null;
- fiber.dependencies = null;
- fiber.memoizedProps = null;
- fiber.memoizedState = null;
- fiber.pendingProps = null;
- fiber.sibling = null;
- fiber.stateNode = null;
- fiber.updateQueue = null;
-
- if (__DEV__) {
- fiber._debugOwner = null;
- }
- } else {
- // Clear cyclical Fiber fields. This level alone is designed to roughly
- // approximate the planned Fiber refactor. In that world, `setState` will be
- // bound to a special "instance" object instead of a Fiber. The Instance
- // object will not have any of these fields. It will only be connected to
- // the fiber tree via a single link at the root. So if this level alone is
- // sufficient to fix memory issues, that bodes well for our plans.
- fiber.child = null;
- fiber.deletions = null;
- fiber.sibling = null;
-
- // The `stateNode` is cyclical because on host nodes it points to the host
- // tree, which has its own pointers to children, parents, and siblings.
- // The other host nodes also point back to fibers, so we should detach that
- // one, too.
- if (fiber.tag === HostComponent) {
- const hostInstance: Instance = fiber.stateNode;
- if (hostInstance !== null) {
- detachDeletedInstance(hostInstance);
- }
- }
- fiber.stateNode = null;
-
- // I'm intentionally not clearing the `return` field in this level. We
- // already disconnect the `return` pointer at the root of the deleted
- // subtree (in `detachFiberMutation`). Besides, `return` by itself is not
- // cyclical — it's only cyclical when combined with `child`, `sibling`, and
- // `alternate`. But we'll clear it in the next level anyway, just in case.
-
- if (__DEV__) {
- fiber._debugOwner = null;
- }
-
- if (deletedTreeCleanUpLevel >= 3) {
- // Theoretically, nothing in here should be necessary, because we already
- // disconnected the fiber from the tree. So even if something leaks this
- // particular fiber, it won't leak anything else
- //
- // The purpose of this branch is to be super aggressive so we can measure
- // if there's any difference in memory impact. If there is, that could
- // indicate a React leak we don't know about.
- fiber.return = null;
- fiber.dependencies = null;
- fiber.memoizedProps = null;
- fiber.memoizedState = null;
- fiber.pendingProps = null;
- fiber.stateNode = null;
- // TODO: Move to `commitPassiveUnmountInsideDeletedTreeOnFiber` instead.
- fiber.updateQueue = null;
- }
- }
-}
-
-function emptyPortalContainer(current: Fiber) {
- if (!supportsPersistence) {
- return;
- }
-
- const portal: {
- containerInfo: Container,
- pendingChildren: ChildSet,
- ...
- } = current.stateNode;
- const {containerInfo} = portal;
- const emptyChildSet = createContainerChildSet(containerInfo);
- replaceContainerChildren(containerInfo, emptyChildSet);
-}
-
-function getHostParentFiber(fiber: Fiber): Fiber {
- let parent = fiber.return;
- while (parent !== null) {
- if (isHostParent(parent)) {
- return parent;
- }
- parent = parent.return;
- }
-
- throw new Error(
- 'Expected to find a host parent. This error is likely caused by a bug ' +
- 'in React. Please file an issue.',
- );
-}
-
-function isHostParent(fiber: Fiber): boolean {
- return (
- fiber.tag === HostComponent ||
- fiber.tag === HostRoot ||
- (enableFloat && supportsResources ? fiber.tag === HostResource : false) ||
- (enableHostSingletons && supportsSingletons
- ? fiber.tag === HostSingleton
- : false) ||
- fiber.tag === HostPortal
- );
-}
-
-function getHostSibling(fiber: Fiber): ?Instance {
- // We're going to search forward into the tree until we find a sibling host
- // node. Unfortunately, if multiple insertions are done in a row we have to
- // search past them. This leads to exponential search for the next sibling.
- // TODO: Find a more efficient way to do this.
- let node: Fiber = fiber;
- siblings: while (true) {
- // If we didn't find anything, let's try the next sibling.
- while (node.sibling === null) {
- if (node.return === null || isHostParent(node.return)) {
- // If we pop out of the root or hit the parent the fiber we are the
- // last sibling.
- return null;
- }
- // $FlowFixMe[incompatible-type] found when upgrading Flow
- node = node.return;
- }
- node.sibling.return = node.return;
- node = node.sibling;
- while (
- node.tag !== HostComponent &&
- node.tag !== HostText &&
- (!(enableHostSingletons && supportsSingletons)
- ? true
- : node.tag !== HostSingleton) &&
- node.tag !== DehydratedFragment
- ) {
- // If it is not host node and, we might have a host node inside it.
- // Try to search down until we find one.
- if (node.flags & Placement) {
- // If we don't have a child, try the siblings instead.
- continue siblings;
- }
- // If we don't have a child, try the siblings instead.
- // We also skip portals because they are not part of this host tree.
- if (node.child === null || node.tag === HostPortal) {
- continue siblings;
- } else {
- node.child.return = node;
- node = node.child;
- }
- }
- // Check if this host node is stable or about to be placed.
- if (!(node.flags & Placement)) {
- // Found it!
- return node.stateNode;
- }
- }
-}
-
-function commitPlacement(finishedWork: Fiber): void {
- if (!supportsMutation) {
- return;
- }
-
- if (enableHostSingletons && supportsSingletons) {
- if (finishedWork.tag === HostSingleton) {
- // Singletons are already in the Host and don't need to be placed
- // Since they operate somewhat like Portals though their children will
- // have Placement and will get placed inside them
- return;
- }
- }
- // Recursively insert all host nodes into the parent.
- const parentFiber = getHostParentFiber(finishedWork);
-
- switch (parentFiber.tag) {
- case HostSingleton: {
- if (enableHostSingletons && supportsSingletons) {
- const parent: Instance = parentFiber.stateNode;
- const before = getHostSibling(finishedWork);
- // We only have the top Fiber that was inserted but we need to recurse down its
- // children to find all the terminal nodes.
- insertOrAppendPlacementNode(finishedWork, before, parent);
- break;
- }
- }
- // eslint-disable-next-line no-fallthrough
- case HostComponent: {
- const parent: Instance = parentFiber.stateNode;
- if (parentFiber.flags & ContentReset) {
- // Reset the text content of the parent before doing any insertions
- resetTextContent(parent);
- // Clear ContentReset from the effect tag
- parentFiber.flags &= ~ContentReset;
- }
-
- const before = getHostSibling(finishedWork);
- // We only have the top Fiber that was inserted but we need to recurse down its
- // children to find all the terminal nodes.
- insertOrAppendPlacementNode(finishedWork, before, parent);
- break;
- }
- case HostRoot:
- case HostPortal: {
- const parent: Container = parentFiber.stateNode.containerInfo;
- const before = getHostSibling(finishedWork);
- insertOrAppendPlacementNodeIntoContainer(finishedWork, before, parent);
- break;
- }
- // eslint-disable-next-line-no-fallthrough
- default:
- throw new Error(
- 'Invalid host parent fiber. This error is likely caused by a bug ' +
- 'in React. Please file an issue.',
- );
- }
-}
-
-function insertOrAppendPlacementNodeIntoContainer(
- node: Fiber,
- before: ?Instance,
- parent: Container,
-): void {
- const {tag} = node;
- const isHost = tag === HostComponent || tag === HostText;
- if (isHost) {
- const stateNode = node.stateNode;
- if (before) {
- insertInContainerBefore(parent, stateNode, before);
- } else {
- appendChildToContainer(parent, stateNode);
- }
- } else if (
- tag === HostPortal ||
- (enableHostSingletons && supportsSingletons ? tag === HostSingleton : false)
- ) {
- // If the insertion itself is a portal, then we don't want to traverse
- // down its children. Instead, we'll get insertions from each child in
- // the portal directly.
- // If the insertion is a HostSingleton then it will be placed independently
- } else {
- const child = node.child;
- if (child !== null) {
- insertOrAppendPlacementNodeIntoContainer(child, before, parent);
- let sibling = child.sibling;
- while (sibling !== null) {
- insertOrAppendPlacementNodeIntoContainer(sibling, before, parent);
- sibling = sibling.sibling;
- }
- }
- }
-}
-
-function insertOrAppendPlacementNode(
- node: Fiber,
- before: ?Instance,
- parent: Instance,
-): void {
- const {tag} = node;
- const isHost = tag === HostComponent || tag === HostText;
- if (isHost) {
- const stateNode = node.stateNode;
- if (before) {
- insertBefore(parent, stateNode, before);
- } else {
- appendChild(parent, stateNode);
- }
- } else if (
- tag === HostPortal ||
- (enableHostSingletons && supportsSingletons ? tag === HostSingleton : false)
- ) {
- // If the insertion itself is a portal, then we don't want to traverse
- // down its children. Instead, we'll get insertions from each child in
- // the portal directly.
- // If the insertion is a HostSingleton then it will be placed independently
- } else {
- const child = node.child;
- if (child !== null) {
- insertOrAppendPlacementNode(child, before, parent);
- let sibling = child.sibling;
- while (sibling !== null) {
- insertOrAppendPlacementNode(sibling, before, parent);
- sibling = sibling.sibling;
- }
- }
- }
-}
-
-// These are tracked on the stack as we recursively traverse a
-// deleted subtree.
-// TODO: Update these during the whole mutation phase, not just during
-// a deletion.
-let hostParent: Instance | Container | null = null;
-let hostParentIsContainer: boolean = false;
-
-function commitDeletionEffects(
- root: FiberRoot,
- returnFiber: Fiber,
- deletedFiber: Fiber,
-) {
- if (supportsMutation) {
- // We only have the top Fiber that was deleted but we need to recurse down its
- // children to find all the terminal nodes.
-
- // Recursively delete all host nodes from the parent, detach refs, clean
- // up mounted layout effects, and call componentWillUnmount.
-
- // We only need to remove the topmost host child in each branch. But then we
- // still need to keep traversing to unmount effects, refs, and cWU. TODO: We
- // could split this into two separate traversals functions, where the second
- // one doesn't include any removeChild logic. This is maybe the same
- // function as "disappearLayoutEffects" (or whatever that turns into after
- // the layout phase is refactored to use recursion).
-
- // Before starting, find the nearest host parent on the stack so we know
- // which instance/container to remove the children from.
- // TODO: Instead of searching up the fiber return path on every deletion, we
- // can track the nearest host component on the JS stack as we traverse the
- // tree during the commit phase. This would make insertions faster, too.
- let parent: null | Fiber = returnFiber;
- findParent: while (parent !== null) {
- switch (parent.tag) {
- case HostSingleton:
- case HostComponent: {
- hostParent = parent.stateNode;
- hostParentIsContainer = false;
- break findParent;
- }
- case HostRoot: {
- hostParent = parent.stateNode.containerInfo;
- hostParentIsContainer = true;
- break findParent;
- }
- case HostPortal: {
- hostParent = parent.stateNode.containerInfo;
- hostParentIsContainer = true;
- break findParent;
- }
- }
- parent = parent.return;
- }
- if (hostParent === null) {
- throw new Error(
- 'Expected to find a host parent. This error is likely caused by ' +
- 'a bug in React. Please file an issue.',
- );
- }
-
- commitDeletionEffectsOnFiber(root, returnFiber, deletedFiber);
- hostParent = null;
- hostParentIsContainer = false;
- } else {
- // Detach refs and call componentWillUnmount() on the whole subtree.
- commitDeletionEffectsOnFiber(root, returnFiber, deletedFiber);
- }
-
- detachFiberMutation(deletedFiber);
-}
-
-function recursivelyTraverseDeletionEffects(
- finishedRoot,
- nearestMountedAncestor,
- parent,
-) {
- // TODO: Use a static flag to skip trees that don't have unmount effects
- let child = parent.child;
- while (child !== null) {
- commitDeletionEffectsOnFiber(finishedRoot, nearestMountedAncestor, child);
- child = child.sibling;
- }
-}
-
-function commitDeletionEffectsOnFiber(
- finishedRoot: FiberRoot,
- nearestMountedAncestor: Fiber,
- deletedFiber: Fiber,
-) {
- onCommitUnmount(deletedFiber);
-
- // The cases in this outer switch modify the stack before they traverse
- // into their subtree. There are simpler cases in the inner switch
- // that don't modify the stack.
- switch (deletedFiber.tag) {
- case HostResource: {
- if (enableFloat && supportsResources) {
- if (!offscreenSubtreeWasHidden) {
- safelyDetachRef(deletedFiber, nearestMountedAncestor);
- }
- recursivelyTraverseDeletionEffects(
- finishedRoot,
- nearestMountedAncestor,
- deletedFiber,
- );
- if (deletedFiber.memoizedState) {
- releaseResource(deletedFiber.memoizedState);
- }
- return;
- }
- }
- // eslint-disable-next-line no-fallthrough
- case HostSingleton: {
- if (enableHostSingletons && supportsSingletons) {
- if (!offscreenSubtreeWasHidden) {
- safelyDetachRef(deletedFiber, nearestMountedAncestor);
- }
-
- const prevHostParent = hostParent;
- const prevHostParentIsContainer = hostParentIsContainer;
- hostParent = deletedFiber.stateNode;
- recursivelyTraverseDeletionEffects(
- finishedRoot,
- nearestMountedAncestor,
- deletedFiber,
- );
-
- // Normally this is called in passive unmount effect phase however with
- // HostSingleton we warn if you acquire one that is already associated to
- // a different fiber. To increase our chances of avoiding this, specifically
- // if you keyed a HostSingleton so there will be a delete followed by a Placement
- // we treat detach eagerly here
- releaseSingletonInstance(deletedFiber.stateNode);
-
- hostParent = prevHostParent;
- hostParentIsContainer = prevHostParentIsContainer;
-
- return;
- }
- }
- // eslint-disable-next-line no-fallthrough
- case HostComponent: {
- if (!offscreenSubtreeWasHidden) {
- safelyDetachRef(deletedFiber, nearestMountedAncestor);
- }
- // Intentional fallthrough to next branch
- }
- // eslint-disable-next-line-no-fallthrough
- case HostText: {
- // We only need to remove the nearest host child. Set the host parent
- // to `null` on the stack to indicate that nested children don't
- // need to be removed.
- if (supportsMutation) {
- const prevHostParent = hostParent;
- const prevHostParentIsContainer = hostParentIsContainer;
- hostParent = null;
- recursivelyTraverseDeletionEffects(
- finishedRoot,
- nearestMountedAncestor,
- deletedFiber,
- );
- hostParent = prevHostParent;
- hostParentIsContainer = prevHostParentIsContainer;
-
- if (hostParent !== null) {
- // Now that all the child effects have unmounted, we can remove the
- // node from the tree.
- if (hostParentIsContainer) {
- removeChildFromContainer(
- ((hostParent: any): Container),
- (deletedFiber.stateNode: Instance | TextInstance),
- );
- } else {
- removeChild(
- ((hostParent: any): Instance),
- (deletedFiber.stateNode: Instance | TextInstance),
- );
- }
- }
- } else {
- recursivelyTraverseDeletionEffects(
- finishedRoot,
- nearestMountedAncestor,
- deletedFiber,
- );
- }
- return;
- }
- case DehydratedFragment: {
- if (enableSuspenseCallback) {
- const hydrationCallbacks = finishedRoot.hydrationCallbacks;
- if (hydrationCallbacks !== null) {
- const onDeleted = hydrationCallbacks.onDeleted;
- if (onDeleted) {
- onDeleted((deletedFiber.stateNode: SuspenseInstance));
- }
- }
- }
-
- // Dehydrated fragments don't have any children
-
- // Delete the dehydrated suspense boundary and all of its content.
- if (supportsMutation) {
- if (hostParent !== null) {
- if (hostParentIsContainer) {
- clearSuspenseBoundaryFromContainer(
- ((hostParent: any): Container),
- (deletedFiber.stateNode: SuspenseInstance),
- );
- } else {
- clearSuspenseBoundary(
- ((hostParent: any): Instance),
- (deletedFiber.stateNode: SuspenseInstance),
- );
- }
- }
- }
- return;
- }
- case HostPortal: {
- if (supportsMutation) {
- // When we go into a portal, it becomes the parent to remove from.
- const prevHostParent = hostParent;
- const prevHostParentIsContainer = hostParentIsContainer;
- hostParent = deletedFiber.stateNode.containerInfo;
- hostParentIsContainer = true;
- recursivelyTraverseDeletionEffects(
- finishedRoot,
- nearestMountedAncestor,
- deletedFiber,
- );
- hostParent = prevHostParent;
- hostParentIsContainer = prevHostParentIsContainer;
- } else {
- emptyPortalContainer(deletedFiber);
-
- recursivelyTraverseDeletionEffects(
- finishedRoot,
- nearestMountedAncestor,
- deletedFiber,
- );
- }
- return;
- }
- case FunctionComponent:
- case ForwardRef:
- case MemoComponent:
- case SimpleMemoComponent: {
- if (!offscreenSubtreeWasHidden) {
- const updateQueue: FunctionComponentUpdateQueue | null = (deletedFiber.updateQueue: any);
- if (updateQueue !== null) {
- const lastEffect = updateQueue.lastEffect;
- if (lastEffect !== null) {
- const firstEffect = lastEffect.next;
-
- let effect = firstEffect;
- do {
- const {destroy, tag} = effect;
- if (destroy !== undefined) {
- if ((tag & HookInsertion) !== NoHookEffect) {
- safelyCallDestroy(
- deletedFiber,
- nearestMountedAncestor,
- destroy,
- );
- } else if ((tag & HookLayout) !== NoHookEffect) {
- if (enableSchedulingProfiler) {
- markComponentLayoutEffectUnmountStarted(deletedFiber);
- }
-
- if (shouldProfile(deletedFiber)) {
- startLayoutEffectTimer();
- safelyCallDestroy(
- deletedFiber,
- nearestMountedAncestor,
- destroy,
- );
- recordLayoutEffectDuration(deletedFiber);
- } else {
- safelyCallDestroy(
- deletedFiber,
- nearestMountedAncestor,
- destroy,
- );
- }
-
- if (enableSchedulingProfiler) {
- markComponentLayoutEffectUnmountStopped();
- }
- }
- }
- effect = effect.next;
- } while (effect !== firstEffect);
- }
- }
- }
-
- recursivelyTraverseDeletionEffects(
- finishedRoot,
- nearestMountedAncestor,
- deletedFiber,
- );
- return;
- }
- case ClassComponent: {
- if (!offscreenSubtreeWasHidden) {
- safelyDetachRef(deletedFiber, nearestMountedAncestor);
- const instance = deletedFiber.stateNode;
- if (typeof instance.componentWillUnmount === 'function') {
- safelyCallComponentWillUnmount(
- deletedFiber,
- nearestMountedAncestor,
- instance,
- );
- }
- }
- recursivelyTraverseDeletionEffects(
- finishedRoot,
- nearestMountedAncestor,
- deletedFiber,
- );
- return;
- }
- case ScopeComponent: {
- if (enableScopeAPI) {
- safelyDetachRef(deletedFiber, nearestMountedAncestor);
- }
- recursivelyTraverseDeletionEffects(
- finishedRoot,
- nearestMountedAncestor,
- deletedFiber,
- );
- return;
- }
- case OffscreenComponent: {
- safelyDetachRef(deletedFiber, nearestMountedAncestor);
- if (deletedFiber.mode & ConcurrentMode) {
- // If this offscreen component is hidden, we already unmounted it. Before
- // deleting the children, track that it's already unmounted so that we
- // don't attempt to unmount the effects again.
- // TODO: If the tree is hidden, in most cases we should be able to skip
- // over the nested children entirely. An exception is we haven't yet found
- // the topmost host node to delete, which we already track on the stack.
- // But the other case is portals, which need to be detached no matter how
- // deeply they are nested. We should use a subtree flag to track whether a
- // subtree includes a nested portal.
- const prevOffscreenSubtreeWasHidden = offscreenSubtreeWasHidden;
- offscreenSubtreeWasHidden =
- prevOffscreenSubtreeWasHidden || deletedFiber.memoizedState !== null;
-
- recursivelyTraverseDeletionEffects(
- finishedRoot,
- nearestMountedAncestor,
- deletedFiber,
- );
- offscreenSubtreeWasHidden = prevOffscreenSubtreeWasHidden;
- } else {
- recursivelyTraverseDeletionEffects(
- finishedRoot,
- nearestMountedAncestor,
- deletedFiber,
- );
- }
- break;
- }
- default: {
- recursivelyTraverseDeletionEffects(
- finishedRoot,
- nearestMountedAncestor,
- deletedFiber,
- );
- return;
- }
- }
-}
-function commitSuspenseCallback(finishedWork: Fiber) {
- // TODO: Move this to passive phase
- const newState: SuspenseState | null = finishedWork.memoizedState;
- if (enableSuspenseCallback && newState !== null) {
- const suspenseCallback = finishedWork.memoizedProps.suspenseCallback;
- if (typeof suspenseCallback === 'function') {
- const wakeables: Set | null = (finishedWork.updateQueue: any);
- if (wakeables !== null) {
- suspenseCallback(new Set(wakeables));
- }
- } else if (__DEV__) {
- if (suspenseCallback !== undefined) {
- console.error('Unexpected type for suspenseCallback.');
- }
- }
- }
-}
-
-function commitSuspenseHydrationCallbacks(
- finishedRoot: FiberRoot,
- finishedWork: Fiber,
-) {
- if (!supportsHydration) {
- return;
- }
- const newState: SuspenseState | null = finishedWork.memoizedState;
- if (newState === null) {
- const current = finishedWork.alternate;
- if (current !== null) {
- const prevState: SuspenseState | null = current.memoizedState;
- if (prevState !== null) {
- const suspenseInstance = prevState.dehydrated;
- if (suspenseInstance !== null) {
- try {
- commitHydratedSuspenseInstance(suspenseInstance);
- if (enableSuspenseCallback) {
- const hydrationCallbacks = finishedRoot.hydrationCallbacks;
- if (hydrationCallbacks !== null) {
- const onHydrated = hydrationCallbacks.onHydrated;
- if (onHydrated) {
- onHydrated(suspenseInstance);
- }
- }
- }
- } catch (error) {
- captureCommitPhaseError(finishedWork, finishedWork.return, error);
- }
- }
- }
- }
- }
-}
-
-function getRetryCache(finishedWork) {
- // TODO: Unify the interface for the retry cache so we don't have to switch
- // on the tag like this.
- switch (finishedWork.tag) {
- case SuspenseComponent:
- case SuspenseListComponent: {
- let retryCache = finishedWork.stateNode;
- if (retryCache === null) {
- retryCache = finishedWork.stateNode = new PossiblyWeakSet();
- }
- return retryCache;
- }
- case OffscreenComponent: {
- const instance: OffscreenInstance = finishedWork.stateNode;
- // $FlowFixMe[incompatible-type-arg] found when upgrading Flow
- let retryCache: null | Set | WeakSet =
- // $FlowFixMe[incompatible-type] found when upgrading Flow
- instance._retryCache;
- if (retryCache === null) {
- // $FlowFixMe[incompatible-type]
- retryCache = instance._retryCache = new PossiblyWeakSet();
- }
- return retryCache;
- }
- default: {
- throw new Error(
- `Unexpected Suspense handler tag (${finishedWork.tag}). This is a ` +
- 'bug in React.',
- );
- }
- }
-}
-
-export function detachOffscreenInstance(instance: OffscreenInstance): void {
- const currentOffscreenFiber = instance._current;
- if (currentOffscreenFiber === null) {
- throw new Error(
- 'Calling Offscreen.detach before instance handle has been set.',
- );
- }
-
- const executionContext = getExecutionContext();
- if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {
- scheduleMicrotask(() => {
- instance._visibility |= OffscreenDetached;
- disappearLayoutEffects(currentOffscreenFiber);
- disconnectPassiveEffect(currentOffscreenFiber);
- });
- } else {
- instance._visibility |= OffscreenDetached;
- disappearLayoutEffects(currentOffscreenFiber);
- disconnectPassiveEffect(currentOffscreenFiber);
- }
-}
-
-function attachSuspenseRetryListeners(
- finishedWork: Fiber,
- wakeables: Set,
-) {
- // If this boundary just timed out, then it will have a set of wakeables.
- // For each wakeable, attach a listener so that when it resolves, React
- // attempts to re-render the boundary in the primary (pre-timeout) state.
- const retryCache = getRetryCache(finishedWork);
- wakeables.forEach(wakeable => {
- // Memoize using the boundary fiber to prevent redundant listeners.
- const retry = resolveRetryWakeable.bind(null, finishedWork, wakeable);
- if (!retryCache.has(wakeable)) {
- retryCache.add(wakeable);
-
- if (enableUpdaterTracking) {
- if (isDevToolsPresent) {
- if (inProgressLanes !== null && inProgressRoot !== null) {
- // If we have pending work still, associate the original updaters with it.
- restorePendingUpdaters(inProgressRoot, inProgressLanes);
- } else {
- throw Error(
- 'Expected finished root and lanes to be set. This is a bug in React.',
- );
- }
- }
- }
-
- wakeable.then(retry, retry);
- }
- });
-}
-
-// This function detects when a Suspense boundary goes from visible to hidden.
-// It returns false if the boundary is already hidden.
-// TODO: Use an effect tag.
-export function isSuspenseBoundaryBeingHidden(
- current: Fiber | null,
- finishedWork: Fiber,
-): boolean {
- if (current !== null) {
- const oldState: SuspenseState | null = current.memoizedState;
- if (oldState === null || oldState.dehydrated !== null) {
- const newState: SuspenseState | null = finishedWork.memoizedState;
- return newState !== null && newState.dehydrated === null;
- }
- }
- return false;
-}
-
-export function commitMutationEffects(
- root: FiberRoot,
- finishedWork: Fiber,
- committedLanes: Lanes,
-) {
- inProgressLanes = committedLanes;
- inProgressRoot = root;
-
- setCurrentDebugFiberInDEV(finishedWork);
- commitMutationEffectsOnFiber(finishedWork, root, committedLanes);
- setCurrentDebugFiberInDEV(finishedWork);
-
- inProgressLanes = null;
- inProgressRoot = null;
-}
-
-function recursivelyTraverseMutationEffects(
- root: FiberRoot,
- parentFiber: Fiber,
- lanes: Lanes,
-) {
- // Deletions effects can be scheduled on any fiber type. They need to happen
- // before the children effects hae fired.
- const deletions = parentFiber.deletions;
- if (deletions !== null) {
- for (let i = 0; i < deletions.length; i++) {
- const childToDelete = deletions[i];
- try {
- commitDeletionEffects(root, parentFiber, childToDelete);
- } catch (error) {
- captureCommitPhaseError(childToDelete, parentFiber, error);
- }
- }
- }
-
- const prevDebugFiber = getCurrentDebugFiberInDEV();
- if (parentFiber.subtreeFlags & MutationMask) {
- let child = parentFiber.child;
- while (child !== null) {
- setCurrentDebugFiberInDEV(child);
- commitMutationEffectsOnFiber(child, root, lanes);
- child = child.sibling;
- }
- }
- setCurrentDebugFiberInDEV(prevDebugFiber);
-}
-
-function commitMutationEffectsOnFiber(
- finishedWork: Fiber,
- root: FiberRoot,
- lanes: Lanes,
-) {
- const current = finishedWork.alternate;
- const flags = finishedWork.flags;
-
- // The effect flag should be checked *after* we refine the type of fiber,
- // because the fiber tag is more specific. An exception is any flag related
- // to reconciliation, because those can be set on all fiber types.
- switch (finishedWork.tag) {
- case FunctionComponent:
- case ForwardRef:
- case MemoComponent:
- case SimpleMemoComponent: {
- recursivelyTraverseMutationEffects(root, finishedWork, lanes);
- commitReconciliationEffects(finishedWork);
-
- if (flags & Update) {
- try {
- commitHookEffectListUnmount(
- HookInsertion | HookHasEffect,
- finishedWork,
- finishedWork.return,
- );
- commitHookEffectListMount(
- HookInsertion | HookHasEffect,
- finishedWork,
- );
- } catch (error) {
- captureCommitPhaseError(finishedWork, finishedWork.return, error);
- }
- // Layout effects are destroyed during the mutation phase so that all
- // destroy functions for all fibers are called before any create functions.
- // This prevents sibling component effects from interfering with each other,
- // e.g. a destroy function in one component should never override a ref set
- // by a create function in another component during the same commit.
- if (shouldProfile(finishedWork)) {
- try {
- startLayoutEffectTimer();
- commitHookEffectListUnmount(
- HookLayout | HookHasEffect,
- finishedWork,
- finishedWork.return,
- );
- } catch (error) {
- captureCommitPhaseError(finishedWork, finishedWork.return, error);
- }
- recordLayoutEffectDuration(finishedWork);
- } else {
- try {
- commitHookEffectListUnmount(
- HookLayout | HookHasEffect,
- finishedWork,
- finishedWork.return,
- );
- } catch (error) {
- captureCommitPhaseError(finishedWork, finishedWork.return, error);
- }
- }
- }
- return;
- }
- case ClassComponent: {
- recursivelyTraverseMutationEffects(root, finishedWork, lanes);
- commitReconciliationEffects(finishedWork);
-
- if (flags & Ref) {
- if (current !== null) {
- safelyDetachRef(current, current.return);
- }
- }
-
- if (flags & Callback && offscreenSubtreeIsHidden) {
- const updateQueue: UpdateQueue | null = (finishedWork.updateQueue: any);
- if (updateQueue !== null) {
- deferHiddenCallbacks(updateQueue);
- }
- }
- return;
- }
- case HostResource: {
- if (enableFloat && supportsResources) {
- recursivelyTraverseMutationEffects(root, finishedWork, lanes);
- commitReconciliationEffects(finishedWork);
-
- if (flags & Ref) {
- if (current !== null) {
- safelyDetachRef(current, current.return);
- }
- }
-
- if (flags & Update) {
- const newResource = finishedWork.memoizedState;
- if (current !== null) {
- const currentResource = current.memoizedState;
- if (currentResource !== newResource) {
- releaseResource(currentResource);
- }
- }
- finishedWork.stateNode = newResource
- ? acquireResource(newResource)
- : null;
- }
- return;
- }
- }
- // eslint-disable-next-line-no-fallthrough
- case HostSingleton: {
- if (enableHostSingletons && supportsSingletons) {
- if (flags & Update) {
- const previousWork = finishedWork.alternate;
- if (previousWork === null) {
- const singleton = finishedWork.stateNode;
- const props = finishedWork.memoizedProps;
- // This was a new mount, we need to clear and set initial properties
- clearSingleton(singleton);
- acquireSingletonInstance(
- finishedWork.type,
- props,
- singleton,
- finishedWork,
- );
- }
- }
- }
- }
- // eslint-disable-next-line-no-fallthrough
- case HostComponent: {
- recursivelyTraverseMutationEffects(root, finishedWork, lanes);
- commitReconciliationEffects(finishedWork);
-
- if (flags & Ref) {
- if (current !== null) {
- safelyDetachRef(current, current.return);
- }
- }
- if (supportsMutation) {
- // TODO: ContentReset gets cleared by the children during the commit
- // phase. This is a refactor hazard because it means we must read
- // flags the flags after `commitReconciliationEffects` has already run;
- // the order matters. We should refactor so that ContentReset does not
- // rely on mutating the flag during commit. Like by setting a flag
- // during the render phase instead.
- if (finishedWork.flags & ContentReset) {
- const instance: Instance = finishedWork.stateNode;
- try {
- resetTextContent(instance);
- } catch (error) {
- captureCommitPhaseError(finishedWork, finishedWork.return, error);
- }
- }
-
- if (flags & Update) {
- const instance: Instance = finishedWork.stateNode;
- if (instance != null) {
- // Commit the work prepared earlier.
- const newProps = finishedWork.memoizedProps;
- // For hydration we reuse the update path but we treat the oldProps
- // as the newProps. The updatePayload will contain the real change in
- // this case.
- const oldProps =
- current !== null ? current.memoizedProps : newProps;
- const type = finishedWork.type;
- // TODO: Type the updateQueue to be specific to host components.
- const updatePayload: null | UpdatePayload = (finishedWork.updateQueue: any);
- finishedWork.updateQueue = null;
- if (updatePayload !== null) {
- try {
- commitUpdate(
- instance,
- updatePayload,
- type,
- oldProps,
- newProps,
- finishedWork,
- );
- } catch (error) {
- captureCommitPhaseError(
- finishedWork,
- finishedWork.return,
- error,
- );
- }
- }
- }
- }
- }
- return;
- }
- case HostText: {
- recursivelyTraverseMutationEffects(root, finishedWork, lanes);
- commitReconciliationEffects(finishedWork);
-
- if (flags & Update) {
- if (supportsMutation) {
- if (finishedWork.stateNode === null) {
- throw new Error(
- 'This should have a text node initialized. This error is likely ' +
- 'caused by a bug in React. Please file an issue.',
- );
- }
-
- const textInstance: TextInstance = finishedWork.stateNode;
- const newText: string = finishedWork.memoizedProps;
- // For hydration we reuse the update path but we treat the oldProps
- // as the newProps. The updatePayload will contain the real change in
- // this case.
- const oldText: string =
- current !== null ? current.memoizedProps : newText;
-
- try {
- commitTextUpdate(textInstance, oldText, newText);
- } catch (error) {
- captureCommitPhaseError(finishedWork, finishedWork.return, error);
- }
- }
- }
- return;
- }
- case HostRoot: {
- recursivelyTraverseMutationEffects(root, finishedWork, lanes);
- commitReconciliationEffects(finishedWork);
-
- if (flags & Update) {
- if (supportsMutation && supportsHydration) {
- if (current !== null) {
- const prevRootState: RootState = current.memoizedState;
- if (prevRootState.isDehydrated) {
- try {
- commitHydratedContainer(root.containerInfo);
- } catch (error) {
- captureCommitPhaseError(
- finishedWork,
- finishedWork.return,
- error,
- );
- }
- }
- }
- }
- if (supportsPersistence) {
- const containerInfo = root.containerInfo;
- const pendingChildren = root.pendingChildren;
- try {
- replaceContainerChildren(containerInfo, pendingChildren);
- } catch (error) {
- captureCommitPhaseError(finishedWork, finishedWork.return, error);
- }
- }
- }
- return;
- }
- case HostPortal: {
- recursivelyTraverseMutationEffects(root, finishedWork, lanes);
- commitReconciliationEffects(finishedWork);
-
- if (flags & Update) {
- if (supportsPersistence) {
- const portal = finishedWork.stateNode;
- const containerInfo = portal.containerInfo;
- const pendingChildren = portal.pendingChildren;
- try {
- replaceContainerChildren(containerInfo, pendingChildren);
- } catch (error) {
- captureCommitPhaseError(finishedWork, finishedWork.return, error);
- }
- }
- }
- return;
- }
- case SuspenseComponent: {
- recursivelyTraverseMutationEffects(root, finishedWork, lanes);
- commitReconciliationEffects(finishedWork);
-
- const offscreenFiber: Fiber = (finishedWork.child: any);
-
- if (offscreenFiber.flags & Visibility) {
- const newState: OffscreenState | null = offscreenFiber.memoizedState;
- const isHidden = newState !== null;
- if (isHidden) {
- const wasHidden =
- offscreenFiber.alternate !== null &&
- offscreenFiber.alternate.memoizedState !== null;
- if (!wasHidden) {
- // TODO: Move to passive phase
- markCommitTimeOfFallback();
- }
- }
- }
-
- if (flags & Update) {
- try {
- commitSuspenseCallback(finishedWork);
- } catch (error) {
- captureCommitPhaseError(finishedWork, finishedWork.return, error);
- }
- const wakeables: Set | null = (finishedWork.updateQueue: any);
- if (wakeables !== null) {
- finishedWork.updateQueue = null;
- attachSuspenseRetryListeners(finishedWork, wakeables);
- }
- }
- return;
- }
- case OffscreenComponent: {
- if (flags & Ref) {
- if (current !== null) {
- safelyDetachRef(current, current.return);
- }
- }
-
- const newState: OffscreenState | null = finishedWork.memoizedState;
- const isHidden = newState !== null;
- const wasHidden = current !== null && current.memoizedState !== null;
-
- if (finishedWork.mode & ConcurrentMode) {
- // Before committing the children, track on the stack whether this
- // offscreen subtree was already hidden, so that we don't unmount the
- // effects again.
- const prevOffscreenSubtreeIsHidden = offscreenSubtreeIsHidden;
- const prevOffscreenSubtreeWasHidden = offscreenSubtreeWasHidden;
- offscreenSubtreeIsHidden = prevOffscreenSubtreeIsHidden || isHidden;
- offscreenSubtreeWasHidden = prevOffscreenSubtreeWasHidden || wasHidden;
- recursivelyTraverseMutationEffects(root, finishedWork, lanes);
- offscreenSubtreeWasHidden = prevOffscreenSubtreeWasHidden;
- offscreenSubtreeIsHidden = prevOffscreenSubtreeIsHidden;
- } else {
- recursivelyTraverseMutationEffects(root, finishedWork, lanes);
- }
-
- commitReconciliationEffects(finishedWork);
- // TODO: Add explicit effect flag to set _current.
- finishedWork.stateNode._current = finishedWork;
-
- if (flags & Visibility) {
- const offscreenInstance: OffscreenInstance = finishedWork.stateNode;
-
- // Track the current state on the Offscreen instance so we can
- // read it during an event
- if (isHidden) {
- offscreenInstance._visibility &= ~OffscreenVisible;
- } else {
- offscreenInstance._visibility |= OffscreenVisible;
- }
-
- if (isHidden) {
- const isUpdate = current !== null;
- const wasHiddenByAncestorOffscreen =
- offscreenSubtreeIsHidden || offscreenSubtreeWasHidden;
- // Only trigger disapper layout effects if:
- // - This is an update, not first mount.
- // - This Offscreen was not hidden before.
- // - Ancestor Offscreen was not hidden in previous commit.
- if (isUpdate && !wasHidden && !wasHiddenByAncestorOffscreen) {
- if ((finishedWork.mode & ConcurrentMode) !== NoMode) {
- // Disappear the layout effects of all the children
- recursivelyTraverseDisappearLayoutEffects(finishedWork);
- }
- }
- } else {
- if (wasHidden) {
- // TODO: Move re-appear call here for symmetry?
- }
- }
-
- // Offscreen with manual mode manages visibility manually.
- if (supportsMutation && !isOffscreenManual(finishedWork)) {
- // TODO: This needs to run whenever there's an insertion or update
- // inside a hidden Offscreen tree.
- hideOrUnhideAllChildren(finishedWork, isHidden);
- }
- }
-
- // TODO: Move to passive phase
- if (flags & Update) {
- const offscreenQueue: OffscreenQueue | null = (finishedWork.updateQueue: any);
- if (offscreenQueue !== null) {
- const wakeables = offscreenQueue.wakeables;
- if (wakeables !== null) {
- offscreenQueue.wakeables = null;
- attachSuspenseRetryListeners(finishedWork, wakeables);
- }
- }
- }
- return;
- }
- case SuspenseListComponent: {
- recursivelyTraverseMutationEffects(root, finishedWork, lanes);
- commitReconciliationEffects(finishedWork);
-
- if (flags & Update) {
- const wakeables: Set | null = (finishedWork.updateQueue: any);
- if (wakeables !== null) {
- finishedWork.updateQueue = null;
- attachSuspenseRetryListeners(finishedWork, wakeables);
- }
- }
- return;
- }
- case ScopeComponent: {
- if (enableScopeAPI) {
- recursivelyTraverseMutationEffects(root, finishedWork, lanes);
- commitReconciliationEffects(finishedWork);
-
- // TODO: This is a temporary solution that allowed us to transition away
- // from React Flare on www.
- if (flags & Ref) {
- if (current !== null) {
- safelyDetachRef(finishedWork, finishedWork.return);
- }
- safelyAttachRef(finishedWork, finishedWork.return);
- }
- if (flags & Update) {
- const scopeInstance = finishedWork.stateNode;
- prepareScopeUpdate(scopeInstance, finishedWork);
- }
- }
- return;
- }
- default: {
- recursivelyTraverseMutationEffects(root, finishedWork, lanes);
- commitReconciliationEffects(finishedWork);
-
- return;
- }
- }
-}
-function commitReconciliationEffects(finishedWork: Fiber) {
- // Placement effects (insertions, reorders) can be scheduled on any fiber
- // type. They needs to happen after the children effects have fired, but
- // before the effects on this fiber have fired.
- const flags = finishedWork.flags;
- if (flags & Placement) {
- try {
- commitPlacement(finishedWork);
- } catch (error) {
- captureCommitPhaseError(finishedWork, finishedWork.return, error);
- }
- // Clear the "placement" from effect tag so that we know that this is
- // inserted, before any life-cycles like componentDidMount gets called.
- // TODO: findDOMNode doesn't rely on this any more but isMounted does
- // and isMounted is deprecated anyway so we should be able to kill this.
- finishedWork.flags &= ~Placement;
- }
- if (flags & Hydrating) {
- finishedWork.flags &= ~Hydrating;
- }
-}
-
-export function commitLayoutEffects(
- finishedWork: Fiber,
- root: FiberRoot,
- committedLanes: Lanes,
-): void {
- inProgressLanes = committedLanes;
- inProgressRoot = root;
-
- const current = finishedWork.alternate;
- commitLayoutEffectOnFiber(root, current, finishedWork, committedLanes);
-
- inProgressLanes = null;
- inProgressRoot = null;
-}
-
-function recursivelyTraverseLayoutEffects(
- root: FiberRoot,
- parentFiber: Fiber,
- lanes: Lanes,
-) {
- const prevDebugFiber = getCurrentDebugFiberInDEV();
- if (parentFiber.subtreeFlags & LayoutMask) {
- let child = parentFiber.child;
- while (child !== null) {
- setCurrentDebugFiberInDEV(child);
- const current = child.alternate;
- commitLayoutEffectOnFiber(root, current, child, lanes);
- child = child.sibling;
- }
- }
- setCurrentDebugFiberInDEV(prevDebugFiber);
-}
-
-export function disappearLayoutEffects(finishedWork: Fiber) {
- switch (finishedWork.tag) {
- case FunctionComponent:
- case ForwardRef:
- case MemoComponent:
- case SimpleMemoComponent: {
- // TODO (Offscreen) Check: flags & LayoutStatic
- if (shouldProfile(finishedWork)) {
- try {
- startLayoutEffectTimer();
- commitHookEffectListUnmount(
- HookLayout,
- finishedWork,
- finishedWork.return,
- );
- } finally {
- recordLayoutEffectDuration(finishedWork);
- }
- } else {
- commitHookEffectListUnmount(
- HookLayout,
- finishedWork,
- finishedWork.return,
- );
- }
-
- recursivelyTraverseDisappearLayoutEffects(finishedWork);
- break;
- }
- case ClassComponent: {
- // TODO (Offscreen) Check: flags & RefStatic
- safelyDetachRef(finishedWork, finishedWork.return);
-
- const instance = finishedWork.stateNode;
- if (typeof instance.componentWillUnmount === 'function') {
- safelyCallComponentWillUnmount(
- finishedWork,
- finishedWork.return,
- instance,
- );
- }
-
- recursivelyTraverseDisappearLayoutEffects(finishedWork);
- break;
- }
- case HostResource:
- case HostSingleton:
- case HostComponent: {
- // TODO (Offscreen) Check: flags & RefStatic
- safelyDetachRef(finishedWork, finishedWork.return);
-
- recursivelyTraverseDisappearLayoutEffects(finishedWork);
- break;
- }
- case OffscreenComponent: {
- // TODO (Offscreen) Check: flags & RefStatic
- safelyDetachRef(finishedWork, finishedWork.return);
-
- const isHidden = finishedWork.memoizedState !== null;
- if (isHidden) {
- // Nested Offscreen tree is already hidden. Don't disappear
- // its effects.
- } else {
- recursivelyTraverseDisappearLayoutEffects(finishedWork);
- }
- break;
- }
- default: {
- recursivelyTraverseDisappearLayoutEffects(finishedWork);
- break;
- }
- }
-}
-
-function recursivelyTraverseDisappearLayoutEffects(parentFiber: Fiber) {
- // TODO (Offscreen) Check: flags & (RefStatic | LayoutStatic)
- let child = parentFiber.child;
- while (child !== null) {
- disappearLayoutEffects(child);
- child = child.sibling;
- }
-}
-
-export function reappearLayoutEffects(
- finishedRoot: FiberRoot,
- current: Fiber | null,
- finishedWork: Fiber,
- // This function visits both newly finished work and nodes that were re-used
- // from a previously committed tree. We cannot check non-static flags if the
- // node was reused.
- includeWorkInProgressEffects: boolean,
-) {
- // Turn on layout effects in a tree that previously disappeared.
- const flags = finishedWork.flags;
- switch (finishedWork.tag) {
- case FunctionComponent:
- case ForwardRef:
- case SimpleMemoComponent: {
- recursivelyTraverseReappearLayoutEffects(
- finishedRoot,
- finishedWork,
- includeWorkInProgressEffects,
- );
- // TODO: Check flags & LayoutStatic
- commitHookLayoutEffects(finishedWork, HookLayout);
- break;
- }
- case ClassComponent: {
- recursivelyTraverseReappearLayoutEffects(
- finishedRoot,
- finishedWork,
- includeWorkInProgressEffects,
- );
-
- // TODO: Check for LayoutStatic flag
- const instance = finishedWork.stateNode;
- if (typeof instance.componentDidMount === 'function') {
- try {
- instance.componentDidMount();
- } catch (error) {
- captureCommitPhaseError(finishedWork, finishedWork.return, error);
- }
- }
-
- // Commit any callbacks that would have fired while the component
- // was hidden.
- const updateQueue: UpdateQueue | null = (finishedWork.updateQueue: any);
- if (updateQueue !== null) {
- commitHiddenCallbacks(updateQueue, instance);
- }
-
- // If this is newly finished work, check for setState callbacks
- if (includeWorkInProgressEffects && flags & Callback) {
- commitClassCallbacks(finishedWork);
- }
-
- // TODO: Check flags & RefStatic
- safelyAttachRef(finishedWork, finishedWork.return);
- break;
- }
- // Unlike commitLayoutEffectsOnFiber, we don't need to handle HostRoot
- // because this function only visits nodes that are inside an
- // Offscreen fiber.
- // case HostRoot: {
- // ...
- // }
- case HostResource:
- case HostSingleton:
- case HostComponent: {
- recursivelyTraverseReappearLayoutEffects(
- finishedRoot,
- finishedWork,
- includeWorkInProgressEffects,
- );
-
- // Renderers may schedule work to be done after host components are mounted
- // (eg DOM renderer may schedule auto-focus for inputs and form controls).
- // These effects should only be committed when components are first mounted,
- // aka when there is no current/alternate.
- if (includeWorkInProgressEffects && current === null && flags & Update) {
- commitHostComponentMount(finishedWork);
- }
-
- // TODO: Check flags & Ref
- safelyAttachRef(finishedWork, finishedWork.return);
- break;
- }
- case Profiler: {
- recursivelyTraverseReappearLayoutEffects(
- finishedRoot,
- finishedWork,
- includeWorkInProgressEffects,
- );
- // TODO: Figure out how Profiler updates should work with Offscreen
- if (includeWorkInProgressEffects && flags & Update) {
- commitProfilerUpdate(finishedWork, current);
- }
- break;
- }
- case SuspenseComponent: {
- recursivelyTraverseReappearLayoutEffects(
- finishedRoot,
- finishedWork,
- includeWorkInProgressEffects,
- );
-
- // TODO: Figure out how Suspense hydration callbacks should work
- // with Offscreen.
- if (includeWorkInProgressEffects && flags & Update) {
- commitSuspenseHydrationCallbacks(finishedRoot, finishedWork);
- }
- break;
- }
- case OffscreenComponent: {
- const offscreenState: OffscreenState = finishedWork.memoizedState;
- const isHidden = offscreenState !== null;
- if (isHidden) {
- // Nested Offscreen tree is still hidden. Don't re-appear its effects.
- } else {
- recursivelyTraverseReappearLayoutEffects(
- finishedRoot,
- finishedWork,
- includeWorkInProgressEffects,
- );
- }
- // TODO: Check flags & Ref
- safelyAttachRef(finishedWork, finishedWork.return);
- break;
- }
- default: {
- recursivelyTraverseReappearLayoutEffects(
- finishedRoot,
- finishedWork,
- includeWorkInProgressEffects,
- );
- break;
- }
- }
-}
-
-function recursivelyTraverseReappearLayoutEffects(
- finishedRoot: FiberRoot,
- parentFiber: Fiber,
- includeWorkInProgressEffects: boolean,
-) {
- // This function visits both newly finished work and nodes that were re-used
- // from a previously committed tree. We cannot check non-static flags if the
- // node was reused.
- const childShouldIncludeWorkInProgressEffects =
- includeWorkInProgressEffects &&
- (parentFiber.subtreeFlags & LayoutMask) !== NoFlags;
-
- // TODO (Offscreen) Check: flags & (RefStatic | LayoutStatic)
- const prevDebugFiber = getCurrentDebugFiberInDEV();
- let child = parentFiber.child;
- while (child !== null) {
- const current = child.alternate;
- reappearLayoutEffects(
- finishedRoot,
- current,
- child,
- childShouldIncludeWorkInProgressEffects,
- );
- child = child.sibling;
- }
- setCurrentDebugFiberInDEV(prevDebugFiber);
-}
-
-function commitHookPassiveMountEffects(
- finishedWork: Fiber,
- hookFlags: HookFlags,
-) {
- if (shouldProfile(finishedWork)) {
- startPassiveEffectTimer();
- try {
- commitHookEffectListMount(hookFlags, finishedWork);
- } catch (error) {
- captureCommitPhaseError(finishedWork, finishedWork.return, error);
- }
- recordPassiveEffectDuration(finishedWork);
- } else {
- try {
- commitHookEffectListMount(hookFlags, finishedWork);
- } catch (error) {
- captureCommitPhaseError(finishedWork, finishedWork.return, error);
- }
- }
-}
-
-function commitOffscreenPassiveMountEffects(
- current: Fiber | null,
- finishedWork: Fiber,
- instance: OffscreenInstance,
-) {
- if (enableCache) {
- let previousCache: Cache | null = null;
- if (
- current !== null &&
- current.memoizedState !== null &&
- current.memoizedState.cachePool !== null
- ) {
- previousCache = current.memoizedState.cachePool.pool;
- }
- let nextCache: Cache | null = null;
- if (
- finishedWork.memoizedState !== null &&
- finishedWork.memoizedState.cachePool !== null
- ) {
- nextCache = finishedWork.memoizedState.cachePool.pool;
- }
- // Retain/release the cache used for pending (suspended) nodes.
- // Note that this is only reached in the non-suspended/visible case:
- // when the content is suspended/hidden, the retain/release occurs
- // via the parent Suspense component (see case above).
- if (nextCache !== previousCache) {
- if (nextCache != null) {
- retainCache(nextCache);
- }
- if (previousCache != null) {
- releaseCache(previousCache);
- }
- }
- }
-
- if (enableTransitionTracing) {
- // TODO: Pre-rendering should not be counted as part of a transition. We
- // may add separate logs for pre-rendering, but it's not part of the
- // primary metrics.
- const offscreenState: OffscreenState = finishedWork.memoizedState;
- const queue: OffscreenQueue | null = (finishedWork.updateQueue: any);
-
- const isHidden = offscreenState !== null;
- if (queue !== null) {
- if (isHidden) {
- const transitions = queue.transitions;
- if (transitions !== null) {
- transitions.forEach(transition => {
- // Add all the transitions saved in the update queue during
- // the render phase (ie the transitions associated with this boundary)
- // into the transitions set.
- if (instance._transitions === null) {
- instance._transitions = new Set();
- }
- instance._transitions.add(transition);
- });
- }
-
- const markerInstances = queue.markerInstances;
- if (markerInstances !== null) {
- markerInstances.forEach(markerInstance => {
- const markerTransitions = markerInstance.transitions;
- // There should only be a few tracing marker transitions because
- // they should be only associated with the transition that
- // caused them
- if (markerTransitions !== null) {
- markerTransitions.forEach(transition => {
- if (instance._transitions === null) {
- instance._transitions = new Set();
- } else if (instance._transitions.has(transition)) {
- if (markerInstance.pendingBoundaries === null) {
- markerInstance.pendingBoundaries = new Map();
- }
- if (instance._pendingMarkers === null) {
- instance._pendingMarkers = new Set();
- }
-
- instance._pendingMarkers.add(markerInstance);
- }
- });
- }
- });
- }
- }
-
- finishedWork.updateQueue = null;
- }
-
- commitTransitionProgress(finishedWork);
-
- // TODO: Refactor this into an if/else branch
- if (!isHidden) {
- instance._transitions = null;
- instance._pendingMarkers = null;
- }
- }
-}
-
-function commitCachePassiveMountEffect(
- current: Fiber | null,
- finishedWork: Fiber,
-) {
- if (enableCache) {
- let previousCache: Cache | null = null;
- if (finishedWork.alternate !== null) {
- previousCache = finishedWork.alternate.memoizedState.cache;
- }
- const nextCache = finishedWork.memoizedState.cache;
- // Retain/release the cache. In theory the cache component
- // could be "borrowing" a cache instance owned by some parent,
- // in which case we could avoid retaining/releasing. But it
- // is non-trivial to determine when that is the case, so we
- // always retain/release.
- if (nextCache !== previousCache) {
- retainCache(nextCache);
- if (previousCache != null) {
- releaseCache(previousCache);
- }
- }
- }
-}
-
-function commitTracingMarkerPassiveMountEffect(finishedWork: Fiber) {
- // Get the transitions that were initiatized during the render
- // and add a start transition callback for each of them
- // We will only call this on initial mount of the tracing marker
- // only if there are no suspense children
- const instance = finishedWork.stateNode;
- if (instance.transitions !== null && instance.pendingBoundaries === null) {
- addMarkerCompleteCallbackToPendingTransition(
- finishedWork.memoizedProps.name,
- instance.transitions,
- );
- instance.transitions = null;
- instance.pendingBoundaries = null;
- instance.aborts = null;
- instance.name = null;
- }
-}
-
-export function commitPassiveMountEffects(
- root: FiberRoot,
- finishedWork: Fiber,
- committedLanes: Lanes,
- committedTransitions: Array | null,
-): void {
- setCurrentDebugFiberInDEV(finishedWork);
- commitPassiveMountOnFiber(
- root,
- finishedWork,
- committedLanes,
- committedTransitions,
- );
- resetCurrentDebugFiberInDEV();
-}
-
-function recursivelyTraversePassiveMountEffects(
- root: FiberRoot,
- parentFiber: Fiber,
- committedLanes: Lanes,
- committedTransitions: Array | null,
-) {
- const prevDebugFiber = getCurrentDebugFiberInDEV();
- if (parentFiber.subtreeFlags & PassiveMask) {
- let child = parentFiber.child;
- while (child !== null) {
- setCurrentDebugFiberInDEV(child);
- commitPassiveMountOnFiber(
- root,
- child,
- committedLanes,
- committedTransitions,
- );
- child = child.sibling;
- }
- }
- setCurrentDebugFiberInDEV(prevDebugFiber);
-}
-
-function commitPassiveMountOnFiber(
- finishedRoot: FiberRoot,
- finishedWork: Fiber,
- committedLanes: Lanes,
- committedTransitions: Array | null,
-): void {
- // When updating this function, also update reconnectPassiveEffects, which does
- // most of the same things when an offscreen tree goes from hidden -> visible,
- // or when toggling effects inside a hidden tree.
- const flags = finishedWork.flags;
- switch (finishedWork.tag) {
- case FunctionComponent:
- case ForwardRef:
- case SimpleMemoComponent: {
- recursivelyTraversePassiveMountEffects(
- finishedRoot,
- finishedWork,
- committedLanes,
- committedTransitions,
- );
- if (flags & Passive) {
- commitHookPassiveMountEffects(
- finishedWork,
- HookPassive | HookHasEffect,
- );
- }
- break;
- }
- case HostRoot: {
- recursivelyTraversePassiveMountEffects(
- finishedRoot,
- finishedWork,
- committedLanes,
- committedTransitions,
- );
- if (flags & Passive) {
- if (enableCache) {
- let previousCache: Cache | null = null;
- if (finishedWork.alternate !== null) {
- previousCache = finishedWork.alternate.memoizedState.cache;
- }
- const nextCache = finishedWork.memoizedState.cache;
- // Retain/release the root cache.
- // Note that on initial mount, previousCache and nextCache will be the same
- // and this retain won't occur. To counter this, we instead retain the HostRoot's
- // initial cache when creating the root itself (see createFiberRoot() in
- // ReactFiberRoot.js). Subsequent updates that change the cache are reflected
- // here, such that previous/next caches are retained correctly.
- if (nextCache !== previousCache) {
- retainCache(nextCache);
- if (previousCache != null) {
- releaseCache(previousCache);
- }
- }
- }
-
- if (enableTransitionTracing) {
- // Get the transitions that were initiatized during the render
- // and add a start transition callback for each of them
- const root: FiberRoot = finishedWork.stateNode;
- const incompleteTransitions = root.incompleteTransitions;
- // Initial render
- if (committedTransitions !== null) {
- committedTransitions.forEach(transition => {
- addTransitionStartCallbackToPendingTransition(transition);
- });
-
- clearTransitionsForLanes(finishedRoot, committedLanes);
- }
-
- incompleteTransitions.forEach((markerInstance, transition) => {
- const pendingBoundaries = markerInstance.pendingBoundaries;
- if (pendingBoundaries === null || pendingBoundaries.size === 0) {
- if (markerInstance.aborts === null) {
- addTransitionCompleteCallbackToPendingTransition(transition);
- }
- incompleteTransitions.delete(transition);
- }
- });
-
- clearTransitionsForLanes(finishedRoot, committedLanes);
- }
- }
- break;
- }
- case LegacyHiddenComponent: {
- if (enableLegacyHidden) {
- recursivelyTraversePassiveMountEffects(
- finishedRoot,
- finishedWork,
- committedLanes,
- committedTransitions,
- );
-
- if (flags & Passive) {
- const current = finishedWork.alternate;
- const instance: OffscreenInstance = finishedWork.stateNode;
- commitOffscreenPassiveMountEffects(current, finishedWork, instance);
- }
- }
- break;
- }
- case OffscreenComponent: {
- // TODO: Pass `current` as argument to this function
- const instance: OffscreenInstance = finishedWork.stateNode;
- const nextState: OffscreenState | null = finishedWork.memoizedState;
-
- const isHidden = nextState !== null;
-
- if (isHidden) {
- if (instance._visibility & OffscreenPassiveEffectsConnected) {
- // The effects are currently connected. Update them.
- recursivelyTraversePassiveMountEffects(
- finishedRoot,
- finishedWork,
- committedLanes,
- committedTransitions,
- );
- } else {
- if (finishedWork.mode & ConcurrentMode) {
- // The effects are currently disconnected. Since the tree is hidden,
- // don't connect them. This also applies to the initial render.
- if (enableCache || enableTransitionTracing) {
- // "Atomic" effects are ones that need to fire on every commit,
- // even during pre-rendering. An example is updating the reference
- // count on cache instances.
- recursivelyTraverseAtomicPassiveEffects(
- finishedRoot,
- finishedWork,
- committedLanes,
- committedTransitions,
- );
- }
- } else {
- // Legacy Mode: Fire the effects even if the tree is hidden.
- instance._visibility |= OffscreenPassiveEffectsConnected;
- recursivelyTraversePassiveMountEffects(
- finishedRoot,
- finishedWork,
- committedLanes,
- committedTransitions,
- );
- }
- }
- } else {
- // Tree is visible
- if (instance._visibility & OffscreenPassiveEffectsConnected) {
- // The effects are currently connected. Update them.
- recursivelyTraversePassiveMountEffects(
- finishedRoot,
- finishedWork,
- committedLanes,
- committedTransitions,
- );
- } else {
- // The effects are currently disconnected. Reconnect them, while also
- // firing effects inside newly mounted trees. This also applies to
- // the initial render.
- instance._visibility |= OffscreenPassiveEffectsConnected;
-
- const includeWorkInProgressEffects =
- (finishedWork.subtreeFlags & PassiveMask) !== NoFlags;
- recursivelyTraverseReconnectPassiveEffects(
- finishedRoot,
- finishedWork,
- committedLanes,
- committedTransitions,
- includeWorkInProgressEffects,
- );
- }
- }
-
- if (flags & Passive) {
- const current = finishedWork.alternate;
- commitOffscreenPassiveMountEffects(current, finishedWork, instance);
- }
- break;
- }
- case CacheComponent: {
- recursivelyTraversePassiveMountEffects(
- finishedRoot,
- finishedWork,
- committedLanes,
- committedTransitions,
- );
- if (flags & Passive) {
- // TODO: Pass `current` as argument to this function
- const current = finishedWork.alternate;
- commitCachePassiveMountEffect(current, finishedWork);
- }
- break;
- }
- case TracingMarkerComponent: {
- if (enableTransitionTracing) {
- recursivelyTraversePassiveMountEffects(
- finishedRoot,
- finishedWork,
- committedLanes,
- committedTransitions,
- );
- if (flags & Passive) {
- commitTracingMarkerPassiveMountEffect(finishedWork);
- }
- break;
- }
- // Intentional fallthrough to next branch
- }
- // eslint-disable-next-line-no-fallthrough
- default: {
- recursivelyTraversePassiveMountEffects(
- finishedRoot,
- finishedWork,
- committedLanes,
- committedTransitions,
- );
- break;
- }
- }
-}
-
-function recursivelyTraverseReconnectPassiveEffects(
- finishedRoot: FiberRoot,
- parentFiber: Fiber,
- committedLanes: Lanes,
- committedTransitions: Array | null,
- includeWorkInProgressEffects: boolean,
-) {
- // This function visits both newly finished work and nodes that were re-used
- // from a previously committed tree. We cannot check non-static flags if the
- // node was reused.
- const childShouldIncludeWorkInProgressEffects =
- includeWorkInProgressEffects &&
- (parentFiber.subtreeFlags & PassiveMask) !== NoFlags;
-
- // TODO (Offscreen) Check: flags & (RefStatic | LayoutStatic)
- const prevDebugFiber = getCurrentDebugFiberInDEV();
- let child = parentFiber.child;
- while (child !== null) {
- reconnectPassiveEffects(
- finishedRoot,
- child,
- committedLanes,
- committedTransitions,
- childShouldIncludeWorkInProgressEffects,
- );
- child = child.sibling;
- }
- setCurrentDebugFiberInDEV(prevDebugFiber);
-}
-
-export function reconnectPassiveEffects(
- finishedRoot: FiberRoot,
- finishedWork: Fiber,
- committedLanes: Lanes,
- committedTransitions: Array | null,
- // This function visits both newly finished work and nodes that were re-used
- // from a previously committed tree. We cannot check non-static flags if the
- // node was reused.
- includeWorkInProgressEffects: boolean,
-) {
- const flags = finishedWork.flags;
- switch (finishedWork.tag) {
- case FunctionComponent:
- case ForwardRef:
- case SimpleMemoComponent: {
- recursivelyTraverseReconnectPassiveEffects(
- finishedRoot,
- finishedWork,
- committedLanes,
- committedTransitions,
- includeWorkInProgressEffects,
- );
- // TODO: Check for PassiveStatic flag
- commitHookPassiveMountEffects(finishedWork, HookPassive);
- break;
- }
- // Unlike commitPassiveMountOnFiber, we don't need to handle HostRoot
- // because this function only visits nodes that are inside an
- // Offscreen fiber.
- // case HostRoot: {
- // ...
- // }
- case LegacyHiddenComponent: {
- if (enableLegacyHidden) {
- recursivelyTraverseReconnectPassiveEffects(
- finishedRoot,
- finishedWork,
- committedLanes,
- committedTransitions,
- includeWorkInProgressEffects,
- );
-
- if (includeWorkInProgressEffects && flags & Passive) {
- // TODO: Pass `current` as argument to this function
- const current: Fiber | null = finishedWork.alternate;
- const instance: OffscreenInstance = finishedWork.stateNode;
- commitOffscreenPassiveMountEffects(current, finishedWork, instance);
- }
- }
- break;
- }
- case OffscreenComponent: {
- const instance: OffscreenInstance = finishedWork.stateNode;
- const nextState: OffscreenState | null = finishedWork.memoizedState;
-
- const isHidden = nextState !== null;
-
- if (isHidden) {
- if (instance._visibility & OffscreenPassiveEffectsConnected) {
- // The effects are currently connected. Update them.
- recursivelyTraverseReconnectPassiveEffects(
- finishedRoot,
- finishedWork,
- committedLanes,
- committedTransitions,
- includeWorkInProgressEffects,
- );
- } else {
- if (finishedWork.mode & ConcurrentMode) {
- // The effects are currently disconnected. Since the tree is hidden,
- // don't connect them. This also applies to the initial render.
- if (enableCache || enableTransitionTracing) {
- // "Atomic" effects are ones that need to fire on every commit,
- // even during pre-rendering. An example is updating the reference
- // count on cache instances.
- recursivelyTraverseAtomicPassiveEffects(
- finishedRoot,
- finishedWork,
- committedLanes,
- committedTransitions,
- );
- }
- } else {
- // Legacy Mode: Fire the effects even if the tree is hidden.
- instance._visibility |= OffscreenPassiveEffectsConnected;
- recursivelyTraverseReconnectPassiveEffects(
- finishedRoot,
- finishedWork,
- committedLanes,
- committedTransitions,
- includeWorkInProgressEffects,
- );
- }
- }
- } else {
- // Tree is visible
-
- // Since we're already inside a reconnecting tree, it doesn't matter
- // whether the effects are currently connected. In either case, we'll
- // continue traversing the tree and firing all the effects.
- //
- // We do need to set the "connected" flag on the instance, though.
- instance._visibility |= OffscreenPassiveEffectsConnected;
-
- recursivelyTraverseReconnectPassiveEffects(
- finishedRoot,
- finishedWork,
- committedLanes,
- committedTransitions,
- includeWorkInProgressEffects,
- );
- }
-
- if (includeWorkInProgressEffects && flags & Passive) {
- // TODO: Pass `current` as argument to this function
- const current: Fiber | null = finishedWork.alternate;
- commitOffscreenPassiveMountEffects(current, finishedWork, instance);
- }
- break;
- }
- case CacheComponent: {
- recursivelyTraverseReconnectPassiveEffects(
- finishedRoot,
- finishedWork,
- committedLanes,
- committedTransitions,
- includeWorkInProgressEffects,
- );
- if (includeWorkInProgressEffects && flags & Passive) {
- // TODO: Pass `current` as argument to this function
- const current = finishedWork.alternate;
- commitCachePassiveMountEffect(current, finishedWork);
- }
- break;
- }
- case TracingMarkerComponent: {
- if (enableTransitionTracing) {
- recursivelyTraverseReconnectPassiveEffects(
- finishedRoot,
- finishedWork,
- committedLanes,
- committedTransitions,
- includeWorkInProgressEffects,
- );
- if (includeWorkInProgressEffects && flags & Passive) {
- commitTracingMarkerPassiveMountEffect(finishedWork);
- }
- break;
- }
- // Intentional fallthrough to next branch
- }
- // eslint-disable-next-line-no-fallthrough
- default: {
- recursivelyTraverseReconnectPassiveEffects(
- finishedRoot,
- finishedWork,
- committedLanes,
- committedTransitions,
- includeWorkInProgressEffects,
- );
- break;
- }
- }
-}
-
-function recursivelyTraverseAtomicPassiveEffects(
- finishedRoot: FiberRoot,
- parentFiber: Fiber,
- committedLanes: Lanes,
- committedTransitions: Array | null,
-) {
- // "Atomic" effects are ones that need to fire on every commit, even during
- // pre-rendering. We call this function when traversing a hidden tree whose
- // regular effects are currently disconnected.
- const prevDebugFiber = getCurrentDebugFiberInDEV();
- // TODO: Add special flag for atomic effects
- if (parentFiber.subtreeFlags & PassiveMask) {
- let child = parentFiber.child;
- while (child !== null) {
- setCurrentDebugFiberInDEV(child);
- commitAtomicPassiveEffects(
- finishedRoot,
- child,
- committedLanes,
- committedTransitions,
- );
- child = child.sibling;
- }
- }
- setCurrentDebugFiberInDEV(prevDebugFiber);
-}
-
-function commitAtomicPassiveEffects(
- finishedRoot: FiberRoot,
- finishedWork: Fiber,
- committedLanes: Lanes,
- committedTransitions: Array | null,
-) {
- // "Atomic" effects are ones that need to fire on every commit, even during
- // pre-rendering. We call this function when traversing a hidden tree whose
- // regular effects are currently disconnected.
- const flags = finishedWork.flags;
- switch (finishedWork.tag) {
- case OffscreenComponent: {
- recursivelyTraverseAtomicPassiveEffects(
- finishedRoot,
- finishedWork,
- committedLanes,
- committedTransitions,
- );
- if (flags & Passive) {
- // TODO: Pass `current` as argument to this function
- const current = finishedWork.alternate;
- const instance: OffscreenInstance = finishedWork.stateNode;
- commitOffscreenPassiveMountEffects(current, finishedWork, instance);
- }
- break;
- }
- case CacheComponent: {
- recursivelyTraverseAtomicPassiveEffects(
- finishedRoot,
- finishedWork,
- committedLanes,
- committedTransitions,
- );
- if (flags & Passive) {
- // TODO: Pass `current` as argument to this function
- const current = finishedWork.alternate;
- commitCachePassiveMountEffect(current, finishedWork);
- }
- break;
- }
- // eslint-disable-next-line-no-fallthrough
- default: {
- recursivelyTraverseAtomicPassiveEffects(
- finishedRoot,
- finishedWork,
- committedLanes,
- committedTransitions,
- );
- break;
- }
- }
-}
-
-export function commitPassiveUnmountEffects(finishedWork: Fiber): void {
- setCurrentDebugFiberInDEV(finishedWork);
- commitPassiveUnmountOnFiber(finishedWork);
- resetCurrentDebugFiberInDEV();
-}
-
-function detachAlternateSiblings(parentFiber: Fiber) {
- if (deletedTreeCleanUpLevel >= 1) {
- // A fiber was deleted from this parent fiber, but it's still part of the
- // previous (alternate) parent fiber's list of children. Because children
- // are a linked list, an earlier sibling that's still alive will be
- // connected to the deleted fiber via its `alternate`:
- //
- // live fiber --alternate--> previous live fiber --sibling--> deleted
- // fiber
- //
- // We can't disconnect `alternate` on nodes that haven't been deleted yet,
- // but we can disconnect the `sibling` and `child` pointers.
-
- const previousFiber = parentFiber.alternate;
- if (previousFiber !== null) {
- let detachedChild = previousFiber.child;
- if (detachedChild !== null) {
- previousFiber.child = null;
- do {
- // $FlowFixMe[incompatible-use] found when upgrading Flow
- const detachedSibling = detachedChild.sibling;
- // $FlowFixMe[incompatible-use] found when upgrading Flow
- detachedChild.sibling = null;
- detachedChild = detachedSibling;
- } while (detachedChild !== null);
- }
- }
- }
-}
-
-function commitHookPassiveUnmountEffects(
- finishedWork: Fiber,
- nearestMountedAncestor,
- hookFlags: HookFlags,
-) {
- if (shouldProfile(finishedWork)) {
- startPassiveEffectTimer();
- commitHookEffectListUnmount(
- hookFlags,
- finishedWork,
- nearestMountedAncestor,
- );
- recordPassiveEffectDuration(finishedWork);
- } else {
- commitHookEffectListUnmount(
- hookFlags,
- finishedWork,
- nearestMountedAncestor,
- );
- }
-}
-
-function recursivelyTraversePassiveUnmountEffects(parentFiber: Fiber): void {
- // Deletions effects can be scheduled on any fiber type. They need to happen
- // before the children effects have fired.
- const deletions = parentFiber.deletions;
-
- if ((parentFiber.flags & ChildDeletion) !== NoFlags) {
- if (deletions !== null) {
- for (let i = 0; i < deletions.length; i++) {
- const childToDelete = deletions[i];
- // TODO: Convert this to use recursion
- nextEffect = childToDelete;
- commitPassiveUnmountEffectsInsideOfDeletedTree_begin(
- childToDelete,
- parentFiber,
- );
- }
- }
- detachAlternateSiblings(parentFiber);
- }
-
- const prevDebugFiber = getCurrentDebugFiberInDEV();
- // TODO: Split PassiveMask into separate masks for mount and unmount?
- if (parentFiber.subtreeFlags & PassiveMask) {
- let child = parentFiber.child;
- while (child !== null) {
- setCurrentDebugFiberInDEV(child);
- commitPassiveUnmountOnFiber(child);
- child = child.sibling;
- }
- }
- setCurrentDebugFiberInDEV(prevDebugFiber);
-}
-
-function commitPassiveUnmountOnFiber(finishedWork: Fiber): void {
- switch (finishedWork.tag) {
- case FunctionComponent:
- case ForwardRef:
- case SimpleMemoComponent: {
- recursivelyTraversePassiveUnmountEffects(finishedWork);
- if (finishedWork.flags & Passive) {
- commitHookPassiveUnmountEffects(
- finishedWork,
- finishedWork.return,
- HookPassive | HookHasEffect,
- );
- }
- break;
- }
- case OffscreenComponent: {
- const instance: OffscreenInstance = finishedWork.stateNode;
- const nextState: OffscreenState | null = finishedWork.memoizedState;
-
- const isHidden = nextState !== null;
-
- if (
- isHidden &&
- instance._visibility & OffscreenPassiveEffectsConnected &&
- // For backwards compatibility, don't unmount when a tree suspends. In
- // the future we may change this to unmount after a delay.
- (finishedWork.return === null ||
- finishedWork.return.tag !== SuspenseComponent)
- ) {
- // The effects are currently connected. Disconnect them.
- // TODO: Add option or heuristic to delay before disconnecting the
- // effects. Then if the tree reappears before the delay has elapsed, we
- // can skip toggling the effects entirely.
- instance._visibility &= ~OffscreenPassiveEffectsConnected;
- recursivelyTraverseDisconnectPassiveEffects(finishedWork);
- } else {
- recursivelyTraversePassiveUnmountEffects(finishedWork);
- }
-
- break;
- }
- default: {
- recursivelyTraversePassiveUnmountEffects(finishedWork);
- break;
- }
- }
-}
-
-function recursivelyTraverseDisconnectPassiveEffects(parentFiber: Fiber): void {
- // Deletions effects can be scheduled on any fiber type. They need to happen
- // before the children effects have fired.
- const deletions = parentFiber.deletions;
-
- if ((parentFiber.flags & ChildDeletion) !== NoFlags) {
- if (deletions !== null) {
- for (let i = 0; i < deletions.length; i++) {
- const childToDelete = deletions[i];
- // TODO: Convert this to use recursion
- nextEffect = childToDelete;
- commitPassiveUnmountEffectsInsideOfDeletedTree_begin(
- childToDelete,
- parentFiber,
- );
- }
- }
- detachAlternateSiblings(parentFiber);
- }
-
- const prevDebugFiber = getCurrentDebugFiberInDEV();
- // TODO: Check PassiveStatic flag
- let child = parentFiber.child;
- while (child !== null) {
- setCurrentDebugFiberInDEV(child);
- disconnectPassiveEffect(child);
- child = child.sibling;
- }
- setCurrentDebugFiberInDEV(prevDebugFiber);
-}
-
-export function disconnectPassiveEffect(finishedWork: Fiber): void {
- switch (finishedWork.tag) {
- case FunctionComponent:
- case ForwardRef:
- case SimpleMemoComponent: {
- // TODO: Check PassiveStatic flag
- commitHookPassiveUnmountEffects(
- finishedWork,
- finishedWork.return,
- HookPassive,
- );
- // When disconnecting passive effects, we fire the effects in the same
- // order as during a deletiong: parent before child
- recursivelyTraverseDisconnectPassiveEffects(finishedWork);
- break;
- }
- case OffscreenComponent: {
- const instance: OffscreenInstance = finishedWork.stateNode;
- if (instance._visibility & OffscreenPassiveEffectsConnected) {
- instance._visibility &= ~OffscreenPassiveEffectsConnected;
- recursivelyTraverseDisconnectPassiveEffects(finishedWork);
- } else {
- // The effects are already disconnected.
- }
- break;
- }
- default: {
- recursivelyTraverseDisconnectPassiveEffects(finishedWork);
- break;
- }
- }
-}
-
-function commitPassiveUnmountEffectsInsideOfDeletedTree_begin(
- deletedSubtreeRoot: Fiber,
- nearestMountedAncestor: Fiber | null,
-) {
- while (nextEffect !== null) {
- const fiber = nextEffect;
-
- // Deletion effects fire in parent -> child order
- // TODO: Check if fiber has a PassiveStatic flag
- setCurrentDebugFiberInDEV(fiber);
- commitPassiveUnmountInsideDeletedTreeOnFiber(fiber, nearestMountedAncestor);
- resetCurrentDebugFiberInDEV();
-
- const child = fiber.child;
- // TODO: Only traverse subtree if it has a PassiveStatic flag. (But, if we
- // do this, still need to handle `deletedTreeCleanUpLevel` correctly.)
- if (child !== null) {
- child.return = fiber;
- nextEffect = child;
- } else {
- commitPassiveUnmountEffectsInsideOfDeletedTree_complete(
- deletedSubtreeRoot,
- );
- }
- }
-}
-
-function commitPassiveUnmountEffectsInsideOfDeletedTree_complete(
- deletedSubtreeRoot: Fiber,
-) {
- while (nextEffect !== null) {
- const fiber = nextEffect;
- const sibling = fiber.sibling;
- const returnFiber = fiber.return;
-
- if (deletedTreeCleanUpLevel >= 2) {
- // Recursively traverse the entire deleted tree and clean up fiber fields.
- // This is more aggressive than ideal, and the long term goal is to only
- // have to detach the deleted tree at the root.
- detachFiberAfterEffects(fiber);
- if (fiber === deletedSubtreeRoot) {
- nextEffect = null;
- return;
- }
- } else {
- // This is the default branch (level 0). We do not recursively clear all
- // the fiber fields. Only the root of the deleted subtree.
- if (fiber === deletedSubtreeRoot) {
- detachFiberAfterEffects(fiber);
- nextEffect = null;
- return;
- }
- }
-
- if (sibling !== null) {
- sibling.return = returnFiber;
- nextEffect = sibling;
- return;
- }
-
- nextEffect = returnFiber;
- }
-}
-
-function commitPassiveUnmountInsideDeletedTreeOnFiber(
- current: Fiber,
- nearestMountedAncestor: Fiber | null,
-): void {
- switch (current.tag) {
- case FunctionComponent:
- case ForwardRef:
- case SimpleMemoComponent: {
- commitHookPassiveUnmountEffects(
- current,
- nearestMountedAncestor,
- HookPassive,
- );
- break;
- }
- // TODO: run passive unmount effects when unmounting a root.
- // Because passive unmount effects are not currently run,
- // the cache instance owned by the root will never be freed.
- // When effects are run, the cache should be freed here:
- // case HostRoot: {
- // if (enableCache) {
- // const cache = current.memoizedState.cache;
- // releaseCache(cache);
- // }
- // break;
- // }
- case LegacyHiddenComponent:
- case OffscreenComponent: {
- if (enableCache) {
- if (
- current.memoizedState !== null &&
- current.memoizedState.cachePool !== null
- ) {
- const cache: Cache = current.memoizedState.cachePool.pool;
- // Retain/release the cache used for pending (suspended) nodes.
- // Note that this is only reached in the non-suspended/visible case:
- // when the content is suspended/hidden, the retain/release occurs
- // via the parent Suspense component (see case above).
- if (cache != null) {
- retainCache(cache);
- }
- }
- }
- break;
- }
- case SuspenseComponent: {
- if (enableTransitionTracing) {
- // We need to mark this fiber's parents as deleted
- const offscreenFiber: Fiber = (current.child: any);
- const instance: OffscreenInstance = offscreenFiber.stateNode;
- const transitions = instance._transitions;
- if (transitions !== null) {
- const abortReason = {
- reason: 'suspense',
- name: current.memoizedProps.unstable_name || null,
- };
- if (
- current.memoizedState === null ||
- current.memoizedState.dehydrated === null
- ) {
- abortParentMarkerTransitionsForDeletedFiber(
- offscreenFiber,
- abortReason,
- transitions,
- instance,
- true,
- );
-
- if (nearestMountedAncestor !== null) {
- abortParentMarkerTransitionsForDeletedFiber(
- nearestMountedAncestor,
- abortReason,
- transitions,
- instance,
- false,
- );
- }
- }
- }
- }
- break;
- }
- case CacheComponent: {
- if (enableCache) {
- const cache = current.memoizedState.cache;
- releaseCache(cache);
- }
- break;
- }
- case TracingMarkerComponent: {
- if (enableTransitionTracing) {
- // We need to mark this fiber's parents as deleted
- const instance: TracingMarkerInstance = current.stateNode;
- const transitions = instance.transitions;
- if (transitions !== null) {
- const abortReason = {
- reason: 'marker',
- name: current.memoizedProps.name,
- };
- abortParentMarkerTransitionsForDeletedFiber(
- current,
- abortReason,
- transitions,
- null,
- true,
- );
-
- if (nearestMountedAncestor !== null) {
- abortParentMarkerTransitionsForDeletedFiber(
- nearestMountedAncestor,
- abortReason,
- transitions,
- null,
- false,
- );
- }
- }
- }
- break;
- }
- }
-}
-
-function invokeLayoutEffectMountInDEV(fiber: Fiber): void {
- if (__DEV__) {
- // We don't need to re-check StrictEffectsMode here.
- // This function is only called if that check has already passed.
- switch (fiber.tag) {
- case FunctionComponent:
- case ForwardRef:
- case SimpleMemoComponent: {
- try {
- commitHookEffectListMount(HookLayout | HookHasEffect, fiber);
- } catch (error) {
- captureCommitPhaseError(fiber, fiber.return, error);
- }
- break;
- }
- case ClassComponent: {
- const instance = fiber.stateNode;
- try {
- instance.componentDidMount();
- } catch (error) {
- captureCommitPhaseError(fiber, fiber.return, error);
- }
- break;
- }
- }
- }
-}
-
-function invokePassiveEffectMountInDEV(fiber: Fiber): void {
- if (__DEV__) {
- // We don't need to re-check StrictEffectsMode here.
- // This function is only called if that check has already passed.
- switch (fiber.tag) {
- case FunctionComponent:
- case ForwardRef:
- case SimpleMemoComponent: {
- try {
- commitHookEffectListMount(HookPassive | HookHasEffect, fiber);
- } catch (error) {
- captureCommitPhaseError(fiber, fiber.return, error);
- }
- break;
- }
- }
- }
-}
-
-function invokeLayoutEffectUnmountInDEV(fiber: Fiber): void {
- if (__DEV__) {
- // We don't need to re-check StrictEffectsMode here.
- // This function is only called if that check has already passed.
- switch (fiber.tag) {
- case FunctionComponent:
- case ForwardRef:
- case SimpleMemoComponent: {
- try {
- commitHookEffectListUnmount(
- HookLayout | HookHasEffect,
- fiber,
- fiber.return,
- );
- } catch (error) {
- captureCommitPhaseError(fiber, fiber.return, error);
- }
- break;
- }
- case ClassComponent: {
- const instance = fiber.stateNode;
- if (typeof instance.componentWillUnmount === 'function') {
- safelyCallComponentWillUnmount(fiber, fiber.return, instance);
- }
- break;
- }
- }
- }
-}
-
-function invokePassiveEffectUnmountInDEV(fiber: Fiber): void {
- if (__DEV__) {
- // We don't need to re-check StrictEffectsMode here.
- // This function is only called if that check has already passed.
- switch (fiber.tag) {
- case FunctionComponent:
- case ForwardRef:
- case SimpleMemoComponent: {
- try {
- commitHookEffectListUnmount(
- HookPassive | HookHasEffect,
- fiber,
- fiber.return,
- );
- } catch (error) {
- captureCommitPhaseError(fiber, fiber.return, error);
- }
- }
- }
- }
-}
-
-export {
- commitPlacement,
- commitAttachRef,
- invokeLayoutEffectMountInDEV,
- invokeLayoutEffectUnmountInDEV,
- invokePassiveEffectMountInDEV,
- invokePassiveEffectUnmountInDEV,
-};
diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.new.js b/packages/react-reconciler/src/ReactFiberCompleteWork.new.js
deleted file mode 100644
index e091bfbbb81d6..0000000000000
--- a/packages/react-reconciler/src/ReactFiberCompleteWork.new.js
+++ /dev/null
@@ -1,1686 +0,0 @@
-/**
- * Copyright (c) Meta Platforms, Inc. and affiliates.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- *
- * @flow
- */
-
-import type {Fiber, FiberRoot} from './ReactInternalTypes';
-import type {RootState} from './ReactFiberRoot.new';
-import type {Lanes, Lane} from './ReactFiberLane.new';
-import type {
- ReactScopeInstance,
- ReactContext,
- Wakeable,
-} from 'shared/ReactTypes';
-import type {
- Instance,
- Type,
- Props,
- Container,
- ChildSet,
-} from './ReactFiberHostConfig';
-import type {
- SuspenseState,
- SuspenseListRenderState,
-} from './ReactFiberSuspenseComponent.new';
-import {isOffscreenManual} from './ReactFiberOffscreenComponent';
-import type {OffscreenState} from './ReactFiberOffscreenComponent';
-import type {TracingMarkerInstance} from './ReactFiberTracingMarkerComponent.new';
-import type {Cache} from './ReactFiberCacheComponent.new';
-import {
- enableLegacyHidden,
- enableHostSingletons,
- enableSuspenseCallback,
- enableScopeAPI,
- enableProfilerTimer,
- enableCache,
- enableTransitionTracing,
- enableFloat,
-} from 'shared/ReactFeatureFlags';
-
-import {resetWorkInProgressVersions as resetMutableSourceWorkInProgressVersions} from './ReactMutableSource.new';
-
-import {now} from './Scheduler';
-
-import {
- IndeterminateComponent,
- FunctionComponent,
- ClassComponent,
- HostRoot,
- HostComponent,
- HostResource,
- HostSingleton,
- HostText,
- HostPortal,
- ContextProvider,
- ContextConsumer,
- ForwardRef,
- Fragment,
- Mode,
- Profiler,
- SuspenseComponent,
- SuspenseListComponent,
- MemoComponent,
- SimpleMemoComponent,
- LazyComponent,
- IncompleteClassComponent,
- ScopeComponent,
- OffscreenComponent,
- LegacyHiddenComponent,
- CacheComponent,
- TracingMarkerComponent,
-} from './ReactWorkTags';
-import {NoMode, ConcurrentMode, ProfileMode} from './ReactTypeOfMode';
-import {
- Ref,
- RefStatic,
- Placement,
- Update,
- Visibility,
- NoFlags,
- DidCapture,
- Snapshot,
- ChildDeletion,
- StaticMask,
- MutationMask,
- Passive,
- Incomplete,
- ShouldCapture,
- ForceClientRender,
-} from './ReactFiberFlags';
-
-import {
- createInstance,
- createTextInstance,
- resolveSingletonInstance,
- appendInitialChild,
- finalizeInitialChildren,
- prepareUpdate,
- supportsMutation,
- supportsPersistence,
- supportsResources,
- supportsSingletons,
- cloneInstance,
- cloneHiddenInstance,
- cloneHiddenTextInstance,
- createContainerChildSet,
- appendChildToContainerChildSet,
- finalizeContainerChildren,
- preparePortalMount,
- prepareScopeUpdate,
-} from './ReactFiberHostConfig';
-import {
- getRootHostContainer,
- popHostContext,
- getHostContext,
- popHostContainer,
-} from './ReactFiberHostContext.new';
-import {
- suspenseStackCursor,
- popSuspenseListContext,
- popSuspenseHandler,
- pushSuspenseListContext,
- setShallowSuspenseListContext,
- ForceSuspenseFallback,
- setDefaultShallowSuspenseListContext,
- isBadSuspenseFallback,
-} from './ReactFiberSuspenseContext.new';
-import {popHiddenContext} from './ReactFiberHiddenContext.new';
-import {findFirstSuspended} from './ReactFiberSuspenseComponent.new';
-import {
- isContextProvider as isLegacyContextProvider,
- popContext as popLegacyContext,
- popTopLevelContextObject as popTopLevelLegacyContextObject,
-} from './ReactFiberContext.new';
-import {popProvider} from './ReactFiberNewContext.new';
-import {
- prepareToHydrateHostInstance,
- prepareToHydrateHostTextInstance,
- prepareToHydrateHostSuspenseInstance,
- warnIfUnhydratedTailNodes,
- popHydrationState,
- resetHydrationState,
- getIsHydrating,
- hasUnhydratedTailNodes,
- upgradeHydrationErrorsToRecoverable,
-} from './ReactFiberHydrationContext.new';
-import {
- renderDidSuspend,
- renderDidSuspendDelayIfPossible,
- renderHasNotSuspendedYet,
- getRenderTargetTime,
- getWorkInProgressTransitions,
-} from './ReactFiberWorkLoop.new';
-import {
- OffscreenLane,
- SomeRetryLane,
- NoLanes,
- includesSomeLane,
- mergeLanes,
-} from './ReactFiberLane.new';
-import {resetChildFibers} from './ReactChildFiber.new';
-import {createScopeInstance} from './ReactFiberScope.new';
-import {transferActualDuration} from './ReactProfilerTimer.new';
-import {popCacheProvider} from './ReactFiberCacheComponent.new';
-import {popTreeContext} from './ReactFiberTreeContext.new';
-import {popRootTransition, popTransition} from './ReactFiberTransition.new';
-import {
- popMarkerInstance,
- popRootMarkerInstance,
-} from './ReactFiberTracingMarkerComponent.new';
-
-function markUpdate(workInProgress: Fiber) {
- // Tag the fiber with an update effect. This turns a Placement into
- // a PlacementAndUpdate.
- workInProgress.flags |= Update;
-}
-
-function markRef(workInProgress: Fiber) {
- workInProgress.flags |= Ref | RefStatic;
-}
-
-function hadNoMutationsEffects(current: null | Fiber, completedWork: Fiber) {
- const didBailout = current !== null && current.child === completedWork.child;
- if (didBailout) {
- return true;
- }
-
- if ((completedWork.flags & ChildDeletion) !== NoFlags) {
- return false;
- }
-
- // TODO: If we move the `hadNoMutationsEffects` call after `bubbleProperties`
- // then we only have to check the `completedWork.subtreeFlags`.
- let child = completedWork.child;
- while (child !== null) {
- if (
- (child.flags & MutationMask) !== NoFlags ||
- (child.subtreeFlags & MutationMask) !== NoFlags
- ) {
- return false;
- }
- child = child.sibling;
- }
- return true;
-}
-
-let appendAllChildren;
-let updateHostContainer;
-let updateHostComponent;
-let updateHostText;
-if (supportsMutation) {
- // Mutation mode
-
- appendAllChildren = function(
- parent: Instance,
- workInProgress: Fiber,
- needsVisibilityToggle: boolean,
- isHidden: boolean,
- ) {
- // We only have the top Fiber that was created but we need recurse down its
- // children to find all the terminal nodes.
- let node = workInProgress.child;
- while (node !== null) {
- if (node.tag === HostComponent || node.tag === HostText) {
- appendInitialChild(parent, node.stateNode);
- } else if (
- node.tag === HostPortal ||
- (enableHostSingletons && supportsSingletons
- ? node.tag === HostSingleton
- : false)
- ) {
- // If we have a portal child, then we don't want to traverse
- // down its children. Instead, we'll get insertions from each child in
- // the portal directly.
- // If we have a HostSingleton it will be placed independently
- } else if (node.child !== null) {
- node.child.return = node;
- node = node.child;
- continue;
- }
- if (node === workInProgress) {
- return;
- }
- // $FlowFixMe[incompatible-use] found when upgrading Flow
- while (node.sibling === null) {
- // $FlowFixMe[incompatible-use] found when upgrading Flow
- if (node.return === null || node.return === workInProgress) {
- return;
- }
- node = node.return;
- }
- // $FlowFixMe[incompatible-use] found when upgrading Flow
- node.sibling.return = node.return;
- node = node.sibling;
- }
- };
-
- updateHostContainer = function(current: null | Fiber, workInProgress: Fiber) {
- // Noop
- };
- updateHostComponent = function(
- current: Fiber,
- workInProgress: Fiber,
- type: Type,
- newProps: Props,
- ) {
- // If we have an alternate, that means this is an update and we need to
- // schedule a side-effect to do the updates.
- const oldProps = current.memoizedProps;
- if (oldProps === newProps) {
- // In mutation mode, this is sufficient for a bailout because
- // we won't touch this node even if children changed.
- return;
- }
-
- // If we get updated because one of our children updated, we don't
- // have newProps so we'll have to reuse them.
- // TODO: Split the update API as separate for the props vs. children.
- // Even better would be if children weren't special cased at all tho.
- const instance: Instance = workInProgress.stateNode;
- const currentHostContext = getHostContext();
- // TODO: Experiencing an error where oldProps is null. Suggests a host
- // component is hitting the resume path. Figure out why. Possibly
- // related to `hidden`.
- const updatePayload = prepareUpdate(
- instance,
- type,
- oldProps,
- newProps,
- currentHostContext,
- );
- // TODO: Type this specific to this type of component.
- workInProgress.updateQueue = (updatePayload: any);
- // If the update payload indicates that there is a change or if there
- // is a new ref we mark this as an update. All the work is done in commitWork.
- if (updatePayload) {
- markUpdate(workInProgress);
- }
- };
- updateHostText = function(
- current: Fiber,
- workInProgress: Fiber,
- oldText: string,
- newText: string,
- ) {
- // If the text differs, mark it as an update. All the work in done in commitWork.
- if (oldText !== newText) {
- markUpdate(workInProgress);
- }
- };
-} else if (supportsPersistence) {
- // Persistent host tree mode
-
- appendAllChildren = function(
- parent: Instance,
- workInProgress: Fiber,
- needsVisibilityToggle: boolean,
- isHidden: boolean,
- ) {
- // We only have the top Fiber that was created but we need recurse down its
- // children to find all the terminal nodes.
- let node = workInProgress.child;
- while (node !== null) {
- // eslint-disable-next-line no-labels
- branches: if (node.tag === HostComponent) {
- let instance = node.stateNode;
- if (needsVisibilityToggle && isHidden) {
- // This child is inside a timed out tree. Hide it.
- const props = node.memoizedProps;
- const type = node.type;
- instance = cloneHiddenInstance(instance, type, props, node);
- }
- appendInitialChild(parent, instance);
- } else if (node.tag === HostText) {
- let instance = node.stateNode;
- if (needsVisibilityToggle && isHidden) {
- // This child is inside a timed out tree. Hide it.
- const text = node.memoizedProps;
- instance = cloneHiddenTextInstance(instance, text, node);
- }
- appendInitialChild(parent, instance);
- } else if (node.tag === HostPortal) {
- // If we have a portal child, then we don't want to traverse
- // down its children. Instead, we'll get insertions from each child in
- // the portal directly.
- } else if (
- node.tag === OffscreenComponent &&
- node.memoizedState !== null
- ) {
- // The children in this boundary are hidden. Toggle their visibility
- // before appending.
- const child = node.child;
- if (child !== null) {
- child.return = node;
- }
- appendAllChildren(parent, node, true, true);
- } else if (node.child !== null) {
- node.child.return = node;
- node = node.child;
- continue;
- }
- node = (node: Fiber);
- if (node === workInProgress) {
- return;
- }
- // $FlowFixMe[incompatible-use] found when upgrading Flow
- while (node.sibling === null) {
- // $FlowFixMe[incompatible-use] found when upgrading Flow
- if (node.return === null || node.return === workInProgress) {
- return;
- }
- node = node.return;
- }
- // $FlowFixMe[incompatible-use] found when upgrading Flow
- node.sibling.return = node.return;
- node = node.sibling;
- }
- };
-
- // An unfortunate fork of appendAllChildren because we have two different parent types.
- const appendAllChildrenToContainer = function(
- containerChildSet: ChildSet,
- workInProgress: Fiber,
- needsVisibilityToggle: boolean,
- isHidden: boolean,
- ) {
- // We only have the top Fiber that was created but we need recurse down its
- // children to find all the terminal nodes.
- let node = workInProgress.child;
- while (node !== null) {
- // eslint-disable-next-line no-labels
- branches: if (node.tag === HostComponent) {
- let instance = node.stateNode;
- if (needsVisibilityToggle && isHidden) {
- // This child is inside a timed out tree. Hide it.
- const props = node.memoizedProps;
- const type = node.type;
- instance = cloneHiddenInstance(instance, type, props, node);
- }
- appendChildToContainerChildSet(containerChildSet, instance);
- } else if (node.tag === HostText) {
- let instance = node.stateNode;
- if (needsVisibilityToggle && isHidden) {
- // This child is inside a timed out tree. Hide it.
- const text = node.memoizedProps;
- instance = cloneHiddenTextInstance(instance, text, node);
- }
- appendChildToContainerChildSet(containerChildSet, instance);
- } else if (node.tag === HostPortal) {
- // If we have a portal child, then we don't want to traverse
- // down its children. Instead, we'll get insertions from each child in
- // the portal directly.
- } else if (
- node.tag === OffscreenComponent &&
- node.memoizedState !== null
- ) {
- // The children in this boundary are hidden. Toggle their visibility
- // before appending.
- const child = node.child;
- if (child !== null) {
- child.return = node;
- }
- // If Offscreen is not in manual mode, detached tree is hidden from user space.
- const _needsVisibilityToggle = !isOffscreenManual(node);
- appendAllChildrenToContainer(
- containerChildSet,
- node,
- _needsVisibilityToggle,
- true,
- );
- } else if (node.child !== null) {
- node.child.return = node;
- node = node.child;
- continue;
- }
- node = (node: Fiber);
- if (node === workInProgress) {
- return;
- }
- // $FlowFixMe[incompatible-use] found when upgrading Flow
- while (node.sibling === null) {
- // $FlowFixMe[incompatible-use] found when upgrading Flow
- if (node.return === null || node.return === workInProgress) {
- return;
- }
- node = node.return;
- }
- // $FlowFixMe[incompatible-use] found when upgrading Flow
- node.sibling.return = node.return;
- node = node.sibling;
- }
- };
- updateHostContainer = function(current: null | Fiber, workInProgress: Fiber) {
- const portalOrRoot: {
- containerInfo: Container,
- pendingChildren: ChildSet,
- ...
- } = workInProgress.stateNode;
- const childrenUnchanged = hadNoMutationsEffects(current, workInProgress);
- if (childrenUnchanged) {
- // No changes, just reuse the existing instance.
- } else {
- const container = portalOrRoot.containerInfo;
- const newChildSet = createContainerChildSet(container);
- // If children might have changed, we have to add them all to the set.
- appendAllChildrenToContainer(newChildSet, workInProgress, false, false);
- portalOrRoot.pendingChildren = newChildSet;
- // Schedule an update on the container to swap out the container.
- markUpdate(workInProgress);
- finalizeContainerChildren(container, newChildSet);
- }
- };
- updateHostComponent = function(
- current: Fiber,
- workInProgress: Fiber,
- type: Type,
- newProps: Props,
- ) {
- const currentInstance = current.stateNode;
- const oldProps = current.memoizedProps;
- // If there are no effects associated with this node, then none of our children had any updates.
- // This guarantees that we can reuse all of them.
- const childrenUnchanged = hadNoMutationsEffects(current, workInProgress);
- if (childrenUnchanged && oldProps === newProps) {
- // No changes, just reuse the existing instance.
- // Note that this might release a previous clone.
- workInProgress.stateNode = currentInstance;
- return;
- }
- const recyclableInstance: Instance = workInProgress.stateNode;
- const currentHostContext = getHostContext();
- let updatePayload = null;
- if (oldProps !== newProps) {
- updatePayload = prepareUpdate(
- recyclableInstance,
- type,
- oldProps,
- newProps,
- currentHostContext,
- );
- }
- if (childrenUnchanged && updatePayload === null) {
- // No changes, just reuse the existing instance.
- // Note that this might release a previous clone.
- workInProgress.stateNode = currentInstance;
- return;
- }
- const newInstance = cloneInstance(
- currentInstance,
- updatePayload,
- type,
- oldProps,
- newProps,
- workInProgress,
- childrenUnchanged,
- recyclableInstance,
- );
- if (
- finalizeInitialChildren(newInstance, type, newProps, currentHostContext)
- ) {
- markUpdate(workInProgress);
- }
- workInProgress.stateNode = newInstance;
- if (childrenUnchanged) {
- // If there are no other effects in this tree, we need to flag this node as having one.
- // Even though we're not going to use it for anything.
- // Otherwise parents won't know that there are new children to propagate upwards.
- markUpdate(workInProgress);
- } else {
- // If children might have changed, we have to add them all to the set.
- appendAllChildren(newInstance, workInProgress, false, false);
- }
- };
- updateHostText = function(
- current: Fiber,
- workInProgress: Fiber,
- oldText: string,
- newText: string,
- ) {
- if (oldText !== newText) {
- // If the text content differs, we'll create a new text instance for it.
- const rootContainerInstance = getRootHostContainer();
- const currentHostContext = getHostContext();
- workInProgress.stateNode = createTextInstance(
- newText,
- rootContainerInstance,
- currentHostContext,
- workInProgress,
- );
- // We'll have to mark it as having an effect, even though we won't use the effect for anything.
- // This lets the parents know that at least one of their children has changed.
- markUpdate(workInProgress);
- } else {
- workInProgress.stateNode = current.stateNode;
- }
- };
-} else {
- // No host operations
- updateHostContainer = function(current: null | Fiber, workInProgress: Fiber) {
- // Noop
- };
- updateHostComponent = function(
- current: Fiber,
- workInProgress: Fiber,
- type: Type,
- newProps: Props,
- ) {
- // Noop
- };
- updateHostText = function(
- current: Fiber,
- workInProgress: Fiber,
- oldText: string,
- newText: string,
- ) {
- // Noop
- };
-}
-
-function cutOffTailIfNeeded(
- renderState: SuspenseListRenderState,
- hasRenderedATailFallback: boolean,
-) {
- if (getIsHydrating()) {
- // If we're hydrating, we should consume as many items as we can
- // so we don't leave any behind.
- return;
- }
- switch (renderState.tailMode) {
- case 'hidden': {
- // Any insertions at the end of the tail list after this point
- // should be invisible. If there are already mounted boundaries
- // anything before them are not considered for collapsing.
- // Therefore we need to go through the whole tail to find if
- // there are any.
- let tailNode = renderState.tail;
- let lastTailNode = null;
- while (tailNode !== null) {
- if (tailNode.alternate !== null) {
- lastTailNode = tailNode;
- }
- tailNode = tailNode.sibling;
- }
- // Next we're simply going to delete all insertions after the
- // last rendered item.
- if (lastTailNode === null) {
- // All remaining items in the tail are insertions.
- renderState.tail = null;
- } else {
- // Detach the insertion after the last node that was already
- // inserted.
- lastTailNode.sibling = null;
- }
- break;
- }
- case 'collapsed': {
- // Any insertions at the end of the tail list after this point
- // should be invisible. If there are already mounted boundaries
- // anything before them are not considered for collapsing.
- // Therefore we need to go through the whole tail to find if
- // there are any.
- let tailNode = renderState.tail;
- let lastTailNode = null;
- while (tailNode !== null) {
- if (tailNode.alternate !== null) {
- lastTailNode = tailNode;
- }
- tailNode = tailNode.sibling;
- }
- // Next we're simply going to delete all insertions after the
- // last rendered item.
- if (lastTailNode === null) {
- // All remaining items in the tail are insertions.
- if (!hasRenderedATailFallback && renderState.tail !== null) {
- // We suspended during the head. We want to show at least one
- // row at the tail. So we'll keep on and cut off the rest.
- renderState.tail.sibling = null;
- } else {
- renderState.tail = null;
- }
- } else {
- // Detach the insertion after the last node that was already
- // inserted.
- lastTailNode.sibling = null;
- }
- break;
- }
- }
-}
-
-function bubbleProperties(completedWork: Fiber) {
- const didBailout =
- completedWork.alternate !== null &&
- completedWork.alternate.child === completedWork.child;
-
- let newChildLanes = NoLanes;
- let subtreeFlags = NoFlags;
-
- if (!didBailout) {
- // Bubble up the earliest expiration time.
- if (enableProfilerTimer && (completedWork.mode & ProfileMode) !== NoMode) {
- // In profiling mode, resetChildExpirationTime is also used to reset
- // profiler durations.
- let actualDuration = completedWork.actualDuration;
- let treeBaseDuration = ((completedWork.selfBaseDuration: any): number);
-
- let child = completedWork.child;
- while (child !== null) {
- newChildLanes = mergeLanes(
- newChildLanes,
- mergeLanes(child.lanes, child.childLanes),
- );
-
- subtreeFlags |= child.subtreeFlags;
- subtreeFlags |= child.flags;
-
- // When a fiber is cloned, its actualDuration is reset to 0. This value will
- // only be updated if work is done on the fiber (i.e. it doesn't bailout).
- // When work is done, it should bubble to the parent's actualDuration. If
- // the fiber has not been cloned though, (meaning no work was done), then
- // this value will reflect the amount of time spent working on a previous
- // render. In that case it should not bubble. We determine whether it was
- // cloned by comparing the child pointer.
- // $FlowFixMe[unsafe-addition] addition with possible null/undefined value
- actualDuration += child.actualDuration;
-
- // $FlowFixMe[unsafe-addition] addition with possible null/undefined value
- treeBaseDuration += child.treeBaseDuration;
- child = child.sibling;
- }
-
- completedWork.actualDuration = actualDuration;
- completedWork.treeBaseDuration = treeBaseDuration;
- } else {
- let child = completedWork.child;
- while (child !== null) {
- newChildLanes = mergeLanes(
- newChildLanes,
- mergeLanes(child.lanes, child.childLanes),
- );
-
- subtreeFlags |= child.subtreeFlags;
- subtreeFlags |= child.flags;
-
- // Update the return pointer so the tree is consistent. This is a code
- // smell because it assumes the commit phase is never concurrent with
- // the render phase. Will address during refactor to alternate model.
- child.return = completedWork;
-
- child = child.sibling;
- }
- }
-
- completedWork.subtreeFlags |= subtreeFlags;
- } else {
- // Bubble up the earliest expiration time.
- if (enableProfilerTimer && (completedWork.mode & ProfileMode) !== NoMode) {
- // In profiling mode, resetChildExpirationTime is also used to reset
- // profiler durations.
- let treeBaseDuration = ((completedWork.selfBaseDuration: any): number);
-
- let child = completedWork.child;
- while (child !== null) {
- newChildLanes = mergeLanes(
- newChildLanes,
- mergeLanes(child.lanes, child.childLanes),
- );
-
- // "Static" flags share the lifetime of the fiber/hook they belong to,
- // so we should bubble those up even during a bailout. All the other
- // flags have a lifetime only of a single render + commit, so we should
- // ignore them.
- subtreeFlags |= child.subtreeFlags & StaticMask;
- subtreeFlags |= child.flags & StaticMask;
-
- // $FlowFixMe[unsafe-addition] addition with possible null/undefined value
- treeBaseDuration += child.treeBaseDuration;
- child = child.sibling;
- }
-
- completedWork.treeBaseDuration = treeBaseDuration;
- } else {
- let child = completedWork.child;
- while (child !== null) {
- newChildLanes = mergeLanes(
- newChildLanes,
- mergeLanes(child.lanes, child.childLanes),
- );
-
- // "Static" flags share the lifetime of the fiber/hook they belong to,
- // so we should bubble those up even during a bailout. All the other
- // flags have a lifetime only of a single render + commit, so we should
- // ignore them.
- subtreeFlags |= child.subtreeFlags & StaticMask;
- subtreeFlags |= child.flags & StaticMask;
-
- // Update the return pointer so the tree is consistent. This is a code
- // smell because it assumes the commit phase is never concurrent with
- // the render phase. Will address during refactor to alternate model.
- child.return = completedWork;
-
- child = child.sibling;
- }
- }
-
- completedWork.subtreeFlags |= subtreeFlags;
- }
-
- completedWork.childLanes = newChildLanes;
-
- return didBailout;
-}
-
-function completeDehydratedSuspenseBoundary(
- current: Fiber | null,
- workInProgress: Fiber,
- nextState: SuspenseState | null,
-): boolean {
- if (
- hasUnhydratedTailNodes() &&
- (workInProgress.mode & ConcurrentMode) !== NoMode &&
- (workInProgress.flags & DidCapture) === NoFlags
- ) {
- warnIfUnhydratedTailNodes(workInProgress);
- resetHydrationState();
- workInProgress.flags |= ForceClientRender | Incomplete | ShouldCapture;
-
- return false;
- }
-
- const wasHydrated = popHydrationState(workInProgress);
-
- if (nextState !== null && nextState.dehydrated !== null) {
- // We might be inside a hydration state the first time we're picking up this
- // Suspense boundary, and also after we've reentered it for further hydration.
- if (current === null) {
- if (!wasHydrated) {
- throw new Error(
- 'A dehydrated suspense component was completed without a hydrated node. ' +
- 'This is probably a bug in React.',
- );
- }
- prepareToHydrateHostSuspenseInstance(workInProgress);
- bubbleProperties(workInProgress);
- if (enableProfilerTimer) {
- if ((workInProgress.mode & ProfileMode) !== NoMode) {
- const isTimedOutSuspense = nextState !== null;
- if (isTimedOutSuspense) {
- // Don't count time spent in a timed out Suspense subtree as part of the base duration.
- const primaryChildFragment = workInProgress.child;
- if (primaryChildFragment !== null) {
- // $FlowFixMe Flow doesn't support type casting in combination with the -= operator
- workInProgress.treeBaseDuration -= ((primaryChildFragment.treeBaseDuration: any): number);
- }
- }
- }
- }
- return false;
- } else {
- // We might have reentered this boundary to hydrate it. If so, we need to reset the hydration
- // state since we're now exiting out of it. popHydrationState doesn't do that for us.
- resetHydrationState();
- if ((workInProgress.flags & DidCapture) === NoFlags) {
- // This boundary did not suspend so it's now hydrated and unsuspended.
- workInProgress.memoizedState = null;
- }
- // If nothing suspended, we need to schedule an effect to mark this boundary
- // as having hydrated so events know that they're free to be invoked.
- // It's also a signal to replay events and the suspense callback.
- // If something suspended, schedule an effect to attach retry listeners.
- // So we might as well always mark this.
- workInProgress.flags |= Update;
- bubbleProperties(workInProgress);
- if (enableProfilerTimer) {
- if ((workInProgress.mode & ProfileMode) !== NoMode) {
- const isTimedOutSuspense = nextState !== null;
- if (isTimedOutSuspense) {
- // Don't count time spent in a timed out Suspense subtree as part of the base duration.
- const primaryChildFragment = workInProgress.child;
- if (primaryChildFragment !== null) {
- // $FlowFixMe Flow doesn't support type casting in combination with the -= operator
- workInProgress.treeBaseDuration -= ((primaryChildFragment.treeBaseDuration: any): number);
- }
- }
- }
- }
- return false;
- }
- } else {
- // Successfully completed this tree. If this was a forced client render,
- // there may have been recoverable errors during first hydration
- // attempt. If so, add them to a queue so we can log them in the
- // commit phase.
- upgradeHydrationErrorsToRecoverable();
-
- // Fall through to normal Suspense path
- return true;
- }
-}
-
-function completeWork(
- current: Fiber | null,
- workInProgress: Fiber,
- renderLanes: Lanes,
-): Fiber | null {
- const newProps = workInProgress.pendingProps;
- // Note: This intentionally doesn't check if we're hydrating because comparing
- // to the current tree provider fiber is just as fast and less error-prone.
- // Ideally we would have a special version of the work loop only
- // for hydration.
- popTreeContext(workInProgress);
- switch (workInProgress.tag) {
- case IndeterminateComponent:
- case LazyComponent:
- case SimpleMemoComponent:
- case FunctionComponent:
- case ForwardRef:
- case Fragment:
- case Mode:
- case Profiler:
- case ContextConsumer:
- case MemoComponent:
- bubbleProperties(workInProgress);
- return null;
- case ClassComponent: {
- const Component = workInProgress.type;
- if (isLegacyContextProvider(Component)) {
- popLegacyContext(workInProgress);
- }
- bubbleProperties(workInProgress);
- return null;
- }
- case HostRoot: {
- const fiberRoot = (workInProgress.stateNode: FiberRoot);
-
- if (enableTransitionTracing) {
- const transitions = getWorkInProgressTransitions();
- // We set the Passive flag here because if there are new transitions,
- // we will need to schedule callbacks and process the transitions,
- // which we do in the passive phase
- if (transitions !== null) {
- workInProgress.flags |= Passive;
- }
- }
-
- if (enableCache) {
- let previousCache: Cache | null = null;
- if (current !== null) {
- previousCache = current.memoizedState.cache;
- }
- const cache: Cache = workInProgress.memoizedState.cache;
- if (cache !== previousCache) {
- // Run passive effects to retain/release the cache.
- workInProgress.flags |= Passive;
- }
- popCacheProvider(workInProgress, cache);
- }
-
- if (enableTransitionTracing) {
- popRootMarkerInstance(workInProgress);
- }
-
- popRootTransition(workInProgress, fiberRoot, renderLanes);
- popHostContainer(workInProgress);
- popTopLevelLegacyContextObject(workInProgress);
- resetMutableSourceWorkInProgressVersions();
- if (fiberRoot.pendingContext) {
- fiberRoot.context = fiberRoot.pendingContext;
- fiberRoot.pendingContext = null;
- }
- if (current === null || current.child === null) {
- // If we hydrated, pop so that we can delete any remaining children
- // that weren't hydrated.
- const wasHydrated = popHydrationState(workInProgress);
- if (wasHydrated) {
- // If we hydrated, then we'll need to schedule an update for
- // the commit side-effects on the root.
- markUpdate(workInProgress);
- } else {
- if (current !== null) {
- const prevState: RootState = current.memoizedState;
- if (
- // Check if this is a client root
- !prevState.isDehydrated ||
- // Check if we reverted to client rendering (e.g. due to an error)
- (workInProgress.flags & ForceClientRender) !== NoFlags
- ) {
- // Schedule an effect to clear this container at the start of the
- // next commit. This handles the case of React rendering into a
- // container with previous children. It's also safe to do for
- // updates too, because current.child would only be null if the
- // previous render was null (so the container would already
- // be empty).
- workInProgress.flags |= Snapshot;
-
- // If this was a forced client render, there may have been
- // recoverable errors during first hydration attempt. If so, add
- // them to a queue so we can log them in the commit phase.
- upgradeHydrationErrorsToRecoverable();
- }
- }
- }
- }
- updateHostContainer(current, workInProgress);
- bubbleProperties(workInProgress);
- if (enableTransitionTracing) {
- if ((workInProgress.subtreeFlags & Visibility) !== NoFlags) {
- // If any of our suspense children toggle visibility, this means that
- // the pending boundaries array needs to be updated, which we only
- // do in the passive phase.
- workInProgress.flags |= Passive;
- }
- }
- return null;
- }
- case HostResource: {
- if (enableFloat && supportsResources) {
- popHostContext(workInProgress);
- const currentRef = current ? current.ref : null;
- if (currentRef !== workInProgress.ref) {
- markRef(workInProgress);
- }
- if (
- current === null ||
- current.memoizedState !== workInProgress.memoizedState
- ) {
- // The workInProgress resource is different than the current one or the current
- // one does not exist
- markUpdate(workInProgress);
- }
- bubbleProperties(workInProgress);
- return null;
- }
- }
- // eslint-disable-next-line-no-fallthrough
- case HostSingleton: {
- if (enableHostSingletons && supportsSingletons) {
- popHostContext(workInProgress);
- const rootContainerInstance = getRootHostContainer();
- const type = workInProgress.type;
- if (current !== null && workInProgress.stateNode != null) {
- updateHostComponent(current, workInProgress, type, newProps);
-
- if (current.ref !== workInProgress.ref) {
- markRef(workInProgress);
- }
- } else {
- if (!newProps) {
- if (workInProgress.stateNode === null) {
- throw new Error(
- 'We must have new props for new mounts. This error is likely ' +
- 'caused by a bug in React. Please file an issue.',
- );
- }
-
- // This can happen when we abort work.
- bubbleProperties(workInProgress);
- return null;
- }
-
- const currentHostContext = getHostContext();
- const wasHydrated = popHydrationState(workInProgress);
- if (wasHydrated) {
- // We ignore the boolean indicating there is an updateQueue because
- // it is used only to set text children and HostSingletons do not
- // use them.
- prepareToHydrateHostInstance(workInProgress, currentHostContext);
- } else {
- workInProgress.stateNode = resolveSingletonInstance(
- type,
- newProps,
- rootContainerInstance,
- currentHostContext,
- true,
- );
- markUpdate(workInProgress);
- }
-
- if (workInProgress.ref !== null) {
- // If there is a ref on a host node we need to schedule a callback
- markRef(workInProgress);
- }
- }
- bubbleProperties(workInProgress);
- return null;
- }
- }
- // eslint-disable-next-line-no-fallthrough
- case HostComponent: {
- popHostContext(workInProgress);
- const type = workInProgress.type;
- if (current !== null && workInProgress.stateNode != null) {
- updateHostComponent(current, workInProgress, type, newProps);
-
- if (current.ref !== workInProgress.ref) {
- markRef(workInProgress);
- }
- } else {
- if (!newProps) {
- if (workInProgress.stateNode === null) {
- throw new Error(
- 'We must have new props for new mounts. This error is likely ' +
- 'caused by a bug in React. Please file an issue.',
- );
- }
-
- // This can happen when we abort work.
- bubbleProperties(workInProgress);
- return null;
- }
-
- const currentHostContext = getHostContext();
- // TODO: Move createInstance to beginWork and keep it on a context
- // "stack" as the parent. Then append children as we go in beginWork
- // or completeWork depending on whether we want to add them top->down or
- // bottom->up. Top->down is faster in IE11.
- const wasHydrated = popHydrationState(workInProgress);
- if (wasHydrated) {
- // TODO: Move this and createInstance step into the beginPhase
- // to consolidate.
- if (
- prepareToHydrateHostInstance(workInProgress, currentHostContext)
- ) {
- // If changes to the hydrated node need to be applied at the
- // commit-phase we mark this as such.
- markUpdate(workInProgress);
- }
- } else {
- const rootContainerInstance = getRootHostContainer();
- const instance = createInstance(
- type,
- newProps,
- rootContainerInstance,
- currentHostContext,
- workInProgress,
- );
- appendAllChildren(instance, workInProgress, false, false);
- workInProgress.stateNode = instance;
-
- // Certain renderers require commit-time effects for initial mount.
- // (eg DOM renderer supports auto-focus for certain elements).
- // Make sure such renderers get scheduled for later work.
- if (
- finalizeInitialChildren(
- instance,
- type,
- newProps,
- currentHostContext,
- )
- ) {
- markUpdate(workInProgress);
- }
- }
-
- if (workInProgress.ref !== null) {
- // If there is a ref on a host node we need to schedule a callback
- markRef(workInProgress);
- }
- }
- bubbleProperties(workInProgress);
- return null;
- }
- case HostText: {
- const newText = newProps;
- if (current && workInProgress.stateNode != null) {
- const oldText = current.memoizedProps;
- // If we have an alternate, that means this is an update and we need
- // to schedule a side-effect to do the updates.
- updateHostText(current, workInProgress, oldText, newText);
- } else {
- if (typeof newText !== 'string') {
- if (workInProgress.stateNode === null) {
- throw new Error(
- 'We must have new props for new mounts. This error is likely ' +
- 'caused by a bug in React. Please file an issue.',
- );
- }
- // This can happen when we abort work.
- }
- const rootContainerInstance = getRootHostContainer();
- const currentHostContext = getHostContext();
- const wasHydrated = popHydrationState(workInProgress);
- if (wasHydrated) {
- if (prepareToHydrateHostTextInstance(workInProgress)) {
- markUpdate(workInProgress);
- }
- } else {
- workInProgress.stateNode = createTextInstance(
- newText,
- rootContainerInstance,
- currentHostContext,
- workInProgress,
- );
- }
- }
- bubbleProperties(workInProgress);
- return null;
- }
- case SuspenseComponent: {
- popSuspenseHandler(workInProgress);
- const nextState: null | SuspenseState = workInProgress.memoizedState;
-
- // Special path for dehydrated boundaries. We may eventually move this
- // to its own fiber type so that we can add other kinds of hydration
- // boundaries that aren't associated with a Suspense tree. In anticipation
- // of such a refactor, all the hydration logic is contained in
- // this branch.
- if (
- current === null ||
- (current.memoizedState !== null &&
- current.memoizedState.dehydrated !== null)
- ) {
- const fallthroughToNormalSuspensePath = completeDehydratedSuspenseBoundary(
- current,
- workInProgress,
- nextState,
- );
- if (!fallthroughToNormalSuspensePath) {
- if (workInProgress.flags & ShouldCapture) {
- // Special case. There were remaining unhydrated nodes. We treat
- // this as a mismatch. Revert to client rendering.
- return workInProgress;
- } else {
- // Did not finish hydrating, either because this is the initial
- // render or because something suspended.
- return null;
- }
- }
-
- // Continue with the normal Suspense path.
- }
-
- if ((workInProgress.flags & DidCapture) !== NoFlags) {
- // Something suspended. Re-render with the fallback children.
- workInProgress.lanes = renderLanes;
- // Do not reset the effect list.
- if (
- enableProfilerTimer &&
- (workInProgress.mode & ProfileMode) !== NoMode
- ) {
- transferActualDuration(workInProgress);
- }
- // Don't bubble properties in this case.
- return workInProgress;
- }
-
- const nextDidTimeout = nextState !== null;
- const prevDidTimeout =
- current !== null &&
- (current.memoizedState: null | SuspenseState) !== null;
-
- if (enableCache && nextDidTimeout) {
- const offscreenFiber: Fiber = (workInProgress.child: any);
- let previousCache: Cache | null = null;
- if (
- offscreenFiber.alternate !== null &&
- offscreenFiber.alternate.memoizedState !== null &&
- offscreenFiber.alternate.memoizedState.cachePool !== null
- ) {
- previousCache = offscreenFiber.alternate.memoizedState.cachePool.pool;
- }
- let cache: Cache | null = null;
- if (
- offscreenFiber.memoizedState !== null &&
- offscreenFiber.memoizedState.cachePool !== null
- ) {
- cache = offscreenFiber.memoizedState.cachePool.pool;
- }
- if (cache !== previousCache) {
- // Run passive effects to retain/release the cache.
- offscreenFiber.flags |= Passive;
- }
- }
-
- // If the suspended state of the boundary changes, we need to schedule
- // a passive effect, which is when we process the transitions
- if (nextDidTimeout !== prevDidTimeout) {
- if (enableTransitionTracing) {
- const offscreenFiber: Fiber = (workInProgress.child: any);
- offscreenFiber.flags |= Passive;
- }
-
- // If the suspended state of the boundary changes, we need to schedule
- // an effect to toggle the subtree's visibility. When we switch from
- // fallback -> primary, the inner Offscreen fiber schedules this effect
- // as part of its normal complete phase. But when we switch from
- // primary -> fallback, the inner Offscreen fiber does not have a complete
- // phase. So we need to schedule its effect here.
- //
- // We also use this flag to connect/disconnect the effects, but the same
- // logic applies: when re-connecting, the Offscreen fiber's complete
- // phase will handle scheduling the effect. It's only when the fallback
- // is active that we have to do anything special.
- if (nextDidTimeout) {
- const offscreenFiber: Fiber = (workInProgress.child: any);
- offscreenFiber.flags |= Visibility;
-
- // TODO: This will still suspend a synchronous tree if anything
- // in the concurrent tree already suspended during this render.
- // This is a known bug.
- if ((workInProgress.mode & ConcurrentMode) !== NoMode) {
- // TODO: Move this back to throwException because this is too late
- // if this is a large tree which is common for initial loads. We
- // don't know if we should restart a render or not until we get
- // this marker, and this is too late.
- // If this render already had a ping or lower pri updates,
- // and this is the first time we know we're going to suspend we
- // should be able to immediately restart from within throwException.
- if (isBadSuspenseFallback(current, newProps)) {
- renderDidSuspendDelayIfPossible();
- } else {
- renderDidSuspend();
- }
- }
- }
- }
-
- const wakeables: Set | null = (workInProgress.updateQueue: any);
- if (wakeables !== null) {
- // Schedule an effect to attach a retry listener to the promise.
- // TODO: Move to passive phase
- workInProgress.flags |= Update;
- }
-
- if (
- enableSuspenseCallback &&
- workInProgress.updateQueue !== null &&
- workInProgress.memoizedProps.suspenseCallback != null
- ) {
- // Always notify the callback
- // TODO: Move to passive phase
- workInProgress.flags |= Update;
- }
- bubbleProperties(workInProgress);
- if (enableProfilerTimer) {
- if ((workInProgress.mode & ProfileMode) !== NoMode) {
- if (nextDidTimeout) {
- // Don't count time spent in a timed out Suspense subtree as part of the base duration.
- const primaryChildFragment = workInProgress.child;
- if (primaryChildFragment !== null) {
- // $FlowFixMe Flow doesn't support type casting in combination with the -= operator
- workInProgress.treeBaseDuration -= ((primaryChildFragment.treeBaseDuration: any): number);
- }
- }
- }
- }
- return null;
- }
- case HostPortal:
- popHostContainer(workInProgress);
- updateHostContainer(current, workInProgress);
- if (current === null) {
- preparePortalMount(workInProgress.stateNode.containerInfo);
- }
- bubbleProperties(workInProgress);
- return null;
- case ContextProvider:
- // Pop provider fiber
- const context: ReactContext = workInProgress.type._context;
- popProvider(context, workInProgress);
- bubbleProperties(workInProgress);
- return null;
- case IncompleteClassComponent: {
- // Same as class component case. I put it down here so that the tags are
- // sequential to ensure this switch is compiled to a jump table.
- const Component = workInProgress.type;
- if (isLegacyContextProvider(Component)) {
- popLegacyContext(workInProgress);
- }
- bubbleProperties(workInProgress);
- return null;
- }
- case SuspenseListComponent: {
- popSuspenseListContext(workInProgress);
-
- const renderState: null | SuspenseListRenderState =
- workInProgress.memoizedState;
-
- if (renderState === null) {
- // We're running in the default, "independent" mode.
- // We don't do anything in this mode.
- bubbleProperties(workInProgress);
- return null;
- }
-
- let didSuspendAlready = (workInProgress.flags & DidCapture) !== NoFlags;
-
- const renderedTail = renderState.rendering;
- if (renderedTail === null) {
- // We just rendered the head.
- if (!didSuspendAlready) {
- // This is the first pass. We need to figure out if anything is still
- // suspended in the rendered set.
-
- // If new content unsuspended, but there's still some content that
- // didn't. Then we need to do a second pass that forces everything
- // to keep showing their fallbacks.
-
- // We might be suspended if something in this render pass suspended, or
- // something in the previous committed pass suspended. Otherwise,
- // there's no chance so we can skip the expensive call to
- // findFirstSuspended.
- const cannotBeSuspended =
- renderHasNotSuspendedYet() &&
- (current === null || (current.flags & DidCapture) === NoFlags);
- if (!cannotBeSuspended) {
- let row = workInProgress.child;
- while (row !== null) {
- const suspended = findFirstSuspended(row);
- if (suspended !== null) {
- didSuspendAlready = true;
- workInProgress.flags |= DidCapture;
- cutOffTailIfNeeded(renderState, false);
-
- // If this is a newly suspended tree, it might not get committed as
- // part of the second pass. In that case nothing will subscribe to
- // its thenables. Instead, we'll transfer its thenables to the
- // SuspenseList so that it can retry if they resolve.
- // There might be multiple of these in the list but since we're
- // going to wait for all of them anyway, it doesn't really matter
- // which ones gets to ping. In theory we could get clever and keep
- // track of how many dependencies remain but it gets tricky because
- // in the meantime, we can add/remove/change items and dependencies.
- // We might bail out of the loop before finding any but that
- // doesn't matter since that means that the other boundaries that
- // we did find already has their listeners attached.
- const newThenables = suspended.updateQueue;
- if (newThenables !== null) {
- workInProgress.updateQueue = newThenables;
- workInProgress.flags |= Update;
- }
-
- // Rerender the whole list, but this time, we'll force fallbacks
- // to stay in place.
- // Reset the effect flags before doing the second pass since that's now invalid.
- // Reset the child fibers to their original state.
- workInProgress.subtreeFlags = NoFlags;
- resetChildFibers(workInProgress, renderLanes);
-
- // Set up the Suspense List Context to force suspense and
- // immediately rerender the children.
- pushSuspenseListContext(
- workInProgress,
- setShallowSuspenseListContext(
- suspenseStackCursor.current,
- ForceSuspenseFallback,
- ),
- );
- // Don't bubble properties in this case.
- return workInProgress.child;
- }
- row = row.sibling;
- }
- }
-
- if (renderState.tail !== null && now() > getRenderTargetTime()) {
- // We have already passed our CPU deadline but we still have rows
- // left in the tail. We'll just give up further attempts to render
- // the main content and only render fallbacks.
- workInProgress.flags |= DidCapture;
- didSuspendAlready = true;
-
- cutOffTailIfNeeded(renderState, false);
-
- // Since nothing actually suspended, there will nothing to ping this
- // to get it started back up to attempt the next item. While in terms
- // of priority this work has the same priority as this current render,
- // it's not part of the same transition once the transition has
- // committed. If it's sync, we still want to yield so that it can be
- // painted. Conceptually, this is really the same as pinging.
- // We can use any RetryLane even if it's the one currently rendering
- // since we're leaving it behind on this node.
- workInProgress.lanes = SomeRetryLane;
- }
- } else {
- cutOffTailIfNeeded(renderState, false);
- }
- // Next we're going to render the tail.
- } else {
- // Append the rendered row to the child list.
- if (!didSuspendAlready) {
- const suspended = findFirstSuspended(renderedTail);
- if (suspended !== null) {
- workInProgress.flags |= DidCapture;
- didSuspendAlready = true;
-
- // Ensure we transfer the update queue to the parent so that it doesn't
- // get lost if this row ends up dropped during a second pass.
- const newThenables = suspended.updateQueue;
- if (newThenables !== null) {
- workInProgress.updateQueue = newThenables;
- workInProgress.flags |= Update;
- }
-
- cutOffTailIfNeeded(renderState, true);
- // This might have been modified.
- if (
- renderState.tail === null &&
- renderState.tailMode === 'hidden' &&
- !renderedTail.alternate &&
- !getIsHydrating() // We don't cut it if we're hydrating.
- ) {
- // We're done.
- bubbleProperties(workInProgress);
- return null;
- }
- } else if (
- // The time it took to render last row is greater than the remaining
- // time we have to render. So rendering one more row would likely
- // exceed it.
- now() * 2 - renderState.renderingStartTime >
- getRenderTargetTime() &&
- renderLanes !== OffscreenLane
- ) {
- // We have now passed our CPU deadline and we'll just give up further
- // attempts to render the main content and only render fallbacks.
- // The assumption is that this is usually faster.
- workInProgress.flags |= DidCapture;
- didSuspendAlready = true;
-
- cutOffTailIfNeeded(renderState, false);
-
- // Since nothing actually suspended, there will nothing to ping this
- // to get it started back up to attempt the next item. While in terms
- // of priority this work has the same priority as this current render,
- // it's not part of the same transition once the transition has
- // committed. If it's sync, we still want to yield so that it can be
- // painted. Conceptually, this is really the same as pinging.
- // We can use any RetryLane even if it's the one currently rendering
- // since we're leaving it behind on this node.
- workInProgress.lanes = SomeRetryLane;
- }
- }
- if (renderState.isBackwards) {
- // The effect list of the backwards tail will have been added
- // to the end. This breaks the guarantee that life-cycles fire in
- // sibling order but that isn't a strong guarantee promised by React.
- // Especially since these might also just pop in during future commits.
- // Append to the beginning of the list.
- renderedTail.sibling = workInProgress.child;
- workInProgress.child = renderedTail;
- } else {
- const previousSibling = renderState.last;
- if (previousSibling !== null) {
- previousSibling.sibling = renderedTail;
- } else {
- workInProgress.child = renderedTail;
- }
- renderState.last = renderedTail;
- }
- }
-
- if (renderState.tail !== null) {
- // We still have tail rows to render.
- // Pop a row.
- const next = renderState.tail;
- renderState.rendering = next;
- renderState.tail = next.sibling;
- renderState.renderingStartTime = now();
- next.sibling = null;
-
- // Restore the context.
- // TODO: We can probably just avoid popping it instead and only
- // setting it the first time we go from not suspended to suspended.
- let suspenseContext = suspenseStackCursor.current;
- if (didSuspendAlready) {
- suspenseContext = setShallowSuspenseListContext(
- suspenseContext,
- ForceSuspenseFallback,
- );
- } else {
- suspenseContext = setDefaultShallowSuspenseListContext(
- suspenseContext,
- );
- }
- pushSuspenseListContext(workInProgress, suspenseContext);
- // Do a pass over the next row.
- // Don't bubble properties in this case.
- return next;
- }
- bubbleProperties(workInProgress);
- return null;
- }
- case ScopeComponent: {
- if (enableScopeAPI) {
- if (current === null) {
- const scopeInstance: ReactScopeInstance = createScopeInstance();
- workInProgress.stateNode = scopeInstance;
- prepareScopeUpdate(scopeInstance, workInProgress);
- if (workInProgress.ref !== null) {
- markRef(workInProgress);
- markUpdate(workInProgress);
- }
- } else {
- if (workInProgress.ref !== null) {
- markUpdate(workInProgress);
- }
- if (current.ref !== workInProgress.ref) {
- markRef(workInProgress);
- }
- }
- bubbleProperties(workInProgress);
- return null;
- }
- break;
- }
- case OffscreenComponent:
- case LegacyHiddenComponent: {
- popSuspenseHandler(workInProgress);
- popHiddenContext(workInProgress);
- const nextState: OffscreenState | null = workInProgress.memoizedState;
- const nextIsHidden = nextState !== null;
-
- // Schedule a Visibility effect if the visibility has changed
- if (enableLegacyHidden && workInProgress.tag === LegacyHiddenComponent) {
- // LegacyHidden doesn't do any hiding — it only pre-renders.
- } else {
- if (current !== null) {
- const prevState: OffscreenState | null = current.memoizedState;
- const prevIsHidden = prevState !== null;
- if (prevIsHidden !== nextIsHidden) {
- workInProgress.flags |= Visibility;
- }
- } else {
- // On initial mount, we only need a Visibility effect if the tree
- // is hidden.
- if (nextIsHidden) {
- workInProgress.flags |= Visibility;
- }
- }
- }
-
- if (!nextIsHidden || (workInProgress.mode & ConcurrentMode) === NoMode) {
- bubbleProperties(workInProgress);
- } else {
- // Don't bubble properties for hidden children unless we're rendering
- // at offscreen priority.
- if (
- includesSomeLane(renderLanes, (OffscreenLane: Lane)) &&
- // Also don't bubble if the tree suspended
- (workInProgress.flags & DidCapture) === NoLanes
- ) {
- bubbleProperties(workInProgress);
- // Check if there was an insertion or update in the hidden subtree.
- // If so, we need to hide those nodes in the commit phase, so
- // schedule a visibility effect.
- if (
- (!enableLegacyHidden ||
- workInProgress.tag !== LegacyHiddenComponent) &&
- workInProgress.subtreeFlags & (Placement | Update)
- ) {
- workInProgress.flags |= Visibility;
- }
- }
- }
-
- if (workInProgress.updateQueue !== null) {
- // Schedule an effect to attach Suspense retry listeners
- // TODO: Move to passive phase
- workInProgress.flags |= Update;
- }
-
- if (enableCache) {
- let previousCache: Cache | null = null;
- if (
- current !== null &&
- current.memoizedState !== null &&
- current.memoizedState.cachePool !== null
- ) {
- previousCache = current.memoizedState.cachePool.pool;
- }
- let cache: Cache | null = null;
- if (
- workInProgress.memoizedState !== null &&
- workInProgress.memoizedState.cachePool !== null
- ) {
- cache = workInProgress.memoizedState.cachePool.pool;
- }
- if (cache !== previousCache) {
- // Run passive effects to retain/release the cache.
- workInProgress.flags |= Passive;
- }
- }
-
- popTransition(workInProgress, current);
-
- return null;
- }
- case CacheComponent: {
- if (enableCache) {
- let previousCache: Cache | null = null;
- if (current !== null) {
- previousCache = current.memoizedState.cache;
- }
- const cache: Cache = workInProgress.memoizedState.cache;
- if (cache !== previousCache) {
- // Run passive effects to retain/release the cache.
- workInProgress.flags |= Passive;
- }
- popCacheProvider(workInProgress, cache);
- bubbleProperties(workInProgress);
- }
- return null;
- }
- case TracingMarkerComponent: {
- if (enableTransitionTracing) {
- const instance: TracingMarkerInstance | null = workInProgress.stateNode;
- if (instance !== null) {
- popMarkerInstance(workInProgress);
- }
- bubbleProperties(workInProgress);
- }
- return null;
- }
- }
-
- throw new Error(
- `Unknown unit of work tag (${workInProgress.tag}). This error is likely caused by a bug in ` +
- 'React. Please file an issue.',
- );
-}
-
-export {completeWork};
diff --git a/packages/react-reconciler/src/ReactFiberConcurrentUpdates.new.js b/packages/react-reconciler/src/ReactFiberConcurrentUpdates.new.js
deleted file mode 100644
index 261bc518fc052..0000000000000
--- a/packages/react-reconciler/src/ReactFiberConcurrentUpdates.new.js
+++ /dev/null
@@ -1,288 +0,0 @@
-/**
- * Copyright (c) Meta Platforms, Inc. and affiliates.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- *
- * @flow
- */
-
-import type {Fiber, FiberRoot} from './ReactInternalTypes';
-import type {
- UpdateQueue as HookQueue,
- Update as HookUpdate,
-} from './ReactFiberHooks.new';
-import type {
- SharedQueue as ClassQueue,
- Update as ClassUpdate,
-} from './ReactFiberClassUpdateQueue.new';
-import type {Lane, Lanes} from './ReactFiberLane.new';
-import type {OffscreenInstance} from './ReactFiberOffscreenComponent';
-
-import {
- warnAboutUpdateOnNotYetMountedFiberInDEV,
- throwIfInfiniteUpdateLoopDetected,
- getWorkInProgressRoot,
-} from './ReactFiberWorkLoop.new';
-import {
- NoLane,
- NoLanes,
- mergeLanes,
- markHiddenUpdate,
-} from './ReactFiberLane.new';
-import {NoFlags, Placement, Hydrating} from './ReactFiberFlags';
-import {HostRoot, OffscreenComponent} from './ReactWorkTags';
-import {OffscreenVisible} from './ReactFiberOffscreenComponent';
-
-export type ConcurrentUpdate = {
- next: ConcurrentUpdate,
- lane: Lane,
-};
-
-type ConcurrentQueue = {
- pending: ConcurrentUpdate | null,
-};
-
-// If a render is in progress, and we receive an update from a concurrent event,
-// we wait until the current render is over (either finished or interrupted)
-// before adding it to the fiber/hook queue. Push to this array so we can
-// access the queue, fiber, update, et al later.
-const concurrentQueues: Array = [];
-let concurrentQueuesIndex = 0;
-
-let concurrentlyUpdatedLanes: Lanes = NoLanes;
-
-export function finishQueueingConcurrentUpdates(): void {
- const endIndex = concurrentQueuesIndex;
- concurrentQueuesIndex = 0;
-
- concurrentlyUpdatedLanes = NoLanes;
-
- let i = 0;
- while (i < endIndex) {
- const fiber: Fiber = concurrentQueues[i];
- concurrentQueues[i++] = null;
- const queue: ConcurrentQueue = concurrentQueues[i];
- concurrentQueues[i++] = null;
- const update: ConcurrentUpdate = concurrentQueues[i];
- concurrentQueues[i++] = null;
- const lane: Lane = concurrentQueues[i];
- concurrentQueues[i++] = null;
-
- if (queue !== null && update !== null) {
- const pending = queue.pending;
- if (pending === null) {
- // This is the first update. Create a circular list.
- update.next = update;
- } else {
- update.next = pending.next;
- pending.next = update;
- }
- queue.pending = update;
- }
-
- if (lane !== NoLane) {
- markUpdateLaneFromFiberToRoot(fiber, update, lane);
- }
- }
-}
-
-export function getConcurrentlyUpdatedLanes(): Lanes {
- return concurrentlyUpdatedLanes;
-}
-
-function enqueueUpdate(
- fiber: Fiber,
- queue: ConcurrentQueue | null,
- update: ConcurrentUpdate | null,
- lane: Lane,
-) {
- // Don't update the `childLanes` on the return path yet. If we already in
- // the middle of rendering, wait until after it has completed.
- concurrentQueues[concurrentQueuesIndex++] = fiber;
- concurrentQueues[concurrentQueuesIndex++] = queue;
- concurrentQueues[concurrentQueuesIndex++] = update;
- concurrentQueues[concurrentQueuesIndex++] = lane;
-
- concurrentlyUpdatedLanes = mergeLanes(concurrentlyUpdatedLanes, lane);
-
- // The fiber's `lane` field is used in some places to check if any work is
- // scheduled, to perform an eager bailout, so we need to update it immediately.
- // TODO: We should probably move this to the "shared" queue instead.
- fiber.lanes = mergeLanes(fiber.lanes, lane);
- const alternate = fiber.alternate;
- if (alternate !== null) {
- alternate.lanes = mergeLanes(alternate.lanes, lane);
- }
-}
-
-export function enqueueConcurrentHookUpdate(
- fiber: Fiber,
- queue: HookQueue,
- update: HookUpdate,
- lane: Lane,
-): FiberRoot | null {
- const concurrentQueue: ConcurrentQueue = (queue: any);
- const concurrentUpdate: ConcurrentUpdate = (update: any);
- enqueueUpdate(fiber, concurrentQueue, concurrentUpdate, lane);
- return getRootForUpdatedFiber(fiber);
-}
-
-export function enqueueConcurrentHookUpdateAndEagerlyBailout(
- fiber: Fiber,
- queue: HookQueue,
- update: HookUpdate,
-): void {
- // This function is used to queue an update that doesn't need a rerender. The
- // only reason we queue it is in case there's a subsequent higher priority
- // update that causes it to be rebased.
- const lane = NoLane;
- const concurrentQueue: ConcurrentQueue = (queue: any);
- const concurrentUpdate: ConcurrentUpdate = (update: any);
- enqueueUpdate(fiber, concurrentQueue, concurrentUpdate, lane);
-
- // Usually we can rely on the upcoming render phase to process the concurrent
- // queue. However, since this is a bail out, we're not scheduling any work
- // here. So the update we just queued will leak until something else happens
- // to schedule work (if ever).
- //
- // Check if we're currently in the middle of rendering a tree, and if not,
- // process the queue immediately to prevent a leak.
- const isConcurrentlyRendering = getWorkInProgressRoot() !== null;
- if (!isConcurrentlyRendering) {
- finishQueueingConcurrentUpdates();
- }
-}
-
-export function enqueueConcurrentClassUpdate(
- fiber: Fiber,
- queue: ClassQueue,
- update: ClassUpdate,
- lane: Lane,
-): FiberRoot | null {
- const concurrentQueue: ConcurrentQueue = (queue: any);
- const concurrentUpdate: ConcurrentUpdate = (update: any);
- enqueueUpdate(fiber, concurrentQueue, concurrentUpdate, lane);
- return getRootForUpdatedFiber(fiber);
-}
-
-export function enqueueConcurrentRenderForLane(
- fiber: Fiber,
- lane: Lane,
-): FiberRoot | null {
- enqueueUpdate(fiber, null, null, lane);
- return getRootForUpdatedFiber(fiber);
-}
-
-// Calling this function outside this module should only be done for backwards
-// compatibility and should always be accompanied by a warning.
-export function unsafe_markUpdateLaneFromFiberToRoot(
- sourceFiber: Fiber,
- lane: Lane,
-): FiberRoot | null {
- // NOTE: For Hyrum's Law reasons, if an infinite update loop is detected, it
- // should throw before `markUpdateLaneFromFiberToRoot` is called. But this is
- // undefined behavior and we can change it if we need to; it just so happens
- // that, at the time of this writing, there's an internal product test that
- // happens to rely on this.
- const root = getRootForUpdatedFiber(sourceFiber);
- markUpdateLaneFromFiberToRoot(sourceFiber, null, lane);
- return root;
-}
-
-function markUpdateLaneFromFiberToRoot(
- sourceFiber: Fiber,
- update: ConcurrentUpdate | null,
- lane: Lane,
-): void {
- // Update the source fiber's lanes
- sourceFiber.lanes = mergeLanes(sourceFiber.lanes, lane);
- let alternate = sourceFiber.alternate;
- if (alternate !== null) {
- alternate.lanes = mergeLanes(alternate.lanes, lane);
- }
- // Walk the parent path to the root and update the child lanes.
- let isHidden = false;
- let parent = sourceFiber.return;
- let node = sourceFiber;
- while (parent !== null) {
- parent.childLanes = mergeLanes(parent.childLanes, lane);
- alternate = parent.alternate;
- if (alternate !== null) {
- alternate.childLanes = mergeLanes(alternate.childLanes, lane);
- }
-
- if (parent.tag === OffscreenComponent) {
- // Check if this offscreen boundary is currently hidden.
- //
- // The instance may be null if the Offscreen parent was unmounted. Usually
- // the parent wouldn't be reachable in that case because we disconnect
- // fibers from the tree when they are deleted. However, there's a weird
- // edge case where setState is called on a fiber that was interrupted
- // before it ever mounted. Because it never mounts, it also never gets
- // deleted. Because it never gets deleted, its return pointer never gets
- // disconnected. Which means it may be attached to a deleted Offscreen
- // parent node. (This discovery suggests it may be better for memory usage
- // if we don't attach the `return` pointer until the commit phase, though
- // in order to do that we'd need some other way to track the return
- // pointer during the initial render, like on the stack.)
- //
- // This case is always accompanied by a warning, but we still need to
- // account for it. (There may be other cases that we haven't discovered,
- // too.)
- const offscreenInstance: OffscreenInstance | null = parent.stateNode;
- if (
- offscreenInstance !== null &&
- !(offscreenInstance._visibility & OffscreenVisible)
- ) {
- isHidden = true;
- }
- }
-
- node = parent;
- parent = parent.return;
- }
-
- if (isHidden && update !== null && node.tag === HostRoot) {
- const root: FiberRoot = node.stateNode;
- markHiddenUpdate(root, update, lane);
- }
-}
-
-function getRootForUpdatedFiber(sourceFiber: Fiber): FiberRoot | null {
- // TODO: We will detect and infinite update loop and throw even if this fiber
- // has already unmounted. This isn't really necessary but it happens to be the
- // current behavior we've used for several release cycles. Consider not
- // performing this check if the updated fiber already unmounted, since it's
- // not possible for that to cause an infinite update loop.
- throwIfInfiniteUpdateLoopDetected();
-
- // When a setState happens, we must ensure the root is scheduled. Because
- // update queues do not have a backpointer to the root, the only way to do
- // this currently is to walk up the return path. This used to not be a big
- // deal because we would have to walk up the return path to set
- // the `childLanes`, anyway, but now those two traversals happen at
- // different times.
- // TODO: Consider adding a `root` backpointer on the update queue.
- detectUpdateOnUnmountedFiber(sourceFiber, sourceFiber);
- let node = sourceFiber;
- let parent = node.return;
- while (parent !== null) {
- detectUpdateOnUnmountedFiber(sourceFiber, node);
- node = parent;
- parent = node.return;
- }
- return node.tag === HostRoot ? (node.stateNode: FiberRoot) : null;
-}
-
-function detectUpdateOnUnmountedFiber(sourceFiber: Fiber, parent: Fiber) {
- if (__DEV__) {
- const alternate = parent.alternate;
- if (
- alternate === null &&
- (parent.flags & (Placement | Hydrating)) !== NoFlags
- ) {
- warnAboutUpdateOnNotYetMountedFiberInDEV(sourceFiber);
- }
- }
-}
diff --git a/packages/react-reconciler/src/ReactFiberContext.new.js b/packages/react-reconciler/src/ReactFiberContext.new.js
deleted file mode 100644
index 6baef778e6c3e..0000000000000
--- a/packages/react-reconciler/src/ReactFiberContext.new.js
+++ /dev/null
@@ -1,343 +0,0 @@
-/**
- * Copyright (c) Meta Platforms, Inc. and affiliates.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- *
- * @flow
- */
-
-import type {Fiber} from './ReactInternalTypes';
-import type {StackCursor} from './ReactFiberStack.new';
-
-import {isFiberMounted} from './ReactFiberTreeReflection';
-import {disableLegacyContext} from 'shared/ReactFeatureFlags';
-import {ClassComponent, HostRoot} from './ReactWorkTags';
-import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
-import checkPropTypes from 'shared/checkPropTypes';
-
-import {createCursor, push, pop} from './ReactFiberStack.new';
-
-let warnedAboutMissingGetChildContext;
-
-if (__DEV__) {
- warnedAboutMissingGetChildContext = {};
-}
-
-// $FlowFixMe[incompatible-exact]
-export const emptyContextObject: {} = {};
-if (__DEV__) {
- Object.freeze(emptyContextObject);
-}
-
-// A cursor to the current merged context object on the stack.
-const contextStackCursor: StackCursor