diff --git a/packages/react-reconciler/src/ReactChildFiber.new.js b/packages/react-reconciler/src/ReactChildFiber.new.js
new file mode 100644
index 0000000000000..3034c4c69e923
--- /dev/null
+++ b/packages/react-reconciler/src/ReactChildFiber.new.js
@@ -0,0 +1,1490 @@
+/**
+ * Copyright (c) Facebook, Inc. and its 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 {BlockComponent} from 'react/src/ReactBlock';
+import type {LazyComponent} from 'react/src/ReactLazy';
+import type {Fiber} from './ReactInternalTypes';
+import type {ExpirationTime} from './ReactFiberExpirationTime';
+
+import getComponentName from 'shared/getComponentName';
+import {Placement, Deletion} from './ReactSideEffectTags';
+import {
+ getIteratorFn,
+ REACT_ELEMENT_TYPE,
+ REACT_FRAGMENT_TYPE,
+ REACT_PORTAL_TYPE,
+ REACT_LAZY_TYPE,
+ REACT_BLOCK_TYPE,
+} from 'shared/ReactSymbols';
+import {
+ FunctionComponent,
+ ClassComponent,
+ HostText,
+ HostPortal,
+ Fragment,
+ Block,
+} from './ReactWorkTags';
+import invariant from 'shared/invariant';
+import {warnAboutStringRefs, enableBlocksAPI} from 'shared/ReactFeatureFlags';
+
+import {
+ createWorkInProgress,
+ resetWorkInProgress,
+ createFiberFromElement,
+ createFiberFromFragment,
+ createFiberFromText,
+ createFiberFromPortal,
+} from './ReactFiber.new';
+import {emptyRefsObject} from './ReactFiberClassComponent.new';
+import {getStackByFiberInDevAndProd} from './ReactFiberComponentStack';
+import {getCurrentFiberStackInDev} from './ReactCurrentFiber';
+import {isCompatibleFamilyForHotReloading} from './ReactFiberHotReloading.new';
+import {StrictMode} from './ReactTypeOfMode';
+
+let didWarnAboutMaps;
+let didWarnAboutGenerators;
+let didWarnAboutStringRefs;
+let ownerHasKeyUseWarning;
+let ownerHasFunctionTypeWarning;
+let warnForMissingKey = (child: mixed) => {};
+
+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) => {
+ if (child === null || typeof child !== 'object') {
+ return;
+ }
+ if (!child._store || child._store.validated || child.key != null) {
+ return;
+ }
+ invariant(
+ typeof child._store === 'object',
+ 'React Component in warnForMissingKey should have a _store. ' +
+ 'This error is likely caused by a bug in React. Please file an issue.',
+ );
+ child._store.validated = true;
+
+ const currentComponentErrorInfo =
+ 'Each child in a list should have a unique ' +
+ '"key" prop. See https://fb.me/react-warning-keys for ' +
+ 'more information.' +
+ getCurrentFiberStackInDev();
+ if (ownerHasKeyUseWarning[currentComponentErrorInfo]) {
+ return;
+ }
+ ownerHasKeyUseWarning[currentComponentErrorInfo] = true;
+
+ console.error(
+ 'Each child in a list should have a unique ' +
+ '"key" prop. See https://fb.me/react-warning-keys for ' +
+ 'more information.',
+ );
+ };
+}
+
+const isArray = Array.isArray;
+
+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 & StrictMode || 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
+ )
+ ) {
+ const componentName = getComponentName(returnFiber.type) || '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://fb.me/react-strict-mode-string-ref%s',
+ componentName,
+ mixedRef,
+ getStackByFiberInDevAndProd(returnFiber),
+ );
+ } 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://fb.me/react-strict-mode-string-ref%s',
+ mixedRef,
+ getStackByFiberInDevAndProd(returnFiber),
+ );
+ }
+ didWarnAboutStringRefs[componentName] = true;
+ }
+ }
+ }
+
+ if (element._owner) {
+ const owner: ?Fiber = (element._owner: any);
+ let inst;
+ if (owner) {
+ const ownerFiber = ((owner: any): Fiber);
+ invariant(
+ ownerFiber.tag === ClassComponent,
+ 'Function components cannot have string refs. ' +
+ 'We recommend using useRef() instead. ' +
+ 'Learn more about using refs safely here: ' +
+ 'https://fb.me/react-strict-mode-string-ref',
+ );
+ inst = ownerFiber.stateNode;
+ }
+ invariant(
+ inst,
+ 'Missing owner for string ref %s. This error is likely caused by a ' +
+ 'bug in React. Please file an issue.',
+ mixedRef,
+ );
+ 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) {
+ let refs = inst.refs;
+ if (refs === emptyRefsObject) {
+ // This is a lazy pooled frozen object, so we need to initialize.
+ refs = inst.refs = {};
+ }
+ if (value === null) {
+ delete refs[stringRef];
+ } else {
+ refs[stringRef] = value;
+ }
+ };
+ ref._stringRef = stringRef;
+ return ref;
+ } else {
+ invariant(
+ typeof mixedRef === 'string',
+ 'Expected ref to be a function, a string, an object returned by React.createRef(), or null.',
+ );
+ invariant(
+ element._owner,
+ 'Element ref was specified as a string (%s) 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://fb.me/react-refs-must-have-owner for more information.',
+ mixedRef,
+ );
+ }
+ }
+ return mixedRef;
+}
+
+function throwOnInvalidObjectType(returnFiber: Fiber, newChild: Object) {
+ if (returnFiber.type !== 'textarea') {
+ let addendum = '';
+ if (__DEV__) {
+ addendum =
+ ' If you meant to render a collection of children, use an array ' +
+ 'instead.' +
+ getCurrentFiberStackInDev();
+ }
+ invariant(
+ false,
+ 'Objects are not valid as a React child (found: %s).%s',
+ Object.prototype.toString.call(newChild) === '[object Object]'
+ ? 'object with keys {' + Object.keys(newChild).join(', ') + '}'
+ : newChild,
+ addendum,
+ );
+ }
+}
+
+function warnOnFunctionType() {
+ if (__DEV__) {
+ const currentComponentErrorInfo =
+ '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.' +
+ getCurrentFiberStackInDev();
+
+ if (ownerHasFunctionTypeWarning[currentComponentErrorInfo]) {
+ return;
+ }
+ ownerHasFunctionTypeWarning[currentComponentErrorInfo] = 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.',
+ );
+ }
+}
+
+// We avoid inlining this to avoid potential deopts from using try/catch.
+/** @noinline */
+function resolveLazyType(
+ lazyComponent: LazyComponent,
+): LazyComponent | T {
+ try {
+ // If we can, let's peek at the resulting type.
+ const payload = lazyComponent._payload;
+ const init = lazyComponent._init;
+ return init(payload);
+ } catch (x) {
+ // Leave it in place and let it throw again in the begin phase.
+ return lazyComponent;
+ }
+}
+
+// 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 ChildReconciler(shouldTrackSideEffects) {
+ function deleteChild(returnFiber: Fiber, childToDelete: Fiber): void {
+ if (!shouldTrackSideEffects) {
+ // Noop.
+ return;
+ }
+ // Deletions are added in reversed order so we add it to the front.
+ // At this point, the return fiber's effect list is empty except for
+ // deletions, so we can just append the deletion to the list. The remaining
+ // effects aren't added until the complete phase. Once we implement
+ // resuming, this may not be true.
+ const last = returnFiber.lastEffect;
+ if (last !== null) {
+ last.nextEffect = childToDelete;
+ returnFiber.lastEffect = childToDelete;
+ } else {
+ returnFiber.firstEffect = returnFiber.lastEffect = childToDelete;
+ }
+ childToDelete.nextEffect = null;
+ childToDelete.effectTag = Deletion;
+ }
+
+ 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 = 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) {
+ // Noop.
+ return lastPlacedIndex;
+ }
+ const current = newFiber.alternate;
+ if (current !== null) {
+ const oldIndex = current.index;
+ if (oldIndex < lastPlacedIndex) {
+ // This is a move.
+ newFiber.effectTag = Placement;
+ return lastPlacedIndex;
+ } else {
+ // This item can stay in place.
+ return oldIndex;
+ }
+ } else {
+ // This is an insertion.
+ newFiber.effectTag = Placement;
+ 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.effectTag = Placement;
+ }
+ return newFiber;
+ }
+
+ function updateTextNode(
+ returnFiber: Fiber,
+ current: Fiber | null,
+ textContent: string,
+ expirationTime: ExpirationTime,
+ ) {
+ if (current === null || current.tag !== HostText) {
+ // Insert
+ const created = createFiberFromText(
+ textContent,
+ returnFiber.mode,
+ expirationTime,
+ );
+ 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,
+ expirationTime: ExpirationTime,
+ ): Fiber {
+ if (current !== null) {
+ if (
+ current.elementType === element.type ||
+ // Keep this check inline so it only runs on the false path:
+ (__DEV__ ? isCompatibleFamilyForHotReloading(current, element) : false)
+ ) {
+ // 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;
+ } else if (enableBlocksAPI && current.tag === Block) {
+ // The new Block might not be initialized yet. We need to initialize
+ // it in case initializing it turns out it would match.
+ let type = element.type;
+ if (type.$$typeof === REACT_LAZY_TYPE) {
+ type = resolveLazyType(type);
+ }
+ if (
+ type.$$typeof === REACT_BLOCK_TYPE &&
+ ((type: any): BlockComponent)._render ===
+ (current.type: BlockComponent)._render
+ ) {
+ // Same as above but also update the .type field.
+ const existing = useFiber(current, element.props);
+ existing.return = returnFiber;
+ existing.type = type;
+ if (__DEV__) {
+ existing._debugSource = element._source;
+ existing._debugOwner = element._owner;
+ }
+ return existing;
+ }
+ }
+ }
+ // Insert
+ const created = createFiberFromElement(
+ element,
+ returnFiber.mode,
+ expirationTime,
+ );
+ created.ref = coerceRef(returnFiber, current, element);
+ created.return = returnFiber;
+ return created;
+ }
+
+ function updatePortal(
+ returnFiber: Fiber,
+ current: Fiber | null,
+ portal: ReactPortal,
+ expirationTime: ExpirationTime,
+ ): Fiber {
+ if (
+ current === null ||
+ current.tag !== HostPortal ||
+ current.stateNode.containerInfo !== portal.containerInfo ||
+ current.stateNode.implementation !== portal.implementation
+ ) {
+ // Insert
+ const created = createFiberFromPortal(
+ portal,
+ returnFiber.mode,
+ expirationTime,
+ );
+ 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<*>,
+ expirationTime: ExpirationTime,
+ key: null | string,
+ ): Fiber {
+ if (current === null || current.tag !== Fragment) {
+ // Insert
+ const created = createFiberFromFragment(
+ fragment,
+ returnFiber.mode,
+ expirationTime,
+ key,
+ );
+ created.return = returnFiber;
+ return created;
+ } else {
+ // Update
+ const existing = useFiber(current, fragment);
+ existing.return = returnFiber;
+ return existing;
+ }
+ }
+
+ function createChild(
+ returnFiber: Fiber,
+ newChild: any,
+ expirationTime: ExpirationTime,
+ ): Fiber | null {
+ if (typeof newChild === 'string' || 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,
+ expirationTime,
+ );
+ created.return = returnFiber;
+ return created;
+ }
+
+ if (typeof newChild === 'object' && newChild !== null) {
+ switch (newChild.$$typeof) {
+ case REACT_ELEMENT_TYPE: {
+ const created = createFiberFromElement(
+ newChild,
+ returnFiber.mode,
+ expirationTime,
+ );
+ created.ref = coerceRef(returnFiber, null, newChild);
+ created.return = returnFiber;
+ return created;
+ }
+ case REACT_PORTAL_TYPE: {
+ const created = createFiberFromPortal(
+ newChild,
+ returnFiber.mode,
+ expirationTime,
+ );
+ created.return = returnFiber;
+ return created;
+ }
+ }
+
+ if (isArray(newChild) || getIteratorFn(newChild)) {
+ const created = createFiberFromFragment(
+ newChild,
+ returnFiber.mode,
+ expirationTime,
+ null,
+ );
+ created.return = returnFiber;
+ return created;
+ }
+
+ throwOnInvalidObjectType(returnFiber, newChild);
+ }
+
+ if (__DEV__) {
+ if (typeof newChild === 'function') {
+ warnOnFunctionType();
+ }
+ }
+
+ return null;
+ }
+
+ function updateSlot(
+ returnFiber: Fiber,
+ oldFiber: Fiber | null,
+ newChild: any,
+ expirationTime: ExpirationTime,
+ ): Fiber | null {
+ // Update the fiber if the keys match, otherwise return null.
+
+ const key = oldFiber !== null ? oldFiber.key : null;
+
+ if (typeof newChild === 'string' || 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,
+ expirationTime,
+ );
+ }
+
+ if (typeof newChild === 'object' && newChild !== null) {
+ switch (newChild.$$typeof) {
+ case REACT_ELEMENT_TYPE: {
+ if (newChild.key === key) {
+ if (newChild.type === REACT_FRAGMENT_TYPE) {
+ return updateFragment(
+ returnFiber,
+ oldFiber,
+ newChild.props.children,
+ expirationTime,
+ key,
+ );
+ }
+ return updateElement(
+ returnFiber,
+ oldFiber,
+ newChild,
+ expirationTime,
+ );
+ } else {
+ return null;
+ }
+ }
+ case REACT_PORTAL_TYPE: {
+ if (newChild.key === key) {
+ return updatePortal(
+ returnFiber,
+ oldFiber,
+ newChild,
+ expirationTime,
+ );
+ } else {
+ return null;
+ }
+ }
+ }
+
+ if (isArray(newChild) || getIteratorFn(newChild)) {
+ if (key !== null) {
+ return null;
+ }
+
+ return updateFragment(
+ returnFiber,
+ oldFiber,
+ newChild,
+ expirationTime,
+ null,
+ );
+ }
+
+ throwOnInvalidObjectType(returnFiber, newChild);
+ }
+
+ if (__DEV__) {
+ if (typeof newChild === 'function') {
+ warnOnFunctionType();
+ }
+ }
+
+ return null;
+ }
+
+ function updateFromMap(
+ existingChildren: Map,
+ returnFiber: Fiber,
+ newIdx: number,
+ newChild: any,
+ expirationTime: ExpirationTime,
+ ): Fiber | null {
+ if (typeof newChild === 'string' || 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,
+ expirationTime,
+ );
+ }
+
+ if (typeof newChild === 'object' && newChild !== null) {
+ switch (newChild.$$typeof) {
+ case REACT_ELEMENT_TYPE: {
+ const matchedFiber =
+ existingChildren.get(
+ newChild.key === null ? newIdx : newChild.key,
+ ) || null;
+ if (newChild.type === REACT_FRAGMENT_TYPE) {
+ return updateFragment(
+ returnFiber,
+ matchedFiber,
+ newChild.props.children,
+ expirationTime,
+ newChild.key,
+ );
+ }
+ return updateElement(
+ returnFiber,
+ matchedFiber,
+ newChild,
+ expirationTime,
+ );
+ }
+ case REACT_PORTAL_TYPE: {
+ const matchedFiber =
+ existingChildren.get(
+ newChild.key === null ? newIdx : newChild.key,
+ ) || null;
+ return updatePortal(
+ returnFiber,
+ matchedFiber,
+ newChild,
+ expirationTime,
+ );
+ }
+ }
+
+ if (isArray(newChild) || getIteratorFn(newChild)) {
+ const matchedFiber = existingChildren.get(newIdx) || null;
+ return updateFragment(
+ returnFiber,
+ matchedFiber,
+ newChild,
+ expirationTime,
+ null,
+ );
+ }
+
+ throwOnInvalidObjectType(returnFiber, newChild);
+ }
+
+ if (__DEV__) {
+ if (typeof newChild === 'function') {
+ warnOnFunctionType();
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Warns if there is a duplicate or missing key
+ */
+ function warnOnInvalidKey(
+ child: mixed,
+ knownKeys: Set | null,
+ ): 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);
+ 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;
+ default:
+ break;
+ }
+ }
+ return knownKeys;
+ }
+
+ function reconcileChildrenArray(
+ returnFiber: Fiber,
+ currentFirstChild: Fiber | null,
+ newChildren: Array<*>,
+ expirationTime: ExpirationTime,
+ ): 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);
+ }
+ }
+
+ 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],
+ expirationTime,
+ );
+ 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);
+ 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],
+ expirationTime,
+ );
+ 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;
+ }
+ 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],
+ expirationTime,
+ );
+ 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));
+ }
+
+ return resultingFirstChild;
+ }
+
+ function reconcileChildrenIterator(
+ returnFiber: Fiber,
+ currentFirstChild: Fiber | null,
+ newChildrenIterable: Iterable<*>,
+ expirationTime: ExpirationTime,
+ ): Fiber | null {
+ // This is the same implementation as reconcileChildrenArray(),
+ // but using the iterator instead.
+
+ const iteratorFn = getIteratorFn(newChildrenIterable);
+ invariant(
+ typeof iteratorFn === 'function',
+ '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);
+ }
+ }
+ }
+
+ const newChildren = iteratorFn.call(newChildrenIterable);
+ invariant(newChildren != null, '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,
+ expirationTime,
+ );
+ 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);
+ 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, expirationTime);
+ 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;
+ }
+ 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,
+ expirationTime,
+ );
+ 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));
+ }
+
+ return resultingFirstChild;
+ }
+
+ function reconcileSingleTextNode(
+ returnFiber: Fiber,
+ currentFirstChild: Fiber | null,
+ textContent: string,
+ expirationTime: ExpirationTime,
+ ): 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,
+ expirationTime,
+ );
+ created.return = returnFiber;
+ return created;
+ }
+
+ function reconcileSingleElement(
+ returnFiber: Fiber,
+ currentFirstChild: Fiber | null,
+ element: ReactElement,
+ expirationTime: ExpirationTime,
+ ): 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) {
+ switch (child.tag) {
+ case Fragment: {
+ if (element.type === REACT_FRAGMENT_TYPE) {
+ 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;
+ }
+ break;
+ }
+ case Block:
+ if (enableBlocksAPI) {
+ let type = element.type;
+ if (type.$$typeof === REACT_LAZY_TYPE) {
+ type = resolveLazyType(type);
+ }
+ if (type.$$typeof === REACT_BLOCK_TYPE) {
+ // The new Block might not be initialized yet. We need to initialize
+ // it in case initializing it turns out it would match.
+ if (
+ ((type: any): BlockComponent)._render ===
+ (child.type: BlockComponent)._render
+ ) {
+ deleteRemainingChildren(returnFiber, child.sibling);
+ const existing = useFiber(child, element.props);
+ existing.type = type;
+ existing.return = returnFiber;
+ if (__DEV__) {
+ existing._debugSource = element._source;
+ existing._debugOwner = element._owner;
+ }
+ return existing;
+ }
+ }
+ }
+ // We intentionally fallthrough here if enableBlocksAPI is not on.
+ // eslint-disable-next-lined no-fallthrough
+ default: {
+ if (
+ child.elementType === element.type ||
+ // Keep this check inline so it only runs on the false path:
+ (__DEV__
+ ? isCompatibleFamilyForHotReloading(child, element)
+ : false)
+ ) {
+ 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;
+ }
+ break;
+ }
+ }
+ // 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,
+ expirationTime,
+ element.key,
+ );
+ created.return = returnFiber;
+ return created;
+ } else {
+ const created = createFiberFromElement(
+ element,
+ returnFiber.mode,
+ expirationTime,
+ );
+ created.ref = coerceRef(returnFiber, currentFirstChild, element);
+ created.return = returnFiber;
+ return created;
+ }
+ }
+
+ function reconcileSinglePortal(
+ returnFiber: Fiber,
+ currentFirstChild: Fiber | null,
+ portal: ReactPortal,
+ expirationTime: ExpirationTime,
+ ): 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,
+ expirationTime,
+ );
+ 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,
+ expirationTime: ExpirationTime,
+ ): 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
+ const isObject = typeof newChild === 'object' && newChild !== null;
+
+ if (isObject) {
+ switch (newChild.$$typeof) {
+ case REACT_ELEMENT_TYPE:
+ return placeSingleChild(
+ reconcileSingleElement(
+ returnFiber,
+ currentFirstChild,
+ newChild,
+ expirationTime,
+ ),
+ );
+ case REACT_PORTAL_TYPE:
+ return placeSingleChild(
+ reconcileSinglePortal(
+ returnFiber,
+ currentFirstChild,
+ newChild,
+ expirationTime,
+ ),
+ );
+ }
+ }
+
+ if (typeof newChild === 'string' || typeof newChild === 'number') {
+ return placeSingleChild(
+ reconcileSingleTextNode(
+ returnFiber,
+ currentFirstChild,
+ '' + newChild,
+ expirationTime,
+ ),
+ );
+ }
+
+ if (isArray(newChild)) {
+ return reconcileChildrenArray(
+ returnFiber,
+ currentFirstChild,
+ newChild,
+ expirationTime,
+ );
+ }
+
+ if (getIteratorFn(newChild)) {
+ return reconcileChildrenIterator(
+ returnFiber,
+ currentFirstChild,
+ newChild,
+ expirationTime,
+ );
+ }
+
+ if (isObject) {
+ throwOnInvalidObjectType(returnFiber, newChild);
+ }
+
+ if (__DEV__) {
+ if (typeof newChild === 'function') {
+ warnOnFunctionType();
+ }
+ }
+ if (typeof newChild === 'undefined' && !isUnkeyedTopLevelFragment) {
+ // If the new child is undefined, and the return fiber is a composite
+ // component, throw an error. If Fiber return types are disabled,
+ // we already threw above.
+ switch (returnFiber.tag) {
+ case ClassComponent: {
+ if (__DEV__) {
+ const instance = returnFiber.stateNode;
+ if (instance.render._isMockFunction) {
+ // We allow auto-mocks to proceed as if they're returning null.
+ break;
+ }
+ }
+ }
+ // Intentionally fall through to the next case, which handles both
+ // functions and classes
+ // eslint-disable-next-lined no-fallthrough
+ case FunctionComponent: {
+ const Component = returnFiber.type;
+ invariant(
+ false,
+ '%s(...): Nothing was returned from render. This usually means a ' +
+ 'return statement is missing. Or, to render nothing, ' +
+ 'return null.',
+ Component.displayName || Component.name || 'Component',
+ );
+ }
+ }
+ }
+
+ // Remaining cases are all treated as empty.
+ return deleteRemainingChildren(returnFiber, currentFirstChild);
+ }
+
+ return reconcileChildFibers;
+}
+
+export const reconcileChildFibers = ChildReconciler(true);
+export const mountChildFibers = ChildReconciler(false);
+
+export function cloneChildFibers(
+ current: Fiber | null,
+ workInProgress: Fiber,
+): void {
+ invariant(
+ current === null || workInProgress.child === current.child,
+ '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,
+ renderExpirationTime: ExpirationTime,
+): void {
+ let child = workInProgress.child;
+ while (child !== null) {
+ resetWorkInProgress(child, renderExpirationTime);
+ child = child.sibling;
+ }
+}
diff --git a/packages/react-reconciler/src/ReactFiber.new.js b/packages/react-reconciler/src/ReactFiber.new.js
new file mode 100644
index 0000000000000..97c8a7a87bb29
--- /dev/null
+++ b/packages/react-reconciler/src/ReactFiber.new.js
@@ -0,0 +1,817 @@
+/**
+ * Copyright (c) Facebook, Inc. and its 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,
+ ReactFundamentalComponent,
+ 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 {ExpirationTime} from './ReactFiberExpirationTime';
+import type {SuspenseInstance} from './ReactFiberHostConfig';
+
+import invariant from 'shared/invariant';
+import {
+ enableProfilerTimer,
+ enableFundamentalAPI,
+ enableScopeAPI,
+ enableBlocksAPI,
+ throwEarlyForMysteriousError,
+} from 'shared/ReactFeatureFlags';
+import {NoEffect, Placement} from './ReactSideEffectTags';
+import {ConcurrentRoot, BlockingRoot} from './ReactRootTags';
+import {
+ IndeterminateComponent,
+ ClassComponent,
+ HostRoot,
+ HostComponent,
+ HostText,
+ HostPortal,
+ ForwardRef,
+ Fragment,
+ Mode,
+ ContextProvider,
+ ContextConsumer,
+ Profiler,
+ SuspenseComponent,
+ SuspenseListComponent,
+ DehydratedFragment,
+ FunctionComponent,
+ MemoComponent,
+ SimpleMemoComponent,
+ LazyComponent,
+ FundamentalComponent,
+ ScopeComponent,
+ Block,
+} from './ReactWorkTags';
+import getComponentName from 'shared/getComponentName';
+
+import {isDevToolsPresent} from './ReactFiberDevToolsHook.new';
+import {
+ resolveClassForHotReloading,
+ resolveFunctionForHotReloading,
+ resolveForwardRefForHotReloading,
+} from './ReactFiberHotReloading.new';
+import {NoWork} from './ReactFiberExpirationTime';
+import {
+ NoMode,
+ ConcurrentMode,
+ ProfileMode,
+ StrictMode,
+ BlockingMode,
+} from './ReactTypeOfMode';
+import {
+ REACT_FORWARD_REF_TYPE,
+ REACT_FRAGMENT_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_FUNDAMENTAL_TYPE,
+ REACT_SCOPE_TYPE,
+ REACT_BLOCK_TYPE,
+} from 'shared/ReactSymbols';
+
+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;
+ }
+}
+
+let debugCounter = 1;
+
+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.pendingProps = pendingProps;
+ this.memoizedProps = null;
+ this.updateQueue = null;
+ this.memoizedState = null;
+ this.dependencies = null;
+
+ this.mode = mode;
+
+ // Effects
+ this.effectTag = NoEffect;
+ this.nextEffect = null;
+
+ this.firstEffect = null;
+ this.lastEffect = null;
+
+ this.expirationTime = NoWork;
+ this.childExpirationTime = NoWork;
+
+ 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._debugID = debugCounter++;
+ 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) {
+ 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;
+ }
+ if (enableBlocksAPI) {
+ if ($$typeof === REACT_BLOCK_TYPE) {
+ return Block;
+ }
+ }
+ }
+ 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._debugID = current._debugID;
+ workInProgress._debugSource = current._debugSource;
+ workInProgress._debugOwner = current._debugOwner;
+ workInProgress._debugHookTypes = current._debugHookTypes;
+ }
+
+ workInProgress.alternate = current;
+ current.alternate = workInProgress;
+ } else {
+ workInProgress.pendingProps = pendingProps;
+
+ // We already have an alternate.
+ // Reset the effect tag.
+ workInProgress.effectTag = NoEffect;
+
+ // The effect list is no longer valid.
+ workInProgress.nextEffect = null;
+ workInProgress.firstEffect = null;
+ workInProgress.lastEffect = 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;
+ }
+ }
+
+ if (throwEarlyForMysteriousError) {
+ // Trying to debug a mysterious internal-only production failure.
+ // See D20130868 and t62461245.
+ // This is only on for RN FB builds.
+ if (current == null) {
+ throw Error('current is ' + current + " but it can't be");
+ }
+ if (workInProgress == null) {
+ throw Error('workInProgress is ' + workInProgress + " but it can't be");
+ }
+ }
+
+ workInProgress.childExpirationTime = current.childExpirationTime;
+ workInProgress.expirationTime = current.expirationTime;
+
+ 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
+ : {
+ expirationTime: currentDependencies.expirationTime,
+ firstContext: currentDependencies.firstContext,
+ responders: currentDependencies.responders,
+ };
+
+ // These will be overridden during the parent's reconciliation
+ workInProgress.sibling = current.sibling;
+ workInProgress.index = current.index;
+ workInProgress.ref = current.ref;
+
+ 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,
+ renderExpirationTime: ExpirationTime,
+) {
+ // 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 tag but keep any Placement tags, since that's something
+ // that child fiber is setting, not the reconciliation.
+ workInProgress.effectTag &= Placement;
+
+ // The effect list is no longer valid.
+ workInProgress.nextEffect = null;
+ workInProgress.firstEffect = null;
+ workInProgress.lastEffect = null;
+
+ const current = workInProgress.alternate;
+ if (current === null) {
+ // Reset to createFiber's initial values.
+ workInProgress.childExpirationTime = NoWork;
+ workInProgress.expirationTime = renderExpirationTime;
+
+ workInProgress.child = null;
+ 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.childExpirationTime = current.childExpirationTime;
+ workInProgress.expirationTime = current.expirationTime;
+
+ 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
+ : {
+ expirationTime: currentDependencies.expirationTime,
+ firstContext: currentDependencies.firstContext,
+ responders: currentDependencies.responders,
+ };
+
+ 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): Fiber {
+ let mode;
+ if (tag === ConcurrentRoot) {
+ mode = ConcurrentMode | BlockingMode | StrictMode;
+ } else if (tag === BlockingRoot) {
+ mode = BlockingMode | StrictMode;
+ } 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,
+ expirationTime: ExpirationTime,
+): 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') {
+ fiberTag = HostComponent;
+ } else {
+ getTag: switch (type) {
+ case REACT_FRAGMENT_TYPE:
+ return createFiberFromFragment(
+ pendingProps.children,
+ mode,
+ expirationTime,
+ key,
+ );
+ case REACT_STRICT_MODE_TYPE:
+ fiberTag = Mode;
+ mode |= StrictMode;
+ break;
+ case REACT_PROFILER_TYPE:
+ return createFiberFromProfiler(pendingProps, mode, expirationTime, key);
+ case REACT_SUSPENSE_TYPE:
+ return createFiberFromSuspense(pendingProps, mode, expirationTime, key);
+ case REACT_SUSPENSE_LIST_TYPE:
+ return createFiberFromSuspenseList(
+ pendingProps,
+ mode,
+ expirationTime,
+ key,
+ );
+ 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;
+ case REACT_BLOCK_TYPE:
+ fiberTag = Block;
+ break getTag;
+ case REACT_FUNDAMENTAL_TYPE:
+ if (enableFundamentalAPI) {
+ return createFiberFromFundamental(
+ type,
+ pendingProps,
+ mode,
+ expirationTime,
+ key,
+ );
+ }
+ break;
+ case REACT_SCOPE_TYPE:
+ if (enableScopeAPI) {
+ return createFiberFromScope(
+ type,
+ pendingProps,
+ mode,
+ expirationTime,
+ key,
+ );
+ }
+ }
+ }
+ 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 ? getComponentName(owner.type) : null;
+ if (ownerName) {
+ info += '\n\nCheck the render method of `' + ownerName + '`.';
+ }
+ }
+ invariant(
+ false,
+ 'Element type is invalid: expected a string (for built-in ' +
+ 'components) or a class/function (for composite components) ' +
+ 'but got: %s.%s',
+ type == null ? type : typeof type,
+ info,
+ );
+ }
+ }
+ }
+
+ const fiber = createFiber(fiberTag, pendingProps, key, mode);
+ fiber.elementType = type;
+ fiber.type = resolvedType;
+ fiber.expirationTime = expirationTime;
+
+ return fiber;
+}
+
+export function createFiberFromElement(
+ element: ReactElement,
+ mode: TypeOfMode,
+ expirationTime: ExpirationTime,
+): 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,
+ expirationTime,
+ );
+ if (__DEV__) {
+ fiber._debugSource = element._source;
+ fiber._debugOwner = element._owner;
+ }
+ return fiber;
+}
+
+export function createFiberFromFragment(
+ elements: ReactFragment,
+ mode: TypeOfMode,
+ expirationTime: ExpirationTime,
+ key: null | string,
+): Fiber {
+ const fiber = createFiber(Fragment, elements, key, mode);
+ fiber.expirationTime = expirationTime;
+ return fiber;
+}
+
+export function createFiberFromFundamental(
+ fundamentalComponent: ReactFundamentalComponent,
+ pendingProps: any,
+ mode: TypeOfMode,
+ expirationTime: ExpirationTime,
+ key: null | string,
+): Fiber {
+ const fiber = createFiber(FundamentalComponent, pendingProps, key, mode);
+ fiber.elementType = fundamentalComponent;
+ fiber.type = fundamentalComponent;
+ fiber.expirationTime = expirationTime;
+ return fiber;
+}
+
+function createFiberFromScope(
+ scope: ReactScope,
+ pendingProps: any,
+ mode: TypeOfMode,
+ expirationTime: ExpirationTime,
+ key: null | string,
+) {
+ const fiber = createFiber(ScopeComponent, pendingProps, key, mode);
+ fiber.type = scope;
+ fiber.elementType = scope;
+ fiber.expirationTime = expirationTime;
+ return fiber;
+}
+
+function createFiberFromProfiler(
+ pendingProps: any,
+ mode: TypeOfMode,
+ expirationTime: ExpirationTime,
+ key: null | string,
+): Fiber {
+ if (__DEV__) {
+ if (typeof pendingProps.id !== 'string') {
+ console.error('Profiler must specify an "id" as a prop');
+ }
+ }
+
+ const fiber = createFiber(Profiler, pendingProps, key, mode | ProfileMode);
+ // TODO: The Profiler fiber shouldn't have a type. It has a tag.
+ fiber.elementType = REACT_PROFILER_TYPE;
+ fiber.type = REACT_PROFILER_TYPE;
+ fiber.expirationTime = expirationTime;
+
+ if (enableProfilerTimer) {
+ fiber.stateNode = {
+ effectDuration: 0,
+ passiveEffectDuration: 0,
+ };
+ }
+
+ return fiber;
+}
+
+export function createFiberFromSuspense(
+ pendingProps: any,
+ mode: TypeOfMode,
+ expirationTime: ExpirationTime,
+ key: null | string,
+) {
+ const fiber = createFiber(SuspenseComponent, pendingProps, key, mode);
+
+ // TODO: The SuspenseComponent fiber shouldn't have a type. It has a tag.
+ // This needs to be fixed in getComponentName so that it relies on the tag
+ // instead.
+ fiber.type = REACT_SUSPENSE_TYPE;
+ fiber.elementType = REACT_SUSPENSE_TYPE;
+
+ fiber.expirationTime = expirationTime;
+ return fiber;
+}
+
+export function createFiberFromSuspenseList(
+ pendingProps: any,
+ mode: TypeOfMode,
+ expirationTime: ExpirationTime,
+ key: null | string,
+) {
+ const fiber = createFiber(SuspenseListComponent, pendingProps, key, mode);
+ if (__DEV__) {
+ // TODO: The SuspenseListComponent fiber shouldn't have a type. It has a tag.
+ // This needs to be fixed in getComponentName so that it relies on the tag
+ // instead.
+ fiber.type = REACT_SUSPENSE_LIST_TYPE;
+ }
+ fiber.elementType = REACT_SUSPENSE_LIST_TYPE;
+ fiber.expirationTime = expirationTime;
+ return fiber;
+}
+
+export function createFiberFromText(
+ content: string,
+ mode: TypeOfMode,
+ expirationTime: ExpirationTime,
+): Fiber {
+ const fiber = createFiber(HostText, content, null, mode);
+ fiber.expirationTime = expirationTime;
+ return fiber;
+}
+
+export function createFiberFromHostInstanceForDeletion(): Fiber {
+ const fiber = createFiber(HostComponent, null, null, NoMode);
+ // TODO: These should not need a type.
+ fiber.elementType = 'DELETED';
+ fiber.type = '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,
+ expirationTime: ExpirationTime,
+): Fiber {
+ const pendingProps = portal.children !== null ? portal.children : [];
+ const fiber = createFiber(HostPortal, pendingProps, portal.key, mode);
+ fiber.expirationTime = expirationTime;
+ 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.pendingProps = source.pendingProps;
+ target.memoizedProps = source.memoizedProps;
+ target.updateQueue = source.updateQueue;
+ target.memoizedState = source.memoizedState;
+ target.dependencies = source.dependencies;
+ target.mode = source.mode;
+ target.effectTag = source.effectTag;
+ target.nextEffect = source.nextEffect;
+ target.firstEffect = source.firstEffect;
+ target.lastEffect = source.lastEffect;
+ target.expirationTime = source.expirationTime;
+ target.childExpirationTime = source.childExpirationTime;
+ target.alternate = source.alternate;
+ if (enableProfilerTimer) {
+ target.actualDuration = source.actualDuration;
+ target.actualStartTime = source.actualStartTime;
+ target.selfBaseDuration = source.selfBaseDuration;
+ target.treeBaseDuration = source.treeBaseDuration;
+ }
+ target._debugID = source._debugID;
+ 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/ReactFiberBeginWork.new.js b/packages/react-reconciler/src/ReactFiberBeginWork.new.js
new file mode 100644
index 0000000000000..513599cdc57a0
--- /dev/null
+++ b/packages/react-reconciler/src/ReactFiberBeginWork.new.js
@@ -0,0 +1,3494 @@
+/**
+ * Copyright (c) Facebook, Inc. and its 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} from 'shared/ReactTypes';
+import type {BlockComponent} from 'react/src/ReactBlock';
+import type {LazyComponent as LazyComponentType} from 'react/src/ReactLazy';
+import type {Fiber} from './ReactInternalTypes';
+import type {FiberRoot} from './ReactInternalTypes';
+import type {ExpirationTime} from './ReactFiberExpirationTime';
+import type {
+ SuspenseState,
+ SuspenseListRenderState,
+ SuspenseListTailMode,
+} from './ReactFiberSuspenseComponent.new';
+import type {SuspenseContext} from './ReactFiberSuspenseContext.new';
+
+import checkPropTypes from 'shared/checkPropTypes';
+
+import {
+ IndeterminateComponent,
+ FunctionComponent,
+ ClassComponent,
+ HostRoot,
+ HostComponent,
+ HostText,
+ HostPortal,
+ ForwardRef,
+ Fragment,
+ Mode,
+ ContextProvider,
+ ContextConsumer,
+ Profiler,
+ SuspenseComponent,
+ SuspenseListComponent,
+ MemoComponent,
+ SimpleMemoComponent,
+ LazyComponent,
+ IncompleteClassComponent,
+ FundamentalComponent,
+ ScopeComponent,
+ Block,
+} from './ReactWorkTags';
+import {
+ NoEffect,
+ PerformedWork,
+ Placement,
+ Hydrating,
+ ContentReset,
+ DidCapture,
+ Update,
+ Ref,
+ Deletion,
+} from './ReactSideEffectTags';
+import ReactSharedInternals from 'shared/ReactSharedInternals';
+import {
+ debugRenderPhaseSideEffectsForStrictMode,
+ disableLegacyContext,
+ disableModulePatternComponents,
+ enableProfilerTimer,
+ enableSchedulerTracing,
+ enableSuspenseServerRenderer,
+ enableFundamentalAPI,
+ warnAboutDefaultPropsOnFunctionComponents,
+ enableScopeAPI,
+ enableBlocksAPI,
+} from 'shared/ReactFeatureFlags';
+import invariant from 'shared/invariant';
+import shallowEqual from 'shared/shallowEqual';
+import getComponentName from 'shared/getComponentName';
+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,
+} from './ReactUpdateQueue.new';
+import {
+ NoWork,
+ Never,
+ Sync,
+ computeAsyncExpiration,
+} from './ReactFiberExpirationTime';
+import {
+ ConcurrentMode,
+ NoMode,
+ ProfileMode,
+ StrictMode,
+ BlockingMode,
+} from './ReactTypeOfMode';
+import {
+ shouldSetTextContent,
+ shouldDeprioritizeSubtree,
+ isSuspenseInstancePending,
+ isSuspenseInstanceFallback,
+ registerSuspenseInstanceRetry,
+} from './ReactFiberHostConfig';
+import type {SuspenseInstance} from './ReactFiberHostConfig';
+import {shouldSuspend} from './ReactFiberReconciler';
+import {pushHostContext, pushHostContainer} from './ReactFiberHostContext.new';
+import {
+ suspenseStackCursor,
+ pushSuspenseContext,
+ InvisibleParentSuspenseContext,
+ ForceSuspenseFallback,
+ hasSuspenseContext,
+ setDefaultShallowSuspenseContext,
+ addSubtreeSuspenseContext,
+ setShallowSuspenseContext,
+} from './ReactFiberSuspenseContext.new';
+import {findFirstSuspended} from './ReactFiberSuspenseComponent.new';
+import {
+ pushProvider,
+ propagateContextChange,
+ readContext,
+ prepareToReadContext,
+ calculateChangedBits,
+ scheduleWorkOnParentPath,
+} from './ReactFiberNewContext.new';
+import {renderWithHooks, bailoutHooks} 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 {
+ enterHydrationState,
+ reenterHydrationStateFromDehydratedSuspenseInstance,
+ resetHydrationState,
+ tryToClaimNextHydratableInstance,
+ warnIfHydrating,
+} from './ReactFiberHydrationContext.new';
+import {
+ adoptClassInstance,
+ applyDerivedStateFromProps,
+ constructClassInstance,
+ mountClassInstance,
+ resumeMountClassInstance,
+ updateClassInstance,
+} from './ReactFiberClassComponent.new';
+import {resolveDefaultProps} from './ReactFiberLazyComponent.new';
+import {
+ resolveLazyComponentTag,
+ createFiberFromTypeAndProps,
+ createFiberFromFragment,
+ createWorkInProgress,
+ isSimpleFunctionComponent,
+} from './ReactFiber.new';
+import {
+ markSpawnedWork,
+ requestCurrentTimeForUpdate,
+ retryDehydratedSuspenseBoundary,
+ scheduleUpdateOnFiber,
+ renderDidSuspendDelayIfPossible,
+ markUnprocessedUpdateTime,
+ getWorkInProgressRoot,
+} from './ReactFiberWorkLoop.new';
+
+import {disableLogs, reenableLogs} from 'shared/ConsolePatchingDev';
+
+const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
+
+let didReceiveUpdate: boolean = false;
+
+let didWarnAboutBadClass;
+let didWarnAboutModulePatternComponent;
+let didWarnAboutContextTypeOnFunctionComponent;
+let didWarnAboutGetDerivedStateOnFunctionComponent;
+let didWarnAboutFunctionRefs;
+export let didWarnAboutReassigningProps;
+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,
+ renderExpirationTime: ExpirationTime,
+) {
+ 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,
+ renderExpirationTime,
+ );
+ } 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,
+ renderExpirationTime,
+ );
+ }
+}
+
+function forceUnmountCurrentAndReconcile(
+ current: Fiber,
+ workInProgress: Fiber,
+ nextChildren: any,
+ renderExpirationTime: ExpirationTime,
+) {
+ // 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,
+ renderExpirationTime,
+ );
+ // 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,
+ renderExpirationTime,
+ );
+}
+
+function updateForwardRef(
+ current: Fiber | null,
+ workInProgress: Fiber,
+ Component: any,
+ nextProps: any,
+ renderExpirationTime: ExpirationTime,
+) {
+ // 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',
+ getComponentName(Component),
+ );
+ }
+ }
+ }
+
+ const render = Component.render;
+ const ref = workInProgress.ref;
+
+ // The rest is a fork of updateFunctionComponent
+ let nextChildren;
+ prepareToReadContext(workInProgress, renderExpirationTime);
+ if (__DEV__) {
+ ReactCurrentOwner.current = workInProgress;
+ setIsRendering(true);
+ nextChildren = renderWithHooks(
+ current,
+ workInProgress,
+ render,
+ nextProps,
+ ref,
+ renderExpirationTime,
+ );
+ if (
+ debugRenderPhaseSideEffectsForStrictMode &&
+ workInProgress.mode & StrictMode
+ ) {
+ disableLogs();
+ try {
+ nextChildren = renderWithHooks(
+ current,
+ workInProgress,
+ render,
+ nextProps,
+ ref,
+ renderExpirationTime,
+ );
+ } finally {
+ reenableLogs();
+ }
+ }
+ setIsRendering(false);
+ } else {
+ nextChildren = renderWithHooks(
+ current,
+ workInProgress,
+ render,
+ nextProps,
+ ref,
+ renderExpirationTime,
+ );
+ }
+
+ if (current !== null && !didReceiveUpdate) {
+ bailoutHooks(current, workInProgress, renderExpirationTime);
+ return bailoutOnAlreadyFinishedWork(
+ current,
+ workInProgress,
+ renderExpirationTime,
+ );
+ }
+
+ // React DevTools reads this flag.
+ workInProgress.effectTag |= PerformedWork;
+ reconcileChildren(
+ current,
+ workInProgress,
+ nextChildren,
+ renderExpirationTime,
+ );
+ return workInProgress.child;
+}
+
+function updateMemoComponent(
+ current: Fiber | null,
+ workInProgress: Fiber,
+ Component: any,
+ nextProps: any,
+ updateExpirationTime,
+ renderExpirationTime: ExpirationTime,
+): 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,
+ updateExpirationTime,
+ renderExpirationTime,
+ );
+ }
+ 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',
+ getComponentName(type),
+ );
+ }
+ }
+ const child = createFiberFromTypeAndProps(
+ Component.type,
+ null,
+ nextProps,
+ null,
+ workInProgress.mode,
+ renderExpirationTime,
+ );
+ 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',
+ getComponentName(type),
+ );
+ }
+ }
+ const currentChild = ((current.child: any): Fiber); // This is always exactly one child
+ if (updateExpirationTime < renderExpirationTime) {
+ // 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,
+ renderExpirationTime,
+ );
+ }
+ }
+ // React DevTools reads this flag.
+ workInProgress.effectTag |= 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,
+ updateExpirationTime,
+ renderExpirationTime: ExpirationTime,
+): 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) {
+ 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',
+ getComponentName(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;
+ if (updateExpirationTime < renderExpirationTime) {
+ // The pending update priority was cleared at the beginning of
+ // beginWork. We're about to bail out, but there might be additional
+ // updates at a lower priority. Usually, the priority level of the
+ // remaining updates is accumlated 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.expirationTime = current.expirationTime;
+ return bailoutOnAlreadyFinishedWork(
+ current,
+ workInProgress,
+ renderExpirationTime,
+ );
+ }
+ }
+ }
+ return updateFunctionComponent(
+ current,
+ workInProgress,
+ Component,
+ nextProps,
+ renderExpirationTime,
+ );
+}
+
+function updateFragment(
+ current: Fiber | null,
+ workInProgress: Fiber,
+ renderExpirationTime: ExpirationTime,
+) {
+ const nextChildren = workInProgress.pendingProps;
+ reconcileChildren(
+ current,
+ workInProgress,
+ nextChildren,
+ renderExpirationTime,
+ );
+ return workInProgress.child;
+}
+
+function updateMode(
+ current: Fiber | null,
+ workInProgress: Fiber,
+ renderExpirationTime: ExpirationTime,
+) {
+ const nextChildren = workInProgress.pendingProps.children;
+ reconcileChildren(
+ current,
+ workInProgress,
+ nextChildren,
+ renderExpirationTime,
+ );
+ return workInProgress.child;
+}
+
+function updateProfiler(
+ current: Fiber | null,
+ workInProgress: Fiber,
+ renderExpirationTime: ExpirationTime,
+) {
+ if (enableProfilerTimer) {
+ workInProgress.effectTag |= Update;
+
+ // 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,
+ renderExpirationTime,
+ );
+ 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.effectTag |= Ref;
+ }
+}
+
+function updateFunctionComponent(
+ current,
+ workInProgress,
+ Component,
+ nextProps: any,
+ renderExpirationTime,
+) {
+ 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',
+ getComponentName(Component),
+ );
+ }
+ }
+ }
+
+ let context;
+ if (!disableLegacyContext) {
+ const unmaskedContext = getUnmaskedContext(workInProgress, Component, true);
+ context = getMaskedContext(workInProgress, unmaskedContext);
+ }
+
+ let nextChildren;
+ prepareToReadContext(workInProgress, renderExpirationTime);
+ if (__DEV__) {
+ ReactCurrentOwner.current = workInProgress;
+ setIsRendering(true);
+ nextChildren = renderWithHooks(
+ current,
+ workInProgress,
+ Component,
+ nextProps,
+ context,
+ renderExpirationTime,
+ );
+ if (
+ debugRenderPhaseSideEffectsForStrictMode &&
+ workInProgress.mode & StrictMode
+ ) {
+ disableLogs();
+ try {
+ nextChildren = renderWithHooks(
+ current,
+ workInProgress,
+ Component,
+ nextProps,
+ context,
+ renderExpirationTime,
+ );
+ } finally {
+ reenableLogs();
+ }
+ }
+ setIsRendering(false);
+ } else {
+ nextChildren = renderWithHooks(
+ current,
+ workInProgress,
+ Component,
+ nextProps,
+ context,
+ renderExpirationTime,
+ );
+ }
+
+ if (current !== null && !didReceiveUpdate) {
+ bailoutHooks(current, workInProgress, renderExpirationTime);
+ return bailoutOnAlreadyFinishedWork(
+ current,
+ workInProgress,
+ renderExpirationTime,
+ );
+ }
+
+ // React DevTools reads this flag.
+ workInProgress.effectTag |= PerformedWork;
+ reconcileChildren(
+ current,
+ workInProgress,
+ nextChildren,
+ renderExpirationTime,
+ );
+ return workInProgress.child;
+}
+
+function updateBlock(
+ current: Fiber | null,
+ workInProgress: Fiber,
+ block: BlockComponent,
+ nextProps: any,
+ renderExpirationTime: ExpirationTime,
+) {
+ // 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.
+
+ const render = block._render;
+ const data = block._data;
+
+ // The rest is a fork of updateFunctionComponent
+ let nextChildren;
+ prepareToReadContext(workInProgress, renderExpirationTime);
+ if (__DEV__) {
+ ReactCurrentOwner.current = workInProgress;
+ setIsRendering(true);
+ nextChildren = renderWithHooks(
+ current,
+ workInProgress,
+ render,
+ nextProps,
+ data,
+ renderExpirationTime,
+ );
+ if (
+ debugRenderPhaseSideEffectsForStrictMode &&
+ workInProgress.mode & StrictMode
+ ) {
+ disableLogs();
+ try {
+ nextChildren = renderWithHooks(
+ current,
+ workInProgress,
+ render,
+ nextProps,
+ data,
+ renderExpirationTime,
+ );
+ } finally {
+ reenableLogs();
+ }
+ }
+ setIsRendering(false);
+ } else {
+ nextChildren = renderWithHooks(
+ current,
+ workInProgress,
+ render,
+ nextProps,
+ data,
+ renderExpirationTime,
+ );
+ }
+
+ if (current !== null && !didReceiveUpdate) {
+ bailoutHooks(current, workInProgress, renderExpirationTime);
+ return bailoutOnAlreadyFinishedWork(
+ current,
+ workInProgress,
+ renderExpirationTime,
+ );
+ }
+
+ // React DevTools reads this flag.
+ workInProgress.effectTag |= PerformedWork;
+ reconcileChildren(
+ current,
+ workInProgress,
+ nextChildren,
+ renderExpirationTime,
+ );
+ return workInProgress.child;
+}
+
+function updateClassComponent(
+ current: Fiber | null,
+ workInProgress: Fiber,
+ Component: any,
+ nextProps,
+ renderExpirationTime: ExpirationTime,
+) {
+ 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',
+ getComponentName(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, renderExpirationTime);
+
+ const instance = workInProgress.stateNode;
+ let shouldUpdate;
+ if (instance === null) {
+ if (current !== null) {
+ // A class component without an instance 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.effectTag |= Placement;
+ }
+ // In the initial pass we might need to construct the instance.
+ constructClassInstance(workInProgress, Component, nextProps);
+ mountClassInstance(
+ workInProgress,
+ Component,
+ nextProps,
+ renderExpirationTime,
+ );
+ shouldUpdate = true;
+ } else if (current === null) {
+ // In a resume, we'll already have an instance we can reuse.
+ shouldUpdate = resumeMountClassInstance(
+ workInProgress,
+ Component,
+ nextProps,
+ renderExpirationTime,
+ );
+ } else {
+ shouldUpdate = updateClassInstance(
+ current,
+ workInProgress,
+ Component,
+ nextProps,
+ renderExpirationTime,
+ );
+ }
+ const nextUnitOfWork = finishClassComponent(
+ current,
+ workInProgress,
+ Component,
+ shouldUpdate,
+ hasContext,
+ renderExpirationTime,
+ );
+ 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.',
+ getComponentName(workInProgress.type) || 'a component',
+ );
+ }
+ didWarnAboutReassigningProps = true;
+ }
+ }
+ return nextUnitOfWork;
+}
+
+function finishClassComponent(
+ current: Fiber | null,
+ workInProgress: Fiber,
+ Component: any,
+ shouldUpdate: boolean,
+ hasContext: boolean,
+ renderExpirationTime: ExpirationTime,
+) {
+ // Refs should update even if shouldComponentUpdate returns false
+ markRef(current, workInProgress);
+
+ const didCaptureError = (workInProgress.effectTag & DidCapture) !== NoEffect;
+
+ if (!shouldUpdate && !didCaptureError) {
+ // Context providers should defer to sCU for rendering
+ if (hasContext) {
+ invalidateContextProvider(workInProgress, Component, false);
+ }
+
+ return bailoutOnAlreadyFinishedWork(
+ current,
+ workInProgress,
+ renderExpirationTime,
+ );
+ }
+
+ 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 (__DEV__) {
+ setIsRendering(true);
+ nextChildren = instance.render();
+ if (
+ debugRenderPhaseSideEffectsForStrictMode &&
+ workInProgress.mode & StrictMode
+ ) {
+ disableLogs();
+ try {
+ instance.render();
+ } finally {
+ reenableLogs();
+ }
+ }
+ setIsRendering(false);
+ } else {
+ nextChildren = instance.render();
+ }
+ }
+
+ // React DevTools reads this flag.
+ workInProgress.effectTag |= 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,
+ renderExpirationTime,
+ );
+ } else {
+ reconcileChildren(
+ current,
+ workInProgress,
+ nextChildren,
+ renderExpirationTime,
+ );
+ }
+
+ // 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, renderExpirationTime) {
+ pushHostRootContext(workInProgress);
+ const updateQueue = workInProgress.updateQueue;
+ invariant(
+ current !== null && updateQueue !== null,
+ 'If the root does not have an updateQueue, we should have already ' +
+ 'bailed out. This error is likely caused by a bug in React. Please ' +
+ 'file an issue.',
+ );
+ const nextProps = workInProgress.pendingProps;
+ const prevState = workInProgress.memoizedState;
+ const prevChildren = prevState !== null ? prevState.element : null;
+ cloneUpdateQueue(current, workInProgress);
+ processUpdateQueue(workInProgress, nextProps, null, renderExpirationTime);
+ const nextState = workInProgress.memoizedState;
+ // Caution: React DevTools currently depends on this property
+ // being called "element".
+ const nextChildren = nextState.element;
+ if (nextChildren === prevChildren) {
+ // If the state is the same as before, that's a bailout because we had
+ // no work that expires at this time.
+ resetHydrationState();
+ return bailoutOnAlreadyFinishedWork(
+ current,
+ workInProgress,
+ renderExpirationTime,
+ );
+ }
+ const root: FiberRoot = workInProgress.stateNode;
+ if (root.hydrate && enterHydrationState(workInProgress)) {
+ // If we don't have any current children this might be the first pass.
+ // We always try to hydrate. If this isn't a hydration pass there won't
+ // be any children to hydrate which is effectively the same thing as
+ // not hydrating.
+
+ const child = mountChildFibers(
+ workInProgress,
+ null,
+ nextChildren,
+ renderExpirationTime,
+ );
+ 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.effectTag = (node.effectTag & ~Placement) | Hydrating;
+ node = node.sibling;
+ }
+ } else {
+ // Otherwise reset hydration state in case we aborted and resumed another
+ // root.
+ reconcileChildren(
+ current,
+ workInProgress,
+ nextChildren,
+ renderExpirationTime,
+ );
+ resetHydrationState();
+ }
+ return workInProgress.child;
+}
+
+function updateHostComponent(current, workInProgress, renderExpirationTime) {
+ 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.effectTag |= ContentReset;
+ }
+
+ markRef(current, workInProgress);
+
+ // Check the host config to see if the children are offscreen/hidden.
+ if (
+ workInProgress.mode & ConcurrentMode &&
+ renderExpirationTime !== Never &&
+ shouldDeprioritizeSubtree(type, nextProps)
+ ) {
+ if (enableSchedulerTracing) {
+ markSpawnedWork(Never);
+ }
+ // Schedule this fiber to re-render at offscreen priority. Then bailout.
+ workInProgress.expirationTime = workInProgress.childExpirationTime = Never;
+ return null;
+ }
+
+ reconcileChildren(
+ current,
+ workInProgress,
+ nextChildren,
+ renderExpirationTime,
+ );
+ 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,
+ updateExpirationTime,
+ renderExpirationTime,
+) {
+ 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.effectTag |= Placement;
+ }
+
+ 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,
+ renderExpirationTime,
+ );
+ return child;
+ }
+ case ClassComponent: {
+ if (__DEV__) {
+ workInProgress.type = Component = resolveClassForHotReloading(
+ Component,
+ );
+ }
+ child = updateClassComponent(
+ null,
+ workInProgress,
+ Component,
+ resolvedProps,
+ renderExpirationTime,
+ );
+ return child;
+ }
+ case ForwardRef: {
+ if (__DEV__) {
+ workInProgress.type = Component = resolveForwardRefForHotReloading(
+ Component,
+ );
+ }
+ child = updateForwardRef(
+ null,
+ workInProgress,
+ Component,
+ resolvedProps,
+ renderExpirationTime,
+ );
+ 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',
+ getComponentName(Component),
+ );
+ }
+ }
+ }
+ child = updateMemoComponent(
+ null,
+ workInProgress,
+ Component,
+ resolveDefaultProps(Component.type, resolvedProps), // The inner type can have defaults too
+ updateExpirationTime,
+ renderExpirationTime,
+ );
+ return child;
+ }
+ case Block: {
+ if (enableBlocksAPI) {
+ // TODO: Resolve for Hot Reloading.
+ child = updateBlock(
+ null,
+ workInProgress,
+ Component,
+ props,
+ renderExpirationTime,
+ );
+ return child;
+ }
+ break;
+ }
+ }
+ 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.
+ invariant(
+ false,
+ 'Element type is invalid. Received a promise that resolves to: %s. ' +
+ 'Lazy element type must resolve to a class or function.%s',
+ Component,
+ hint,
+ );
+}
+
+function mountIncompleteClassComponent(
+ _current,
+ workInProgress,
+ Component,
+ nextProps,
+ renderExpirationTime,
+) {
+ if (_current !== null) {
+ // An incomplete 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.effectTag |= Placement;
+ }
+
+ // 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, renderExpirationTime);
+
+ constructClassInstance(workInProgress, Component, nextProps);
+ mountClassInstance(
+ workInProgress,
+ Component,
+ nextProps,
+ renderExpirationTime,
+ );
+
+ return finishClassComponent(
+ null,
+ workInProgress,
+ Component,
+ true,
+ hasContext,
+ renderExpirationTime,
+ );
+}
+
+function mountIndeterminateComponent(
+ _current,
+ workInProgress,
+ Component,
+ renderExpirationTime,
+) {
+ if (_current !== null) {
+ // An indeterminate 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.effectTag |= Placement;
+ }
+
+ const props = workInProgress.pendingProps;
+ let context;
+ if (!disableLegacyContext) {
+ const unmaskedContext = getUnmaskedContext(
+ workInProgress,
+ Component,
+ false,
+ );
+ context = getMaskedContext(workInProgress, unmaskedContext);
+ }
+
+ prepareToReadContext(workInProgress, renderExpirationTime);
+ let value;
+
+ if (__DEV__) {
+ if (
+ Component.prototype &&
+ typeof Component.prototype.render === 'function'
+ ) {
+ const componentName = getComponentName(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 & StrictMode) {
+ ReactStrictModeWarnings.recordLegacyContextWarning(workInProgress, null);
+ }
+
+ setIsRendering(true);
+ ReactCurrentOwner.current = workInProgress;
+ value = renderWithHooks(
+ null,
+ workInProgress,
+ Component,
+ props,
+ context,
+ renderExpirationTime,
+ );
+ setIsRendering(false);
+ } else {
+ value = renderWithHooks(
+ null,
+ workInProgress,
+ Component,
+ props,
+ context,
+ renderExpirationTime,
+ );
+ }
+ // React DevTools reads this flag.
+ workInProgress.effectTag |= 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 = getComponentName(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 = getComponentName(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);
+
+ const getDerivedStateFromProps = Component.getDerivedStateFromProps;
+ if (typeof getDerivedStateFromProps === 'function') {
+ applyDerivedStateFromProps(
+ workInProgress,
+ Component,
+ getDerivedStateFromProps,
+ props,
+ );
+ }
+
+ adoptClassInstance(workInProgress, value);
+ mountClassInstance(workInProgress, Component, props, renderExpirationTime);
+ return finishClassComponent(
+ null,
+ workInProgress,
+ Component,
+ true,
+ hasContext,
+ renderExpirationTime,
+ );
+ } 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.',
+ getComponentName(Component) || 'Unknown',
+ );
+ }
+
+ if (
+ debugRenderPhaseSideEffectsForStrictMode &&
+ workInProgress.mode & StrictMode
+ ) {
+ disableLogs();
+ try {
+ value = renderWithHooks(
+ null,
+ workInProgress,
+ Component,
+ props,
+ context,
+ renderExpirationTime,
+ );
+ } finally {
+ reenableLogs();
+ }
+ }
+ }
+ reconcileChildren(null, workInProgress, value, renderExpirationTime);
+ 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 || workInProgress._debugID || '';
+ 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 = getComponentName(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 = getComponentName(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 = getComponentName(Component) || 'Unknown';
+
+ if (!didWarnAboutContextTypeOnFunctionComponent[componentName]) {
+ console.error(
+ '%s: Function components do not support contextType.',
+ componentName,
+ );
+ didWarnAboutContextTypeOnFunctionComponent[componentName] = true;
+ }
+ }
+ }
+}
+
+function mountSuspenseState(
+ renderExpirationTime: ExpirationTime,
+): SuspenseState {
+ return {
+ dehydrated: null,
+ baseTime: renderExpirationTime,
+ retryTime: NoWork,
+ };
+}
+
+function updateSuspenseState(
+ prevSuspenseState: SuspenseState,
+ renderExpirationTime: ExpirationTime,
+): SuspenseState {
+ const prevSuspendedTime = prevSuspenseState.baseTime;
+ return {
+ dehydrated: null,
+ baseTime:
+ // Choose whichever time is inclusive of the other one. This represents
+ // the union of all the levels that suspended.
+ prevSuspendedTime !== NoWork && prevSuspendedTime < renderExpirationTime
+ ? prevSuspendedTime
+ : renderExpirationTime,
+ retryTime: NoWork,
+ };
+}
+
+function shouldRemainOnFallback(
+ suspenseContext: SuspenseContext,
+ current: null | Fiber,
+ workInProgress: Fiber,
+ renderExpirationTime: ExpirationTime,
+) {
+ // 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 a fallback. If the current render includes
+ // the level that triggered the fallback, we must continue showing it,
+ // regardless of what the Suspense context says.
+ const baseTime = suspenseState.baseTime;
+ if (baseTime !== NoWork && baseTime < renderExpirationTime) {
+ return true;
+ }
+ // Otherwise, fall through to check the Suspense context.
+ } else {
+ // Currently showing content. Don't hide it, even if ForceSuspenseFallack
+ // 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.
+ return hasSuspenseContext(
+ suspenseContext,
+ (ForceSuspenseFallback: SuspenseContext),
+ );
+}
+
+function getRemainingWorkInPrimaryTree(
+ current: Fiber,
+ workInProgress: Fiber,
+ renderExpirationTime,
+) {
+ const currentChildExpirationTime = current.childExpirationTime;
+ const currentSuspenseState: SuspenseState = current.memoizedState;
+ if (currentSuspenseState !== null) {
+ // This boundary already timed out. Check if this render includes the level
+ // that previously suspended.
+ const baseTime = currentSuspenseState.baseTime;
+ if (
+ baseTime !== NoWork &&
+ baseTime < renderExpirationTime &&
+ baseTime > currentChildExpirationTime
+ ) {
+ // There's pending work at a lower level that might now be unblocked.
+ return baseTime;
+ }
+ }
+
+ if (currentChildExpirationTime < renderExpirationTime) {
+ // The highest priority remaining work is not part of this render. So the
+ // remaining work has not changed.
+ return currentChildExpirationTime;
+ }
+
+ if ((workInProgress.mode & BlockingMode) !== NoMode) {
+ // The highest priority remaining work is part of this render. Since we only
+ // keep track of the highest level, we don't know if there's a lower
+ // priority level scheduled. As a compromise, we'll render at the lowest
+ // known level in the entire tree, since that will include everything.
+ // TODO: If expirationTime were a bitmask where each bit represents a
+ // separate task thread, this would be: currentChildBits & ~renderBits
+ const root = getWorkInProgressRoot();
+ if (root !== null) {
+ const lastPendingTime = root.lastPendingTime;
+ if (lastPendingTime < renderExpirationTime) {
+ return lastPendingTime;
+ }
+ }
+ }
+ // In legacy mode, there's no work left.
+ return NoWork;
+}
+
+function updateSuspenseComponent(
+ current,
+ workInProgress,
+ renderExpirationTime,
+) {
+ const mode = workInProgress.mode;
+ const nextProps = workInProgress.pendingProps;
+
+ // This is used by DevTools to force a boundary to suspend.
+ if (__DEV__) {
+ if (shouldSuspend(workInProgress)) {
+ workInProgress.effectTag |= DidCapture;
+ }
+ }
+
+ let suspenseContext: SuspenseContext = suspenseStackCursor.current;
+
+ let nextDidTimeout = false;
+ const didSuspend = (workInProgress.effectTag & DidCapture) !== NoEffect;
+
+ if (
+ didSuspend ||
+ shouldRemainOnFallback(
+ suspenseContext,
+ current,
+ workInProgress,
+ renderExpirationTime,
+ )
+ ) {
+ // Something in this boundary's subtree already suspended. Switch to
+ // rendering the fallback children.
+ nextDidTimeout = true;
+ workInProgress.effectTag &= ~DidCapture;
+ } else {
+ // Attempting the main content
+ if (
+ current === null ||
+ (current.memoizedState: null | SuspenseState) !== null
+ ) {
+ // This is a new mount or this boundary is already showing a fallback state.
+ // Mark this subtree context as having at least one invisible parent that could
+ // handle the fallback state.
+ // Boundaries without fallbacks or should be avoided are not considered since
+ // they cannot handle preferred fallback states.
+ if (
+ nextProps.fallback !== undefined &&
+ nextProps.unstable_avoidThisFallback !== true
+ ) {
+ suspenseContext = addSubtreeSuspenseContext(
+ suspenseContext,
+ InvisibleParentSuspenseContext,
+ );
+ }
+ }
+ }
+
+ suspenseContext = setDefaultShallowSuspenseContext(suspenseContext);
+
+ pushSuspenseContext(workInProgress, suspenseContext);
+
+ // This next part is a bit confusing. If the children timeout, we switch to
+ // showing the fallback children in place of the "primary" children.
+ // However, we don't want to delete the primary children because then their
+ // state will be lost (both the React state and the host state, e.g.
+ // uncontrolled form inputs). Instead we keep them mounted and hide them.
+ // Both the fallback children AND the primary children are rendered at the
+ // same time. Once the primary children are un-suspended, we can delete
+ // the fallback children — don't need to preserve their state.
+ //
+ // The two sets of children are siblings in the host environment, but
+ // semantically, for purposes of reconciliation, they are two separate sets.
+ // So we store them using two fragment fibers.
+ //
+ // However, we want to avoid allocating extra fibers for every placeholder.
+ // They're only necessary when the children time out, because that's the
+ // only time when both sets are mounted.
+ //
+ // So, the extra fragment fibers are only used if the children time out.
+ // Otherwise, we render the primary children directly. This requires some
+ // custom reconciliation logic to preserve the state of the primary
+ // children. It's essentially a very basic form of re-parenting.
+
+ if (current === null) {
+ // If we're currently hydrating, try to hydrate this boundary.
+ // But only if this has a fallback.
+ if (nextProps.fallback !== undefined) {
+ tryToClaimNextHydratableInstance(workInProgress);
+ // This could've been a dehydrated suspense component.
+ if (enableSuspenseServerRenderer) {
+ const suspenseState: null | SuspenseState =
+ workInProgress.memoizedState;
+ if (suspenseState !== null) {
+ const dehydrated = suspenseState.dehydrated;
+ if (dehydrated !== null) {
+ return mountDehydratedSuspenseComponent(
+ workInProgress,
+ dehydrated,
+ renderExpirationTime,
+ );
+ }
+ }
+ }
+ }
+
+ // This is the initial mount. This branch is pretty simple because there's
+ // no previous state that needs to be preserved.
+ if (nextDidTimeout) {
+ // Mount separate fragments for primary and fallback children.
+ const nextFallbackChildren = nextProps.fallback;
+ const primaryChildFragment = createFiberFromFragment(
+ null,
+ mode,
+ NoWork,
+ null,
+ );
+ primaryChildFragment.return = workInProgress;
+
+ if ((workInProgress.mode & BlockingMode) === NoMode) {
+ // Outside of blocking mode, we commit the effects from the
+ // partially completed, timed-out tree, too.
+ const progressedState: SuspenseState = workInProgress.memoizedState;
+ const progressedPrimaryChild: Fiber | null =
+ progressedState !== null
+ ? (workInProgress.child: any).child
+ : (workInProgress.child: any);
+ primaryChildFragment.child = progressedPrimaryChild;
+ let progressedChild = progressedPrimaryChild;
+ while (progressedChild !== null) {
+ progressedChild.return = primaryChildFragment;
+ progressedChild = progressedChild.sibling;
+ }
+ }
+
+ const fallbackChildFragment = createFiberFromFragment(
+ nextFallbackChildren,
+ mode,
+ renderExpirationTime,
+ null,
+ );
+ fallbackChildFragment.return = workInProgress;
+ primaryChildFragment.sibling = fallbackChildFragment;
+ // Skip the primary children, and continue working on the
+ // fallback children.
+ workInProgress.memoizedState = mountSuspenseState(renderExpirationTime);
+ workInProgress.child = primaryChildFragment;
+ return fallbackChildFragment;
+ } else {
+ // Mount the primary children without an intermediate fragment fiber.
+ const nextPrimaryChildren = nextProps.children;
+ workInProgress.memoizedState = null;
+ return (workInProgress.child = mountChildFibers(
+ workInProgress,
+ null,
+ nextPrimaryChildren,
+ renderExpirationTime,
+ ));
+ }
+ } else {
+ // This is an update. This branch is more complicated because we need to
+ // ensure the state of the primary children is preserved.
+ const prevState: null | SuspenseState = current.memoizedState;
+ if (prevState !== null) {
+ if (enableSuspenseServerRenderer) {
+ const dehydrated = prevState.dehydrated;
+ if (dehydrated !== null) {
+ if (!didSuspend) {
+ return updateDehydratedSuspenseComponent(
+ current,
+ workInProgress,
+ dehydrated,
+ prevState,
+ renderExpirationTime,
+ );
+ } else if (
+ (workInProgress.memoizedState: null | SuspenseState) !== null
+ ) {
+ // Something suspended and we should still be in dehydrated mode.
+ // Leave the existing child in place.
+ workInProgress.child = current.child;
+ // The dehydrated completion pass expects this flag to be there
+ // but the normal suspense pass doesn't.
+ workInProgress.effectTag |= DidCapture;
+ return null;
+ } else {
+ // Suspended but we should no longer be in dehydrated mode.
+ // Therefore we now have to render the fallback. Wrap the children
+ // in a fragment fiber to keep them separate from the fallback
+ // children.
+ const nextFallbackChildren = nextProps.fallback;
+ const primaryChildFragment = createFiberFromFragment(
+ // It shouldn't matter what the pending props are because we aren't
+ // going to render this fragment.
+ null,
+ mode,
+ NoWork,
+ null,
+ );
+ primaryChildFragment.return = workInProgress;
+
+ // This is always null since we never want the previous child
+ // that we're not going to hydrate.
+ primaryChildFragment.child = null;
+
+ if ((workInProgress.mode & BlockingMode) === NoMode) {
+ // Outside of blocking mode, we commit the effects from the
+ // partially completed, timed-out tree, too.
+ let progressedChild = (primaryChildFragment.child =
+ workInProgress.child);
+ while (progressedChild !== null) {
+ progressedChild.return = primaryChildFragment;
+ progressedChild = progressedChild.sibling;
+ }
+ } else {
+ // 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,
+ renderExpirationTime,
+ );
+ }
+
+ // Because primaryChildFragment is a new fiber that we're inserting as the
+ // parent of a new tree, we need to set its treeBaseDuration.
+ if (enableProfilerTimer && workInProgress.mode & ProfileMode) {
+ // treeBaseDuration is the sum of all the child tree base durations.
+ let treeBaseDuration = 0;
+ let hiddenChild = primaryChildFragment.child;
+ while (hiddenChild !== null) {
+ treeBaseDuration += hiddenChild.treeBaseDuration;
+ hiddenChild = hiddenChild.sibling;
+ }
+ primaryChildFragment.treeBaseDuration = treeBaseDuration;
+ }
+
+ // Create a fragment from the fallback children, too.
+ const fallbackChildFragment = createFiberFromFragment(
+ nextFallbackChildren,
+ mode,
+ renderExpirationTime,
+ null,
+ );
+ fallbackChildFragment.return = workInProgress;
+ primaryChildFragment.sibling = fallbackChildFragment;
+ fallbackChildFragment.effectTag |= Placement;
+ primaryChildFragment.childExpirationTime = getRemainingWorkInPrimaryTree(
+ current,
+ workInProgress,
+ renderExpirationTime,
+ );
+ workInProgress.memoizedState = updateSuspenseState(
+ current.memoizedState,
+ renderExpirationTime,
+ );
+ workInProgress.child = primaryChildFragment;
+
+ // Skip the primary children, and continue working on the
+ // fallback children.
+ return fallbackChildFragment;
+ }
+ }
+ }
+ // The current tree already timed out. That means each child set is
+ // wrapped in a fragment fiber.
+ const currentPrimaryChildFragment: Fiber = (current.child: any);
+ const currentFallbackChildFragment: Fiber = (currentPrimaryChildFragment.sibling: any);
+ if (nextDidTimeout) {
+ // Still timed out. Reuse the current primary children by cloning
+ // its fragment. We're going to skip over these entirely.
+ const nextFallbackChildren = nextProps.fallback;
+ const primaryChildFragment = createWorkInProgress(
+ currentPrimaryChildFragment,
+ currentPrimaryChildFragment.pendingProps,
+ );
+ primaryChildFragment.return = workInProgress;
+
+ if ((workInProgress.mode & BlockingMode) === NoMode) {
+ // Outside of blocking mode, we commit the effects from the
+ // partially completed, timed-out tree, too.
+ const progressedState: SuspenseState = workInProgress.memoizedState;
+ const progressedPrimaryChild: Fiber | null =
+ progressedState !== null
+ ? (workInProgress.child: any).child
+ : (workInProgress.child: any);
+ if (progressedPrimaryChild !== currentPrimaryChildFragment.child) {
+ primaryChildFragment.child = progressedPrimaryChild;
+ let progressedChild = progressedPrimaryChild;
+ while (progressedChild !== null) {
+ progressedChild.return = primaryChildFragment;
+ progressedChild = progressedChild.sibling;
+ }
+ }
+ }
+
+ // Because primaryChildFragment is a new fiber that we're inserting as the
+ // parent of a new tree, we need to set its treeBaseDuration.
+ if (enableProfilerTimer && workInProgress.mode & ProfileMode) {
+ // treeBaseDuration is the sum of all the child tree base durations.
+ let treeBaseDuration = 0;
+ let hiddenChild = primaryChildFragment.child;
+ while (hiddenChild !== null) {
+ treeBaseDuration += hiddenChild.treeBaseDuration;
+ hiddenChild = hiddenChild.sibling;
+ }
+ primaryChildFragment.treeBaseDuration = treeBaseDuration;
+ }
+
+ // Clone the fallback child fragment, too. These we'll continue
+ // working on.
+ const fallbackChildFragment = createWorkInProgress(
+ currentFallbackChildFragment,
+ nextFallbackChildren,
+ );
+ fallbackChildFragment.return = workInProgress;
+ primaryChildFragment.sibling = fallbackChildFragment;
+ primaryChildFragment.childExpirationTime = getRemainingWorkInPrimaryTree(
+ current,
+ workInProgress,
+ renderExpirationTime,
+ );
+ // Skip the primary children, and continue working on the
+ // fallback children.
+ workInProgress.memoizedState = updateSuspenseState(
+ current.memoizedState,
+ renderExpirationTime,
+ );
+ workInProgress.child = primaryChildFragment;
+ return fallbackChildFragment;
+ } else {
+ // No longer suspended. Switch back to showing the primary children,
+ // and remove the intermediate fragment fiber.
+ const nextPrimaryChildren = nextProps.children;
+ const currentPrimaryChild = currentPrimaryChildFragment.child;
+ const primaryChild = reconcileChildFibers(
+ workInProgress,
+ currentPrimaryChild,
+ nextPrimaryChildren,
+ renderExpirationTime,
+ );
+
+ // If this render doesn't suspend, we need to delete the fallback
+ // children. Wait until the complete phase, after we've confirmed the
+ // fallback is no longer needed.
+ // TODO: Would it be better to store the fallback fragment on
+ // the stateNode?
+
+ // Continue rendering the children, like we normally do.
+ workInProgress.memoizedState = null;
+ return (workInProgress.child = primaryChild);
+ }
+ } else {
+ // The current tree has not already timed out. That means the primary
+ // children are not wrapped in a fragment fiber.
+ const currentPrimaryChild = current.child;
+ if (nextDidTimeout) {
+ // Timed out. Wrap the children in a fragment fiber to keep them
+ // separate from the fallback children.
+ const nextFallbackChildren = nextProps.fallback;
+ const primaryChildFragment = createFiberFromFragment(
+ // It shouldn't matter what the pending props are because we aren't
+ // going to render this fragment.
+ null,
+ mode,
+ NoWork,
+ null,
+ );
+ primaryChildFragment.return = workInProgress;
+ primaryChildFragment.child = currentPrimaryChild;
+ if (currentPrimaryChild !== null) {
+ currentPrimaryChild.return = primaryChildFragment;
+ }
+
+ // Even though we're creating a new fiber, there are no new children,
+ // because we're reusing an already mounted tree. So we don't need to
+ // schedule a placement.
+ // primaryChildFragment.effectTag |= Placement;
+
+ if ((workInProgress.mode & BlockingMode) === NoMode) {
+ // Outside of blocking mode, we commit the effects from the
+ // partially completed, timed-out tree, too.
+ const progressedState: SuspenseState = workInProgress.memoizedState;
+ const progressedPrimaryChild: Fiber | null =
+ progressedState !== null
+ ? (workInProgress.child: any).child
+ : (workInProgress.child: any);
+ primaryChildFragment.child = progressedPrimaryChild;
+ let progressedChild = progressedPrimaryChild;
+ while (progressedChild !== null) {
+ progressedChild.return = primaryChildFragment;
+ progressedChild = progressedChild.sibling;
+ }
+ }
+
+ // Because primaryChildFragment is a new fiber that we're inserting as the
+ // parent of a new tree, we need to set its treeBaseDuration.
+ if (enableProfilerTimer && workInProgress.mode & ProfileMode) {
+ // treeBaseDuration is the sum of all the child tree base durations.
+ let treeBaseDuration = 0;
+ let hiddenChild = primaryChildFragment.child;
+ while (hiddenChild !== null) {
+ treeBaseDuration += hiddenChild.treeBaseDuration;
+ hiddenChild = hiddenChild.sibling;
+ }
+ primaryChildFragment.treeBaseDuration = treeBaseDuration;
+ }
+
+ // Create a fragment from the fallback children, too.
+ const fallbackChildFragment = createFiberFromFragment(
+ nextFallbackChildren,
+ mode,
+ renderExpirationTime,
+ null,
+ );
+ fallbackChildFragment.return = workInProgress;
+ primaryChildFragment.sibling = fallbackChildFragment;
+ fallbackChildFragment.effectTag |= Placement;
+ primaryChildFragment.childExpirationTime = getRemainingWorkInPrimaryTree(
+ current,
+ workInProgress,
+ renderExpirationTime,
+ );
+ // Skip the primary children, and continue working on the
+ // fallback children.
+ workInProgress.memoizedState = mountSuspenseState(renderExpirationTime);
+ workInProgress.child = primaryChildFragment;
+ return fallbackChildFragment;
+ } else {
+ // Still haven't timed out. Continue rendering the children, like we
+ // normally do.
+ workInProgress.memoizedState = null;
+ const nextPrimaryChildren = nextProps.children;
+ return (workInProgress.child = reconcileChildFibers(
+ workInProgress,
+ currentPrimaryChild,
+ nextPrimaryChildren,
+ renderExpirationTime,
+ ));
+ }
+ }
+ }
+}
+
+function retrySuspenseComponentWithoutHydrating(
+ current: Fiber,
+ workInProgress: Fiber,
+ renderExpirationTime: ExpirationTime,
+) {
+ // We're now not suspended nor dehydrated.
+ workInProgress.memoizedState = null;
+ // Retry with the full children.
+ const nextProps = workInProgress.pendingProps;
+ const nextChildren = nextProps.children;
+ // This will ensure that the children get Placement effects and
+ // that the old child gets a Deletion effect.
+ // We could also call forceUnmountCurrentAndReconcile.
+ reconcileChildren(
+ current,
+ workInProgress,
+ nextChildren,
+ renderExpirationTime,
+ );
+ return workInProgress.child;
+}
+
+function mountDehydratedSuspenseComponent(
+ workInProgress: Fiber,
+ suspenseInstance: SuspenseInstance,
+ renderExpirationTime: ExpirationTime,
+): 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 & BlockingMode) === NoMode) {
+ if (__DEV__) {
+ console.error(
+ 'Cannot hydrate Suspense in legacy mode. Switch from ' +
+ 'ReactDOM.hydrate(element, container) to ' +
+ 'ReactDOM.createBlockingRoot(container, { hydrate: true })' +
+ '.render(element) or remove the Suspense components from ' +
+ 'the server rendered components.',
+ );
+ }
+ workInProgress.expirationTime = Sync;
+ } 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.
+ const serverDisplayTime = requestCurrentTimeForUpdate();
+ // Schedule a normal pri update to render this content.
+ const newExpirationTime = computeAsyncExpiration(serverDisplayTime);
+ if (enableSchedulerTracing) {
+ markSpawnedWork(newExpirationTime);
+ }
+ workInProgress.expirationTime = newExpirationTime;
+ } 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.expirationTime = Never;
+ if (enableSchedulerTracing) {
+ markSpawnedWork(Never);
+ }
+ }
+ return null;
+}
+
+function updateDehydratedSuspenseComponent(
+ current: Fiber,
+ workInProgress: Fiber,
+ suspenseInstance: SuspenseInstance,
+ suspenseState: SuspenseState,
+ renderExpirationTime: ExpirationTime,
+): null | Fiber {
+ // 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 & BlockingMode) === NoMode) {
+ return retrySuspenseComponentWithoutHydrating(
+ current,
+ workInProgress,
+ renderExpirationTime,
+ );
+ }
+
+ 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.
+ return retrySuspenseComponentWithoutHydrating(
+ current,
+ workInProgress,
+ renderExpirationTime,
+ );
+ }
+ // We use childExpirationTime 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 = current.childExpirationTime >= renderExpirationTime;
+ 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 an earlier expiration time, if
+ // we are rendering at lower expiration than sync.
+ if (renderExpirationTime < Sync) {
+ if (suspenseState.retryTime <= renderExpirationTime) {
+ // This render is even higher pri than we've seen before, let's try again
+ // at even higher pri.
+ const attemptHydrationAtExpirationTime = renderExpirationTime + 1;
+ suspenseState.retryTime = attemptHydrationAtExpirationTime;
+ scheduleUpdateOnFiber(current, attemptHydrationAtExpirationTime);
+ // TODO: Early abort this render.
+ } 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 have scheduled higher pri work above, this will probably just abort the render
+ // since we now have higher priority work, but in case it doesn't, we need to prepare to
+ // render something, if we time out. Even if that requires us to delete everything and
+ // skip hydration.
+ // Delay having to do this as long as the suspense timeout allows us.
+ renderDidSuspendDelayIfPossible();
+ return retrySuspenseComponentWithoutHydrating(
+ current,
+ workInProgress,
+ renderExpirationTime,
+ );
+ } 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.effectTag |= 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.
+ registerSuspenseInstanceRetry(
+ suspenseInstance,
+ retryDehydratedSuspenseBoundary.bind(null, current),
+ );
+ return null;
+ } else {
+ // This is the first attempt.
+ reenterHydrationStateFromDehydratedSuspenseInstance(
+ workInProgress,
+ suspenseInstance,
+ );
+ const nextProps = workInProgress.pendingProps;
+ const nextChildren = nextProps.children;
+ const child = mountChildFibers(
+ workInProgress,
+ null,
+ nextChildren,
+ renderExpirationTime,
+ );
+ 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.effectTag |= Hydrating;
+ node = node.sibling;
+ }
+ workInProgress.child = child;
+ return workInProgress.child;
+ }
+}
+
+function scheduleWorkOnFiber(
+ fiber: Fiber,
+ renderExpirationTime: ExpirationTime,
+) {
+ if (fiber.expirationTime < renderExpirationTime) {
+ fiber.expirationTime = renderExpirationTime;
+ }
+ const alternate = fiber.alternate;
+ if (alternate !== null && alternate.expirationTime < renderExpirationTime) {
+ alternate.expirationTime = renderExpirationTime;
+ }
+ scheduleWorkOnParentPath(fiber.return, renderExpirationTime);
+}
+
+function propagateSuspenseContextChange(
+ workInProgress: Fiber,
+ firstChild: null | Fiber,
+ renderExpirationTime: ExpirationTime,
+): 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) {
+ scheduleWorkOnFiber(node, renderExpirationTime);
+ }
+ } 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.
+ scheduleWorkOnFiber(node, renderExpirationTime);
+ } else if (node.child !== null) {
+ node.child.return = node;
+ node = node.child;
+ continue;
+ }
+ if (node === workInProgress) {
+ return;
+ }
+ while (node.sibling === null) {
+ if (node.return === null || node.return === workInProgress) {
+ return;
+ }
+ node = node.return;
+ }
+ 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 isArray = Array.isArray(childSlot);
+ const isIterable =
+ !isArray && typeof getIteratorFn(childSlot) === 'function';
+ if (isArray || isIterable) {
+ const type = isArray ? '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 (Array.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,
+ lastEffectBeforeRendering: null | Fiber,
+): void {
+ const renderState: null | SuspenseListRenderState =
+ workInProgress.memoizedState;
+ if (renderState === null) {
+ workInProgress.memoizedState = ({
+ isBackwards: isBackwards,
+ rendering: null,
+ renderingStartTime: 0,
+ last: lastContentRow,
+ tail: tail,
+ tailExpiration: 0,
+ tailMode: tailMode,
+ lastEffect: lastEffectBeforeRendering,
+ }: 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.tailExpiration = 0;
+ renderState.tailMode = tailMode;
+ renderState.lastEffect = lastEffectBeforeRendering;
+ }
+}
+
+// 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,
+ renderExpirationTime: ExpirationTime,
+) {
+ 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, renderExpirationTime);
+
+ let suspenseContext: SuspenseContext = suspenseStackCursor.current;
+
+ const shouldForceFallback = hasSuspenseContext(
+ suspenseContext,
+ (ForceSuspenseFallback: SuspenseContext),
+ );
+ if (shouldForceFallback) {
+ suspenseContext = setShallowSuspenseContext(
+ suspenseContext,
+ ForceSuspenseFallback,
+ );
+ workInProgress.effectTag |= DidCapture;
+ } else {
+ const didSuspendBefore =
+ current !== null && (current.effectTag & DidCapture) !== NoEffect;
+ 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,
+ renderExpirationTime,
+ );
+ }
+ suspenseContext = setDefaultShallowSuspenseContext(suspenseContext);
+ }
+ pushSuspenseContext(workInProgress, suspenseContext);
+
+ if ((workInProgress.mode & BlockingMode) === NoMode) {
+ // Outside of blocking 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,
+ workInProgress.lastEffect,
+ );
+ 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,
+ workInProgress.lastEffect,
+ );
+ break;
+ }
+ case 'together': {
+ initSuspenseListRenderState(
+ workInProgress,
+ false, // isBackwards
+ null, // tail
+ null, // last
+ undefined,
+ workInProgress.lastEffect,
+ );
+ 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,
+ renderExpirationTime: ExpirationTime,
+) {
+ 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,
+ renderExpirationTime,
+ );
+ } else {
+ reconcileChildren(
+ current,
+ workInProgress,
+ nextChildren,
+ renderExpirationTime,
+ );
+ }
+ return workInProgress.child;
+}
+
+function updateContextProvider(
+ current: Fiber | null,
+ workInProgress: Fiber,
+ renderExpirationTime: ExpirationTime,
+) {
+ const providerType: ReactProviderType = workInProgress.type;
+ const context: ReactContext = providerType._context;
+
+ const newProps = workInProgress.pendingProps;
+ const oldProps = workInProgress.memoizedProps;
+
+ const newValue = newProps.value;
+
+ if (__DEV__) {
+ const providerPropTypes = workInProgress.type.propTypes;
+
+ if (providerPropTypes) {
+ checkPropTypes(providerPropTypes, newProps, 'prop', 'Context.Provider');
+ }
+ }
+
+ pushProvider(workInProgress, newValue);
+
+ if (oldProps !== null) {
+ const oldValue = oldProps.value;
+ const changedBits = calculateChangedBits(context, newValue, oldValue);
+ if (changedBits === 0) {
+ // No change. Bailout early if children are the same.
+ if (
+ oldProps.children === newProps.children &&
+ !hasLegacyContextChanged()
+ ) {
+ return bailoutOnAlreadyFinishedWork(
+ current,
+ workInProgress,
+ renderExpirationTime,
+ );
+ }
+ } else {
+ // The context value changed. Search for matching consumers and schedule
+ // them to update.
+ propagateContextChange(
+ workInProgress,
+ context,
+ changedBits,
+ renderExpirationTime,
+ );
+ }
+ }
+
+ const newChildren = newProps.children;
+ reconcileChildren(current, workInProgress, newChildren, renderExpirationTime);
+ return workInProgress.child;
+}
+
+let hasWarnedAboutUsingContextAsConsumer = false;
+
+function updateContextConsumer(
+ current: Fiber | null,
+ workInProgress: Fiber,
+ renderExpirationTime: ExpirationTime,
+) {
+ 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, renderExpirationTime);
+ const newValue = readContext(context, newProps.unstable_observedBits);
+ let newChildren;
+ if (__DEV__) {
+ ReactCurrentOwner.current = workInProgress;
+ setIsRendering(true);
+ newChildren = render(newValue);
+ setIsRendering(false);
+ } else {
+ newChildren = render(newValue);
+ }
+
+ // React DevTools reads this flag.
+ workInProgress.effectTag |= PerformedWork;
+ reconcileChildren(current, workInProgress, newChildren, renderExpirationTime);
+ return workInProgress.child;
+}
+
+function updateFundamentalComponent(
+ current,
+ workInProgress,
+ renderExpirationTime,
+) {
+ const fundamentalImpl = workInProgress.type.impl;
+ if (fundamentalImpl.reconcileChildren === false) {
+ return null;
+ }
+ const nextProps = workInProgress.pendingProps;
+ const nextChildren = nextProps.children;
+
+ reconcileChildren(
+ current,
+ workInProgress,
+ nextChildren,
+ renderExpirationTime,
+ );
+ return workInProgress.child;
+}
+
+function updateScopeComponent(current, workInProgress, renderExpirationTime) {
+ const nextProps = workInProgress.pendingProps;
+ const nextChildren = nextProps.children;
+
+ reconcileChildren(
+ current,
+ workInProgress,
+ nextChildren,
+ renderExpirationTime,
+ );
+ return workInProgress.child;
+}
+
+export function markWorkInProgressReceivedUpdate() {
+ didReceiveUpdate = true;
+}
+
+function bailoutOnAlreadyFinishedWork(
+ current: Fiber | null,
+ workInProgress: Fiber,
+ renderExpirationTime: ExpirationTime,
+): Fiber | null {
+ if (current !== null) {
+ // Reuse previous dependencies
+ workInProgress.dependencies = current.dependencies;
+ }
+
+ if (enableProfilerTimer) {
+ // Don't update "base" render times for bailouts.
+ stopProfilerTimerIfRunning(workInProgress);
+ }
+
+ const updateExpirationTime = workInProgress.expirationTime;
+ if (updateExpirationTime !== NoWork) {
+ markUnprocessedUpdateTime(updateExpirationTime);
+ }
+
+ // Check if the children have any pending work.
+ const childExpirationTime = workInProgress.childExpirationTime;
+ if (childExpirationTime < renderExpirationTime) {
+ // 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.
+ return null;
+ } else {
+ // 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) {
+ 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) {
+ throw new Error('Expected parent to have a child.');
+ }
+ while (prevSibling.sibling !== oldWorkInProgress) {
+ prevSibling = prevSibling.sibling;
+ if (prevSibling === null) {
+ throw new Error('Expected to find the previous sibling.');
+ }
+ }
+ 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 last = returnFiber.lastEffect;
+ if (last !== null) {
+ last.nextEffect = current;
+ returnFiber.lastEffect = current;
+ } else {
+ returnFiber.firstEffect = returnFiber.lastEffect = current;
+ }
+ current.nextEffect = null;
+ current.effectTag = Deletion;
+
+ newWorkInProgress.effectTag |= 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 beginWork(
+ current: Fiber | null,
+ workInProgress: Fiber,
+ renderExpirationTime: ExpirationTime,
+): Fiber | null {
+ const updateExpirationTime = workInProgress.expirationTime;
+
+ 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.expirationTime,
+ ),
+ );
+ }
+ }
+
+ 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 if (updateExpirationTime < renderExpirationTime) {
+ didReceiveUpdate = false;
+ // 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);
+ resetHydrationState();
+ break;
+ case HostComponent:
+ pushHostContext(workInProgress);
+ if (
+ workInProgress.mode & ConcurrentMode &&
+ renderExpirationTime !== Never &&
+ shouldDeprioritizeSubtree(workInProgress.type, newProps)
+ ) {
+ if (enableSchedulerTracing) {
+ markSpawnedWork(Never);
+ }
+ // Schedule this fiber to re-render at offscreen priority. Then bailout.
+ workInProgress.expirationTime = workInProgress.childExpirationTime = Never;
+ return null;
+ }
+ 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;
+ pushProvider(workInProgress, newValue);
+ break;
+ }
+ case Profiler:
+ if (enableProfilerTimer) {
+ // Profiler should only call onRender when one of its descendants actually rendered.
+ const hasChildWork =
+ workInProgress.childExpirationTime >= renderExpirationTime;
+ if (hasChildWork) {
+ workInProgress.effectTag |= Update;
+ }
+
+ // 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 (enableSuspenseServerRenderer) {
+ if (state.dehydrated !== null) {
+ pushSuspenseContext(
+ workInProgress,
+ setDefaultShallowSuspenseContext(suspenseStackCursor.current),
+ );
+ // 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.effectTag |= DidCapture;
+ break;
+ }
+ }
+
+ // 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 primaryChildExpirationTime =
+ primaryChildFragment.childExpirationTime;
+ if (
+ primaryChildExpirationTime !== NoWork &&
+ primaryChildExpirationTime >= renderExpirationTime
+ ) {
+ // The primary children have pending work. Use the normal path
+ // to attempt to render the primary children again.
+ return updateSuspenseComponent(
+ current,
+ workInProgress,
+ renderExpirationTime,
+ );
+ } else {
+ // The primary child fragment does not have pending work marked
+ // on it...
+
+ // ...usually. There's an unfortunate edge case where the fragment
+ // fiber is not part of the return path of the children, so when
+ // an update happens, the fragment doesn't get marked during
+ // setState. This is something we should consider addressing when
+ // we refactor the Fiber data structure. (There's a test with more
+ // details; to find it, comment out the following block and see
+ // which one fails.)
+ //
+ // As a workaround, we need to recompute the `childExpirationTime`
+ // by bubbling it up from the next level of children. This is
+ // based on similar logic in `resetChildExpirationTime`.
+ let primaryChild = primaryChildFragment.child;
+ while (primaryChild !== null) {
+ const childUpdateExpirationTime = primaryChild.expirationTime;
+ const childChildExpirationTime =
+ primaryChild.childExpirationTime;
+ if (
+ (childUpdateExpirationTime !== NoWork &&
+ childUpdateExpirationTime >= renderExpirationTime) ||
+ (childChildExpirationTime !== NoWork &&
+ childChildExpirationTime >= renderExpirationTime)
+ ) {
+ // Found a child with an update with sufficient priority.
+ // Use the normal path to render the primary children again.
+ return updateSuspenseComponent(
+ current,
+ workInProgress,
+ renderExpirationTime,
+ );
+ }
+ primaryChild = primaryChild.sibling;
+ }
+
+ pushSuspenseContext(
+ workInProgress,
+ setDefaultShallowSuspenseContext(suspenseStackCursor.current),
+ );
+ // The primary children do not have pending work with sufficient
+ // priority. Bailout.
+ const child = bailoutOnAlreadyFinishedWork(
+ current,
+ workInProgress,
+ renderExpirationTime,
+ );
+ if (child !== null) {
+ // The fallback children have pending work. Skip over the
+ // primary children and work on the fallback.
+ return child.sibling;
+ } else {
+ return null;
+ }
+ }
+ } else {
+ pushSuspenseContext(
+ workInProgress,
+ setDefaultShallowSuspenseContext(suspenseStackCursor.current),
+ );
+ }
+ break;
+ }
+ case SuspenseListComponent: {
+ const didSuspendBefore =
+ (current.effectTag & DidCapture) !== NoEffect;
+
+ const hasChildWork =
+ workInProgress.childExpirationTime >= renderExpirationTime;
+
+ 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,
+ renderExpirationTime,
+ );
+ }
+ // 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.effectTag |= 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;
+ }
+ pushSuspenseContext(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;
+ }
+ }
+ }
+ return bailoutOnAlreadyFinishedWork(
+ current,
+ workInProgress,
+ renderExpirationTime,
+ );
+ } 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;
+ }
+
+ // 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.expirationTime = NoWork;
+
+ switch (workInProgress.tag) {
+ case IndeterminateComponent: {
+ return mountIndeterminateComponent(
+ current,
+ workInProgress,
+ workInProgress.type,
+ renderExpirationTime,
+ );
+ }
+ case LazyComponent: {
+ const elementType = workInProgress.elementType;
+ return mountLazyComponent(
+ current,
+ workInProgress,
+ elementType,
+ updateExpirationTime,
+ renderExpirationTime,
+ );
+ }
+ 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,
+ renderExpirationTime,
+ );
+ }
+ 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,
+ renderExpirationTime,
+ );
+ }
+ case HostRoot:
+ return updateHostRoot(current, workInProgress, renderExpirationTime);
+ case HostComponent:
+ return updateHostComponent(current, workInProgress, renderExpirationTime);
+ case HostText:
+ return updateHostText(current, workInProgress);
+ case SuspenseComponent:
+ return updateSuspenseComponent(
+ current,
+ workInProgress,
+ renderExpirationTime,
+ );
+ case HostPortal:
+ return updatePortalComponent(
+ current,
+ workInProgress,
+ renderExpirationTime,
+ );
+ 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,
+ renderExpirationTime,
+ );
+ }
+ case Fragment:
+ return updateFragment(current, workInProgress, renderExpirationTime);
+ case Mode:
+ return updateMode(current, workInProgress, renderExpirationTime);
+ case Profiler:
+ return updateProfiler(current, workInProgress, renderExpirationTime);
+ case ContextProvider:
+ return updateContextProvider(
+ current,
+ workInProgress,
+ renderExpirationTime,
+ );
+ case ContextConsumer:
+ return updateContextConsumer(
+ current,
+ workInProgress,
+ renderExpirationTime,
+ );
+ 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',
+ getComponentName(type),
+ );
+ }
+ }
+ }
+ resolvedProps = resolveDefaultProps(type.type, resolvedProps);
+ return updateMemoComponent(
+ current,
+ workInProgress,
+ type,
+ resolvedProps,
+ updateExpirationTime,
+ renderExpirationTime,
+ );
+ }
+ case SimpleMemoComponent: {
+ return updateSimpleMemoComponent(
+ current,
+ workInProgress,
+ workInProgress.type,
+ workInProgress.pendingProps,
+ updateExpirationTime,
+ renderExpirationTime,
+ );
+ }
+ 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,
+ renderExpirationTime,
+ );
+ }
+ case SuspenseListComponent: {
+ return updateSuspenseListComponent(
+ current,
+ workInProgress,
+ renderExpirationTime,
+ );
+ }
+ case FundamentalComponent: {
+ if (enableFundamentalAPI) {
+ return updateFundamentalComponent(
+ current,
+ workInProgress,
+ renderExpirationTime,
+ );
+ }
+ break;
+ }
+ case ScopeComponent: {
+ if (enableScopeAPI) {
+ return updateScopeComponent(
+ current,
+ workInProgress,
+ renderExpirationTime,
+ );
+ }
+ break;
+ }
+ case Block: {
+ if (enableBlocksAPI) {
+ const block = workInProgress.type;
+ const props = workInProgress.pendingProps;
+ return updateBlock(
+ current,
+ workInProgress,
+ block,
+ props,
+ renderExpirationTime,
+ );
+ }
+ break;
+ }
+ }
+ invariant(
+ false,
+ 'Unknown unit of work tag (%s). This error is likely caused by a bug in ' +
+ 'React. Please file an issue.',
+ workInProgress.tag,
+ );
+}
+
+export {beginWork};
diff --git a/packages/react-reconciler/src/ReactFiberClassComponent.new.js b/packages/react-reconciler/src/ReactFiberClassComponent.new.js
new file mode 100644
index 0000000000000..461fd94d5e9dd
--- /dev/null
+++ b/packages/react-reconciler/src/ReactFiberClassComponent.new.js
@@ -0,0 +1,1182 @@
+/**
+ * Copyright (c) Facebook, Inc. and its 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 {ExpirationTime} from './ReactFiberExpirationTime';
+import type {UpdateQueue} from './ReactUpdateQueue.new';
+
+import * as React from 'react';
+import {Update, Snapshot} from './ReactSideEffectTags';
+import {
+ debugRenderPhaseSideEffectsForStrictMode,
+ disableLegacyContext,
+ warnAboutDeprecatedLifecycles,
+} 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 getComponentName from 'shared/getComponentName';
+import invariant from 'shared/invariant';
+import {REACT_CONTEXT_TYPE, REACT_PROVIDER_TYPE} from 'shared/ReactSymbols';
+
+import {resolveDefaultProps} from './ReactFiberLazyComponent.new';
+import {StrictMode} from './ReactTypeOfMode';
+
+import {
+ enqueueUpdate,
+ processUpdateQueue,
+ checkHasForceUpdateAfterProcessing,
+ resetHasForceUpdateBeforeProcessing,
+ createUpdate,
+ ReplaceState,
+ ForceUpdate,
+ initializeUpdateQueue,
+ cloneUpdateQueue,
+} from './ReactUpdateQueue.new';
+import {NoWork} from './ReactFiberExpirationTime';
+import {
+ cacheContext,
+ getMaskedContext,
+ getUnmaskedContext,
+ hasContextChanged,
+ emptyContextObject,
+} from './ReactFiberContext.new';
+import {readContext} from './ReactFiberNewContext.new';
+import {
+ requestCurrentTimeForUpdate,
+ computeExpirationForFiber,
+ scheduleUpdateOnFiber,
+} from './ReactFiberWorkLoop.new';
+import {requestCurrentSuspenseConfig} from './ReactFiberSuspenseConfig';
+
+import {disableLogs, reenableLogs} from 'shared/ConsolePatchingDev';
+
+const fakeInternalInstance = {};
+const isArray = Array.isArray;
+
+// React.Component uses a shared frozen object by default.
+// We'll use it to determine whether we need to initialize legacy refs.
+export const emptyRefsObject = new React.Component().refs;
+
+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 = getComponentName(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() {
+ invariant(
+ false,
+ '_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);
+}
+
+export function applyDerivedStateFromProps(
+ workInProgress: Fiber,
+ ctor: any,
+ getDerivedStateFromProps: (props: any, state: any) => any,
+ nextProps: any,
+) {
+ const prevState = workInProgress.memoizedState;
+
+ if (__DEV__) {
+ if (
+ debugRenderPhaseSideEffectsForStrictMode &&
+ workInProgress.mode & StrictMode
+ ) {
+ disableLogs();
+ try {
+ // Invoke the function an extra time to help detect side-effects.
+ getDerivedStateFromProps(nextProps, prevState);
+ } finally {
+ reenableLogs();
+ }
+ }
+ }
+
+ const partialState = getDerivedStateFromProps(nextProps, prevState);
+
+ if (__DEV__) {
+ warnOnUndefinedDerivedState(ctor, partialState);
+ }
+ // Merge the partial state and the previous state.
+ const memoizedState =
+ partialState === null || partialState === undefined
+ ? prevState
+ : Object.assign({}, prevState, partialState);
+ workInProgress.memoizedState = memoizedState;
+
+ // Once the update queue is empty, persist the derived state onto the
+ // base state.
+ if (workInProgress.expirationTime === NoWork) {
+ // 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 currentTime = requestCurrentTimeForUpdate();
+ const suspenseConfig = requestCurrentSuspenseConfig();
+ const expirationTime = computeExpirationForFiber(
+ currentTime,
+ fiber,
+ suspenseConfig,
+ );
+
+ const update = createUpdate(expirationTime, suspenseConfig);
+ update.payload = payload;
+ if (callback !== undefined && callback !== null) {
+ if (__DEV__) {
+ warnOnInvalidCallback(callback, 'setState');
+ }
+ update.callback = callback;
+ }
+
+ enqueueUpdate(fiber, update);
+ scheduleUpdateOnFiber(fiber, expirationTime);
+ },
+ enqueueReplaceState(inst, payload, callback) {
+ const fiber = getInstance(inst);
+ const currentTime = requestCurrentTimeForUpdate();
+ const suspenseConfig = requestCurrentSuspenseConfig();
+ const expirationTime = computeExpirationForFiber(
+ currentTime,
+ fiber,
+ suspenseConfig,
+ );
+
+ const update = createUpdate(expirationTime, suspenseConfig);
+ update.tag = ReplaceState;
+ update.payload = payload;
+
+ if (callback !== undefined && callback !== null) {
+ if (__DEV__) {
+ warnOnInvalidCallback(callback, 'replaceState');
+ }
+ update.callback = callback;
+ }
+
+ enqueueUpdate(fiber, update);
+ scheduleUpdateOnFiber(fiber, expirationTime);
+ },
+ enqueueForceUpdate(inst, callback) {
+ const fiber = getInstance(inst);
+ const currentTime = requestCurrentTimeForUpdate();
+ const suspenseConfig = requestCurrentSuspenseConfig();
+ const expirationTime = computeExpirationForFiber(
+ currentTime,
+ fiber,
+ suspenseConfig,
+ );
+
+ const update = createUpdate(expirationTime, suspenseConfig);
+ update.tag = ForceUpdate;
+
+ if (callback !== undefined && callback !== null) {
+ if (__DEV__) {
+ warnOnInvalidCallback(callback, 'forceUpdate');
+ }
+ update.callback = callback;
+ }
+
+ enqueueUpdate(fiber, update);
+ scheduleUpdateOnFiber(fiber, expirationTime);
+ },
+};
+
+function checkShouldComponentUpdate(
+ workInProgress,
+ ctor,
+ oldProps,
+ newProps,
+ oldState,
+ newState,
+ nextContext,
+) {
+ const instance = workInProgress.stateNode;
+ if (typeof instance.shouldComponentUpdate === 'function') {
+ if (__DEV__) {
+ if (
+ debugRenderPhaseSideEffectsForStrictMode &&
+ workInProgress.mode & StrictMode
+ ) {
+ disableLogs();
+ try {
+ // Invoke the function an extra time to help detect side-effects.
+ instance.shouldComponentUpdate(newProps, newState, nextContext);
+ } finally {
+ reenableLogs();
+ }
+ }
+ }
+ const shouldUpdate = instance.shouldComponentUpdate(
+ newProps,
+ newState,
+ nextContext,
+ );
+
+ if (__DEV__) {
+ if (shouldUpdate === undefined) {
+ console.error(
+ '%s.shouldComponentUpdate(): Returned undefined instead of a ' +
+ 'boolean value. Make sure to return true or false.',
+ getComponentName(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 = getComponentName(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.',
+ getComponentName(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.',
+ getComponentName(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',
+ getComponentName(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;
+ }
+
+ // Instantiate twice to help detect side-effects.
+ if (__DEV__) {
+ if (
+ debugRenderPhaseSideEffectsForStrictMode &&
+ workInProgress.mode & StrictMode
+ ) {
+ disableLogs();
+ try {
+ new ctor(props, context); // eslint-disable-line no-new
+ } finally {
+ reenableLogs();
+ }
+ }
+ }
+
+ const instance = new ctor(props, context);
+ 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 = getComponentName(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 = getComponentName(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://fb.me/react-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.',
+ getComponentName(workInProgress.type) || '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 =
+ getComponentName(workInProgress.type) || '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,
+ renderExpirationTime: ExpirationTime,
+): void {
+ if (__DEV__) {
+ checkClassInstance(workInProgress, ctor, newProps);
+ }
+
+ const instance = workInProgress.stateNode;
+ instance.props = newProps;
+ instance.state = workInProgress.memoizedState;
+ instance.refs = emptyRefsObject;
+
+ 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 = getComponentName(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 & StrictMode) {
+ ReactStrictModeWarnings.recordLegacyContextWarning(
+ workInProgress,
+ instance,
+ );
+ }
+
+ if (warnAboutDeprecatedLifecycles) {
+ ReactStrictModeWarnings.recordUnsafeLifecycleWarnings(
+ workInProgress,
+ instance,
+ );
+ }
+ }
+
+ processUpdateQueue(workInProgress, newProps, instance, renderExpirationTime);
+ 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,
+ renderExpirationTime,
+ );
+ instance.state = workInProgress.memoizedState;
+ }
+
+ if (typeof instance.componentDidMount === 'function') {
+ workInProgress.effectTag |= Update;
+ }
+}
+
+function resumeMountClassInstance(
+ workInProgress: Fiber,
+ ctor: any,
+ newProps: any,
+ renderExpirationTime: ExpirationTime,
+): 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, renderExpirationTime);
+ 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') {
+ workInProgress.effectTag |= Update;
+ }
+ 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') {
+ workInProgress.effectTag |= Update;
+ }
+ } 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') {
+ workInProgress.effectTag |= Update;
+ }
+
+ // 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,
+ renderExpirationTime: ExpirationTime,
+): 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, renderExpirationTime);
+ newState = workInProgress.memoizedState;
+
+ if (
+ unresolvedOldProps === unresolvedNewProps &&
+ 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.componentDidUpdate === 'function') {
+ if (
+ unresolvedOldProps !== current.memoizedProps ||
+ oldState !== current.memoizedState
+ ) {
+ workInProgress.effectTag |= Update;
+ }
+ }
+ if (typeof instance.getSnapshotBeforeUpdate === 'function') {
+ if (
+ unresolvedOldProps !== current.memoizedProps ||
+ oldState !== current.memoizedState
+ ) {
+ workInProgress.effectTag |= 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,
+ );
+
+ 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.effectTag |= Update;
+ }
+ if (typeof instance.getSnapshotBeforeUpdate === 'function') {
+ workInProgress.effectTag |= 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.effectTag |= Update;
+ }
+ }
+ if (typeof instance.getSnapshotBeforeUpdate === 'function') {
+ if (
+ unresolvedOldProps !== current.memoizedProps ||
+ oldState !== current.memoizedState
+ ) {
+ workInProgress.effectTag |= 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/ReactFiberCommitWork.new.js b/packages/react-reconciler/src/ReactFiberCommitWork.new.js
new file mode 100644
index 0000000000000..2901e8ea35b43
--- /dev/null
+++ b/packages/react-reconciler/src/ReactFiberCommitWork.new.js
@@ -0,0 +1,1837 @@
+/**
+ * Copyright (c) Facebook, Inc. and its 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} from './ReactInternalTypes';
+import type {FiberRoot} from './ReactInternalTypes';
+import type {ExpirationTime} from './ReactFiberExpirationTime';
+import type {SuspenseState} from './ReactFiberSuspenseComponent.new';
+import type {UpdateQueue} from './ReactUpdateQueue.new';
+import type {FunctionComponentUpdateQueue} from './ReactFiberHooks.new';
+import type {Wakeable} from 'shared/ReactTypes';
+import type {ReactPriorityLevel} from './ReactInternalTypes';
+
+import {unstable_wrap as Schedule_tracing_wrap} from 'scheduler/tracing';
+import {
+ deferPassiveEffectCleanupDuringUnmount,
+ enableSchedulerTracing,
+ enableProfilerTimer,
+ enableProfilerCommitHooks,
+ enableSuspenseServerRenderer,
+ enableDeprecatedFlareAPI,
+ enableFundamentalAPI,
+ enableSuspenseCallback,
+ enableScopeAPI,
+ runAllPassiveEffectDestroysBeforeCreates,
+ enableUseEventAPI,
+} from 'shared/ReactFeatureFlags';
+import {
+ FunctionComponent,
+ ForwardRef,
+ ClassComponent,
+ HostRoot,
+ HostComponent,
+ HostText,
+ HostPortal,
+ Profiler,
+ SuspenseComponent,
+ DehydratedFragment,
+ IncompleteClassComponent,
+ MemoComponent,
+ SimpleMemoComponent,
+ SuspenseListComponent,
+ FundamentalComponent,
+ ScopeComponent,
+ Block,
+} from './ReactWorkTags';
+import {
+ invokeGuardedCallback,
+ hasCaughtError,
+ clearCaughtError,
+} from 'shared/ReactErrorUtils';
+import {
+ NoEffect,
+ ContentReset,
+ Placement,
+ Snapshot,
+ Update,
+ Passive,
+} from './ReactSideEffectTags';
+import getComponentName from 'shared/getComponentName';
+import invariant from 'shared/invariant';
+
+import {onCommitUnmount} from './ReactFiberDevToolsHook.new';
+import {getStackByFiberInDevAndProd} from './ReactFiberComponentStack';
+import {resolveDefaultProps} from './ReactFiberLazyComponent.new';
+import {
+ getCommitTime,
+ recordLayoutEffectDuration,
+ recordPassiveEffectDuration,
+ startLayoutEffectTimer,
+ startPassiveEffectTimer,
+} from './ReactProfilerTimer.new';
+import {ProfileMode} from './ReactTypeOfMode';
+import {commitUpdateQueue} from './ReactUpdateQueue.new';
+import {
+ getPublicInstance,
+ supportsMutation,
+ supportsPersistence,
+ supportsHydration,
+ commitMount,
+ commitUpdate,
+ resetTextContent,
+ commitTextUpdate,
+ appendChild,
+ appendChildToContainer,
+ insertBefore,
+ insertInContainerBefore,
+ removeChild,
+ removeChildFromContainer,
+ clearSuspenseBoundary,
+ clearSuspenseBoundaryFromContainer,
+ replaceContainerChildren,
+ createContainerChildSet,
+ hideInstance,
+ hideTextInstance,
+ unhideInstance,
+ unhideTextInstance,
+ unmountFundamentalComponent,
+ updateFundamentalComponent,
+ commitHydratedContainer,
+ commitHydratedSuspenseInstance,
+ beforeRemoveInstance,
+} from './ReactFiberHostConfig';
+import {
+ captureCommitPhaseError,
+ resolveRetryWakeable,
+ markCommitTimeOfFallback,
+ enqueuePendingPassiveHookEffectMount,
+ enqueuePendingPassiveHookEffectUnmount,
+ enqueuePendingPassiveProfilerEffect,
+} from './ReactFiberWorkLoop.new';
+import {
+ NoEffect as NoHookEffect,
+ HasEffect as HookHasEffect,
+ Layout as HookLayout,
+ Passive as HookPassive,
+} from './ReactHookEffectTags';
+import {didWarnAboutReassigningProps} from './ReactFiberBeginWork.new';
+import {
+ runWithPriority,
+ NormalPriority,
+} from './SchedulerWithReactIntegration.new';
+import {
+ updateDeprecatedEventListeners,
+ unmountDeprecatedResponderListeners,
+} from './ReactFiberDeprecatedEvents.new';
+
+let didWarnAboutUndefinedSnapshotBeforeUpdate: Set | null = null;
+if (__DEV__) {
+ didWarnAboutUndefinedSnapshotBeforeUpdate = new Set();
+}
+
+const PossiblyWeakSet = typeof WeakSet === 'function' ? WeakSet : Set;
+
+const callComponentWillUnmountWithTimer = function(current, instance) {
+ instance.props = current.memoizedProps;
+ instance.state = current.memoizedState;
+ if (
+ enableProfilerTimer &&
+ enableProfilerCommitHooks &&
+ current.mode & ProfileMode
+ ) {
+ try {
+ startLayoutEffectTimer();
+ instance.componentWillUnmount();
+ } finally {
+ recordLayoutEffectDuration(current);
+ }
+ } else {
+ instance.componentWillUnmount();
+ }
+};
+
+// Capture errors so they don't interrupt unmounting.
+function safelyCallComponentWillUnmount(current, instance) {
+ if (__DEV__) {
+ invokeGuardedCallback(
+ null,
+ callComponentWillUnmountWithTimer,
+ null,
+ current,
+ instance,
+ );
+ if (hasCaughtError()) {
+ const unmountError = clearCaughtError();
+ captureCommitPhaseError(current, unmountError);
+ }
+ } else {
+ try {
+ callComponentWillUnmountWithTimer(current, instance);
+ } catch (unmountError) {
+ captureCommitPhaseError(current, unmountError);
+ }
+ }
+}
+
+function safelyDetachRef(current: Fiber) {
+ const ref = current.ref;
+ if (ref !== null) {
+ if (typeof ref === 'function') {
+ if (__DEV__) {
+ invokeGuardedCallback(null, ref, null, null);
+ if (hasCaughtError()) {
+ const refError = clearCaughtError();
+ captureCommitPhaseError(current, refError);
+ }
+ } else {
+ try {
+ ref(null);
+ } catch (refError) {
+ captureCommitPhaseError(current, refError);
+ }
+ }
+ } else {
+ ref.current = null;
+ }
+ }
+}
+
+function safelyCallDestroy(current, destroy) {
+ if (__DEV__) {
+ invokeGuardedCallback(null, destroy, null);
+ if (hasCaughtError()) {
+ const error = clearCaughtError();
+ captureCommitPhaseError(current, error);
+ }
+ } else {
+ try {
+ destroy();
+ } catch (error) {
+ captureCommitPhaseError(current, error);
+ }
+ }
+}
+
+function commitBeforeMutationLifeCycles(
+ current: Fiber | null,
+ finishedWork: Fiber,
+): void {
+ switch (finishedWork.tag) {
+ case FunctionComponent:
+ case ForwardRef:
+ case SimpleMemoComponent:
+ case Block: {
+ return;
+ }
+ case ClassComponent: {
+ if (finishedWork.effectTag & Snapshot) {
+ 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.',
+ getComponentName(finishedWork.type) || '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.',
+ getComponentName(finishedWork.type) || '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.',
+ getComponentName(finishedWork.type),
+ );
+ }
+ }
+ instance.__reactInternalSnapshotBeforeUpdate = snapshot;
+ }
+ }
+ return;
+ }
+ case HostRoot:
+ case HostComponent:
+ case HostText:
+ case HostPortal:
+ case IncompleteClassComponent:
+ // Nothing to do for these component types
+ return;
+ }
+ invariant(
+ false,
+ 'This unit of work tag should not have side-effects. This error is ' +
+ 'likely caused by a bug in React. Please file an issue.',
+ );
+}
+
+function commitHookEffectListUnmount(tag: number, 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 & tag) === tag) {
+ // Unmount
+ const destroy = effect.destroy;
+ effect.destroy = undefined;
+ if (destroy !== undefined) {
+ destroy();
+ }
+ }
+ effect = effect.next;
+ } while (effect !== firstEffect);
+ }
+}
+
+function commitHookEffectListMount(tag: number, 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 & tag) === tag) {
+ // Mount
+ const create = effect.create;
+ effect.destroy = create();
+
+ if (__DEV__) {
+ const destroy = effect.destroy;
+ if (destroy !== undefined && typeof destroy !== 'function') {
+ 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 useEffect(async () => ...) or returned a Promise. ' +
+ 'Instead, write the async function inside your effect ' +
+ 'and call it immediately:\n\n' +
+ 'useEffect(() => {\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://fb.me/react-hooks-data-fetching';
+ } else {
+ addendum = ' You returned: ' + destroy;
+ }
+ console.error(
+ 'An effect function must not return anything besides a function, ' +
+ 'which is used for clean-up.%s%s',
+ addendum,
+ getStackByFiberInDevAndProd(finishedWork),
+ );
+ }
+ }
+ }
+ effect = effect.next;
+ } while (effect !== firstEffect);
+ }
+}
+
+function schedulePassiveEffects(finishedWork: Fiber) {
+ if (runAllPassiveEffectDestroysBeforeCreates) {
+ 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 {
+ const {next, tag} = effect;
+ if (
+ (tag & HookPassive) !== NoHookEffect &&
+ (tag & HookHasEffect) !== NoHookEffect
+ ) {
+ enqueuePendingPassiveHookEffectUnmount(finishedWork, effect);
+ enqueuePendingPassiveHookEffectMount(finishedWork, effect);
+ }
+ effect = next;
+ } while (effect !== firstEffect);
+ }
+ }
+}
+
+export function commitPassiveHookEffects(finishedWork: Fiber): void {
+ if ((finishedWork.effectTag & Passive) !== NoEffect) {
+ switch (finishedWork.tag) {
+ case FunctionComponent:
+ case ForwardRef:
+ case SimpleMemoComponent:
+ case Block: {
+ // TODO (#17945) We should call all passive destroy functions (for all fibers)
+ // before calling any create functions. The current approach only serializes
+ // these for a single fiber.
+ if (
+ enableProfilerTimer &&
+ enableProfilerCommitHooks &&
+ finishedWork.mode & ProfileMode
+ ) {
+ try {
+ startPassiveEffectTimer();
+ commitHookEffectListUnmount(
+ HookPassive | HookHasEffect,
+ finishedWork,
+ );
+ commitHookEffectListMount(
+ HookPassive | HookHasEffect,
+ finishedWork,
+ );
+ } finally {
+ recordPassiveEffectDuration(finishedWork);
+ }
+ } else {
+ commitHookEffectListUnmount(
+ HookPassive | HookHasEffect,
+ finishedWork,
+ );
+ commitHookEffectListMount(HookPassive | HookHasEffect, finishedWork);
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ }
+}
+
+export function commitPassiveEffectDurations(
+ finishedRoot: FiberRoot,
+ finishedWork: Fiber,
+): void {
+ if (enableProfilerTimer && enableProfilerCommitHooks) {
+ // Only Profilers with work in their subtree will have an Update effect scheduled.
+ if ((finishedWork.effectTag & Update) !== NoEffect) {
+ 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();
+
+ if (typeof onPostCommit === 'function') {
+ if (enableSchedulerTracing) {
+ onPostCommit(
+ id,
+ finishedWork.alternate === null ? 'mount' : 'update',
+ passiveEffectDuration,
+ commitTime,
+ finishedRoot.memoizedInteractions,
+ );
+ } else {
+ onPostCommit(
+ id,
+ finishedWork.alternate === null ? 'mount' : 'update',
+ passiveEffectDuration,
+ commitTime,
+ );
+ }
+ }
+
+ // Bubble times to the next nearest ancestor Profiler.
+ // After we process that Profiler, we'll bubble further up.
+ let parentFiber = finishedWork.return;
+ while (parentFiber !== null) {
+ if (parentFiber.tag === Profiler) {
+ const parentStateNode = parentFiber.stateNode;
+ parentStateNode.passiveEffectDuration += passiveEffectDuration;
+ break;
+ }
+ parentFiber = parentFiber.return;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ }
+}
+
+function commitLifeCycles(
+ finishedRoot: FiberRoot,
+ current: Fiber | null,
+ finishedWork: Fiber,
+ committedExpirationTime: ExpirationTime,
+): void {
+ switch (finishedWork.tag) {
+ case FunctionComponent:
+ case ForwardRef:
+ case SimpleMemoComponent:
+ case Block: {
+ // 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 (
+ enableProfilerTimer &&
+ enableProfilerCommitHooks &&
+ finishedWork.mode & ProfileMode
+ ) {
+ try {
+ startLayoutEffectTimer();
+ commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);
+ } finally {
+ recordLayoutEffectDuration(finishedWork);
+ }
+ } else {
+ commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);
+ }
+
+ if (runAllPassiveEffectDestroysBeforeCreates) {
+ schedulePassiveEffects(finishedWork);
+ }
+ return;
+ }
+ case ClassComponent: {
+ const instance = finishedWork.stateNode;
+ if (finishedWork.effectTag & Update) {
+ 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.',
+ getComponentName(finishedWork.type) || '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.',
+ getComponentName(finishedWork.type) || 'instance',
+ );
+ }
+ }
+ }
+ if (
+ enableProfilerTimer &&
+ enableProfilerCommitHooks &&
+ finishedWork.mode & ProfileMode
+ ) {
+ try {
+ startLayoutEffectTimer();
+ instance.componentDidMount();
+ } finally {
+ recordLayoutEffectDuration(finishedWork);
+ }
+ } else {
+ instance.componentDidMount();
+ }
+ } 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.',
+ getComponentName(finishedWork.type) || '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.',
+ getComponentName(finishedWork.type) || 'instance',
+ );
+ }
+ }
+ }
+ if (
+ enableProfilerTimer &&
+ enableProfilerCommitHooks &&
+ finishedWork.mode & ProfileMode
+ ) {
+ try {
+ startLayoutEffectTimer();
+ instance.componentDidUpdate(
+ prevProps,
+ prevState,
+ instance.__reactInternalSnapshotBeforeUpdate,
+ );
+ } finally {
+ recordLayoutEffectDuration(finishedWork);
+ }
+ } else {
+ instance.componentDidUpdate(
+ prevProps,
+ prevState,
+ instance.__reactInternalSnapshotBeforeUpdate,
+ );
+ }
+ }
+ }
+
+ // 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) {
+ 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.',
+ getComponentName(finishedWork.type) || '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.',
+ getComponentName(finishedWork.type) || '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.
+ commitUpdateQueue(finishedWork, updateQueue, instance);
+ }
+ return;
+ }
+ case HostRoot: {
+ // 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 HostComponent:
+ instance = getPublicInstance(finishedWork.child.stateNode);
+ break;
+ case ClassComponent:
+ instance = finishedWork.child.stateNode;
+ break;
+ }
+ }
+ commitUpdateQueue(finishedWork, updateQueue, instance);
+ }
+ return;
+ }
+ case HostComponent: {
+ const instance: Instance = finishedWork.stateNode;
+
+ // 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 && finishedWork.effectTag & Update) {
+ const type = finishedWork.type;
+ const props = finishedWork.memoizedProps;
+ commitMount(instance, type, props, finishedWork);
+ }
+
+ return;
+ }
+ case HostText: {
+ // We have no life-cycles associated with text.
+ return;
+ }
+ case HostPortal: {
+ // We have no life-cycles associated with portals.
+ return;
+ }
+ case Profiler: {
+ if (enableProfilerTimer) {
+ const {onCommit, onRender} = finishedWork.memoizedProps;
+ const {effectDuration} = finishedWork.stateNode;
+
+ const commitTime = getCommitTime();
+
+ if (typeof onRender === 'function') {
+ if (enableSchedulerTracing) {
+ onRender(
+ finishedWork.memoizedProps.id,
+ current === null ? 'mount' : 'update',
+ finishedWork.actualDuration,
+ finishedWork.treeBaseDuration,
+ finishedWork.actualStartTime,
+ commitTime,
+ finishedRoot.memoizedInteractions,
+ );
+ } else {
+ onRender(
+ finishedWork.memoizedProps.id,
+ current === null ? 'mount' : 'update',
+ finishedWork.actualDuration,
+ finishedWork.treeBaseDuration,
+ finishedWork.actualStartTime,
+ commitTime,
+ );
+ }
+ }
+
+ if (enableProfilerCommitHooks) {
+ if (typeof onCommit === 'function') {
+ if (enableSchedulerTracing) {
+ onCommit(
+ finishedWork.memoizedProps.id,
+ current === null ? 'mount' : 'update',
+ effectDuration,
+ commitTime,
+ finishedRoot.memoizedInteractions,
+ );
+ } else {
+ onCommit(
+ finishedWork.memoizedProps.id,
+ current === null ? 'mount' : 'update',
+ 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;
+ while (parentFiber !== null) {
+ if (parentFiber.tag === Profiler) {
+ const parentStateNode = parentFiber.stateNode;
+ parentStateNode.effectDuration += effectDuration;
+ break;
+ }
+ parentFiber = parentFiber.return;
+ }
+ }
+ }
+ return;
+ }
+ case SuspenseComponent: {
+ commitSuspenseHydrationCallbacks(finishedRoot, finishedWork);
+ return;
+ }
+ case SuspenseListComponent:
+ case IncompleteClassComponent:
+ case FundamentalComponent:
+ case ScopeComponent:
+ return;
+ }
+ invariant(
+ false,
+ 'This unit of work tag should not have side-effects. This error is ' +
+ 'likely caused by a bug in React. Please file an issue.',
+ );
+}
+
+function hideOrUnhideAllChildren(finishedWork, isHidden) {
+ 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) {
+ const instance = node.stateNode;
+ if (isHidden) {
+ hideInstance(instance);
+ } else {
+ unhideInstance(node.stateNode, node.memoizedProps);
+ }
+ } else if (node.tag === HostText) {
+ const instance = node.stateNode;
+ if (isHidden) {
+ hideTextInstance(instance);
+ } else {
+ unhideTextInstance(instance, node.memoizedProps);
+ }
+ } else if (
+ node.tag === SuspenseComponent &&
+ node.memoizedState !== null &&
+ node.memoizedState.dehydrated === null
+ ) {
+ // Found a nested Suspense component that timed out. Skip over the
+ // primary child fragment, which should remain hidden.
+ const fallbackChildFragment: Fiber = (node.child: any).sibling;
+ fallbackChildFragment.return = node;
+ node = fallbackChildFragment;
+ continue;
+ } 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;
+ }
+ node = node.return;
+ }
+ 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 HostComponent:
+ instanceToUse = getPublicInstance(instance);
+ break;
+ default:
+ instanceToUse = instance;
+ }
+ // Moved outside to ensure DCE works with this flag
+ if (enableScopeAPI && finishedWork.tag === ScopeComponent) {
+ instanceToUse = instance.methods;
+ }
+ if (typeof ref === 'function') {
+ 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().%s',
+ getComponentName(finishedWork.type),
+ getStackByFiberInDevAndProd(finishedWork),
+ );
+ }
+ }
+
+ ref.current = instanceToUse;
+ }
+ }
+}
+
+function commitDetachRef(current: Fiber) {
+ const currentRef = current.ref;
+ if (currentRef !== null) {
+ if (typeof currentRef === 'function') {
+ currentRef(null);
+ } else {
+ currentRef.current = null;
+ }
+ }
+}
+
+// User-originating errors (lifecycles and refs) should not interrupt
+// deletion, so don't let them throw. Host-originating errors should
+// interrupt deletion, so it's okay
+function commitUnmount(
+ finishedRoot: FiberRoot,
+ current: Fiber,
+ renderPriorityLevel: ReactPriorityLevel,
+): void {
+ onCommitUnmount(current);
+
+ switch (current.tag) {
+ case FunctionComponent:
+ case ForwardRef:
+ case MemoComponent:
+ case SimpleMemoComponent:
+ case Block: {
+ const updateQueue: FunctionComponentUpdateQueue | null = (current.updateQueue: any);
+ if (updateQueue !== null) {
+ const lastEffect = updateQueue.lastEffect;
+ if (lastEffect !== null) {
+ const firstEffect = lastEffect.next;
+
+ if (
+ deferPassiveEffectCleanupDuringUnmount &&
+ runAllPassiveEffectDestroysBeforeCreates
+ ) {
+ let effect = firstEffect;
+ do {
+ const {destroy, tag} = effect;
+ if (destroy !== undefined) {
+ if ((tag & HookPassive) !== NoHookEffect) {
+ enqueuePendingPassiveHookEffectUnmount(current, effect);
+ } else {
+ if (
+ enableProfilerTimer &&
+ enableProfilerCommitHooks &&
+ current.mode & ProfileMode
+ ) {
+ startLayoutEffectTimer();
+ safelyCallDestroy(current, destroy);
+ recordLayoutEffectDuration(current);
+ } else {
+ safelyCallDestroy(current, destroy);
+ }
+ }
+ }
+ effect = effect.next;
+ } while (effect !== firstEffect);
+ } else {
+ // When the owner fiber is deleted, the destroy function of a passive
+ // effect hook is called during the synchronous commit phase. This is
+ // a concession to implementation complexity. Calling it in the
+ // passive effect phase (like they usually are, when dependencies
+ // change during an update) would require either traversing the
+ // children of the deleted fiber again, or including unmount effects
+ // as part of the fiber effect list.
+ //
+ // Because this is during the sync commit phase, we need to change
+ // the priority.
+ //
+ // TODO: Reconsider this implementation trade off.
+ const priorityLevel =
+ renderPriorityLevel > NormalPriority
+ ? NormalPriority
+ : renderPriorityLevel;
+ runWithPriority(priorityLevel, () => {
+ let effect = firstEffect;
+ do {
+ const {destroy, tag} = effect;
+ if (destroy !== undefined) {
+ if (
+ enableProfilerTimer &&
+ enableProfilerCommitHooks &&
+ current.mode & ProfileMode
+ ) {
+ if ((tag & HookPassive) !== NoHookEffect) {
+ safelyCallDestroy(current, destroy);
+ } else {
+ startLayoutEffectTimer();
+ safelyCallDestroy(current, destroy);
+ recordLayoutEffectDuration(current);
+ }
+ } else {
+ safelyCallDestroy(current, destroy);
+ }
+ }
+ effect = effect.next;
+ } while (effect !== firstEffect);
+ });
+ }
+ }
+ }
+ return;
+ }
+ case ClassComponent: {
+ safelyDetachRef(current);
+ const instance = current.stateNode;
+ if (typeof instance.componentWillUnmount === 'function') {
+ safelyCallComponentWillUnmount(current, instance);
+ }
+ return;
+ }
+ case HostComponent: {
+ if (enableDeprecatedFlareAPI) {
+ unmountDeprecatedResponderListeners(current);
+ }
+ if (enableDeprecatedFlareAPI || enableUseEventAPI) {
+ beforeRemoveInstance(current.stateNode);
+ }
+ safelyDetachRef(current);
+ return;
+ }
+ case HostPortal: {
+ // TODO: this is recursive.
+ // We are also not using this parent because
+ // the portal will get pushed immediately.
+ if (supportsMutation) {
+ unmountHostComponents(finishedRoot, current, renderPriorityLevel);
+ } else if (supportsPersistence) {
+ emptyPortalContainer(current);
+ }
+ return;
+ }
+ case FundamentalComponent: {
+ if (enableFundamentalAPI) {
+ const fundamentalInstance = current.stateNode;
+ if (fundamentalInstance !== null) {
+ unmountFundamentalComponent(fundamentalInstance);
+ current.stateNode = null;
+ }
+ }
+ return;
+ }
+ case DehydratedFragment: {
+ if (enableSuspenseCallback) {
+ const hydrationCallbacks = finishedRoot.hydrationCallbacks;
+ if (hydrationCallbacks !== null) {
+ const onDeleted = hydrationCallbacks.onDeleted;
+ if (onDeleted) {
+ onDeleted((current.stateNode: SuspenseInstance));
+ }
+ }
+ }
+ return;
+ }
+ case ScopeComponent: {
+ if (enableDeprecatedFlareAPI) {
+ unmountDeprecatedResponderListeners(current);
+ }
+ if (enableScopeAPI) {
+ safelyDetachRef(current);
+ }
+ return;
+ }
+ }
+}
+
+function commitNestedUnmounts(
+ finishedRoot: FiberRoot,
+ root: Fiber,
+ renderPriorityLevel: ReactPriorityLevel,
+): void {
+ // While we're inside a removed host node we don't want to call
+ // removeChild on the inner nodes because they're removed by the top
+ // call anyway. We also want to call componentWillUnmount on all
+ // composites before this host node is removed from the tree. Therefore
+ // we do an inner loop while we're still inside the host node.
+ let node: Fiber = root;
+ while (true) {
+ commitUnmount(finishedRoot, node, renderPriorityLevel);
+ // Visit children because they may contain more composite or host nodes.
+ // Skip portals because commitUnmount() currently visits them recursively.
+ if (
+ node.child !== null &&
+ // If we use mutation we drill down into portals using commitUnmount above.
+ // If we don't use mutation we drill down into portals here instead.
+ (!supportsMutation || node.tag !== HostPortal)
+ ) {
+ node.child.return = node;
+ node = node.child;
+ continue;
+ }
+ if (node === root) {
+ return;
+ }
+ while (node.sibling === null) {
+ if (node.return === null || node.return === root) {
+ return;
+ }
+ node = node.return;
+ }
+ node.sibling.return = node.return;
+ node = node.sibling;
+ }
+}
+
+function detachFiber(fiber: Fiber) {
+ // Cut off the return pointers to disconnect it from the tree. Ideally, we
+ // should 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.
+ fiber.return = null;
+ fiber.child = null;
+ fiber.memoizedState = null;
+ fiber.updateQueue = null;
+ fiber.dependencies = null;
+ fiber.alternate = null;
+ fiber.firstEffect = null;
+ fiber.lastEffect = null;
+ fiber.pendingProps = null;
+ fiber.memoizedProps = null;
+ fiber.stateNode = 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 commitContainer(finishedWork: Fiber) {
+ if (!supportsPersistence) {
+ return;
+ }
+
+ switch (finishedWork.tag) {
+ case ClassComponent:
+ case HostComponent:
+ case HostText:
+ case FundamentalComponent: {
+ return;
+ }
+ case HostRoot:
+ case HostPortal: {
+ const portalOrRoot: {
+ containerInfo: Container,
+ pendingChildren: ChildSet,
+ ...
+ } = finishedWork.stateNode;
+ const {containerInfo, pendingChildren} = portalOrRoot;
+ replaceContainerChildren(containerInfo, pendingChildren);
+ return;
+ }
+ }
+ invariant(
+ false,
+ 'This unit of work tag should not have side-effects. This error is ' +
+ 'likely caused by a bug in React. Please file an issue.',
+ );
+}
+
+function getHostParentFiber(fiber: Fiber): Fiber {
+ let parent = fiber.return;
+ while (parent !== null) {
+ if (isHostParent(parent)) {
+ return parent;
+ }
+ parent = parent.return;
+ }
+ invariant(
+ false,
+ '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 ||
+ 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;
+ }
+ node = node.return;
+ }
+ node.sibling.return = node.return;
+ node = node.sibling;
+ while (
+ node.tag !== HostComponent &&
+ node.tag !== HostText &&
+ 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.effectTag & 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.effectTag & Placement)) {
+ // Found it!
+ return node.stateNode;
+ }
+ }
+}
+
+function commitPlacement(finishedWork: Fiber): void {
+ if (!supportsMutation) {
+ return;
+ }
+
+ // Recursively insert all host nodes into the parent.
+ const parentFiber = getHostParentFiber(finishedWork);
+
+ // Note: these two variables *must* always be updated together.
+ let parent;
+ let isContainer;
+ const parentStateNode = parentFiber.stateNode;
+ switch (parentFiber.tag) {
+ case HostComponent:
+ parent = parentStateNode;
+ isContainer = false;
+ break;
+ case HostRoot:
+ parent = parentStateNode.containerInfo;
+ isContainer = true;
+ break;
+ case HostPortal:
+ parent = parentStateNode.containerInfo;
+ isContainer = true;
+ break;
+ case FundamentalComponent:
+ if (enableFundamentalAPI) {
+ parent = parentStateNode.instance;
+ isContainer = false;
+ }
+ // eslint-disable-next-line-no-fallthrough
+ default:
+ invariant(
+ false,
+ 'Invalid host parent fiber. This error is likely caused by a bug ' +
+ 'in React. Please file an issue.',
+ );
+ }
+ if (parentFiber.effectTag & ContentReset) {
+ // Reset the text content of the parent before doing any insertions
+ resetTextContent(parent);
+ // Clear ContentReset from the effect tag
+ parentFiber.effectTag &= ~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.
+ if (isContainer) {
+ insertOrAppendPlacementNodeIntoContainer(finishedWork, before, parent);
+ } else {
+ insertOrAppendPlacementNode(finishedWork, before, parent);
+ }
+}
+
+function insertOrAppendPlacementNodeIntoContainer(
+ node: Fiber,
+ before: ?Instance,
+ parent: Container,
+): void {
+ const {tag} = node;
+ const isHost = tag === HostComponent || tag === HostText;
+ if (isHost || (enableFundamentalAPI && tag === FundamentalComponent)) {
+ const stateNode = isHost ? node.stateNode : node.stateNode.instance;
+ if (before) {
+ insertInContainerBefore(parent, stateNode, before);
+ } else {
+ appendChildToContainer(parent, stateNode);
+ }
+ } else if (tag === HostPortal) {
+ // 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.
+ } 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 || (enableFundamentalAPI && tag === FundamentalComponent)) {
+ const stateNode = isHost ? node.stateNode : node.stateNode.instance;
+ if (before) {
+ insertBefore(parent, stateNode, before);
+ } else {
+ appendChild(parent, stateNode);
+ }
+ } else if (tag === HostPortal) {
+ // 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.
+ } 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;
+ }
+ }
+ }
+}
+
+function unmountHostComponents(
+ finishedRoot,
+ current,
+ renderPriorityLevel,
+): void {
+ // We only have the top Fiber that was deleted but we need to recurse down its
+ // children to find all the terminal nodes.
+ let node: Fiber = current;
+
+ // Each iteration, currentParent is populated with node's host parent if not
+ // currentParentIsValid.
+ let currentParentIsValid = false;
+
+ // Note: these two variables *must* always be updated together.
+ let currentParent;
+ let currentParentIsContainer;
+
+ while (true) {
+ if (!currentParentIsValid) {
+ let parent = node.return;
+ findParent: while (true) {
+ invariant(
+ parent !== null,
+ 'Expected to find a host parent. This error is likely caused by ' +
+ 'a bug in React. Please file an issue.',
+ );
+ const parentStateNode = parent.stateNode;
+ switch (parent.tag) {
+ case HostComponent:
+ currentParent = parentStateNode;
+ currentParentIsContainer = false;
+ break findParent;
+ case HostRoot:
+ currentParent = parentStateNode.containerInfo;
+ currentParentIsContainer = true;
+ break findParent;
+ case HostPortal:
+ currentParent = parentStateNode.containerInfo;
+ currentParentIsContainer = true;
+ break findParent;
+ case FundamentalComponent:
+ if (enableFundamentalAPI) {
+ currentParent = parentStateNode.instance;
+ currentParentIsContainer = false;
+ }
+ }
+ parent = parent.return;
+ }
+ currentParentIsValid = true;
+ }
+
+ if (node.tag === HostComponent || node.tag === HostText) {
+ commitNestedUnmounts(finishedRoot, node, renderPriorityLevel);
+ // After all the children have unmounted, it is now safe to remove the
+ // node from the tree.
+ if (currentParentIsContainer) {
+ removeChildFromContainer(
+ ((currentParent: any): Container),
+ (node.stateNode: Instance | TextInstance),
+ );
+ } else {
+ removeChild(
+ ((currentParent: any): Instance),
+ (node.stateNode: Instance | TextInstance),
+ );
+ }
+ // Don't visit children because we already visited them.
+ } else if (enableFundamentalAPI && node.tag === FundamentalComponent) {
+ const fundamentalNode = node.stateNode.instance;
+ commitNestedUnmounts(finishedRoot, node, renderPriorityLevel);
+ // After all the children have unmounted, it is now safe to remove the
+ // node from the tree.
+ if (currentParentIsContainer) {
+ removeChildFromContainer(
+ ((currentParent: any): Container),
+ (fundamentalNode: Instance),
+ );
+ } else {
+ removeChild(
+ ((currentParent: any): Instance),
+ (fundamentalNode: Instance),
+ );
+ }
+ } else if (
+ enableSuspenseServerRenderer &&
+ node.tag === DehydratedFragment
+ ) {
+ if (enableSuspenseCallback) {
+ const hydrationCallbacks = finishedRoot.hydrationCallbacks;
+ if (hydrationCallbacks !== null) {
+ const onDeleted = hydrationCallbacks.onDeleted;
+ if (onDeleted) {
+ onDeleted((node.stateNode: SuspenseInstance));
+ }
+ }
+ }
+
+ // Delete the dehydrated suspense boundary and all of its content.
+ if (currentParentIsContainer) {
+ clearSuspenseBoundaryFromContainer(
+ ((currentParent: any): Container),
+ (node.stateNode: SuspenseInstance),
+ );
+ } else {
+ clearSuspenseBoundary(
+ ((currentParent: any): Instance),
+ (node.stateNode: SuspenseInstance),
+ );
+ }
+ } else if (node.tag === HostPortal) {
+ if (node.child !== null) {
+ // When we go into a portal, it becomes the parent to remove from.
+ // We will reassign it back when we pop the portal on the way up.
+ currentParent = node.stateNode.containerInfo;
+ currentParentIsContainer = true;
+ // Visit children because portals might contain host components.
+ node.child.return = node;
+ node = node.child;
+ continue;
+ }
+ } else {
+ commitUnmount(finishedRoot, node, renderPriorityLevel);
+ // Visit children because we may find more host components below.
+ if (node.child !== null) {
+ node.child.return = node;
+ node = node.child;
+ continue;
+ }
+ }
+ if (node === current) {
+ return;
+ }
+ while (node.sibling === null) {
+ if (node.return === null || node.return === current) {
+ return;
+ }
+ node = node.return;
+ if (node.tag === HostPortal) {
+ // When we go out of the portal, we need to restore the parent.
+ // Since we don't keep a stack of them, we will search for it.
+ currentParentIsValid = false;
+ }
+ }
+ node.sibling.return = node.return;
+ node = node.sibling;
+ }
+}
+
+function commitDeletion(
+ finishedRoot: FiberRoot,
+ current: Fiber,
+ renderPriorityLevel: ReactPriorityLevel,
+): void {
+ if (supportsMutation) {
+ // Recursively delete all host nodes from the parent.
+ // Detach refs and call componentWillUnmount() on the whole subtree.
+ unmountHostComponents(finishedRoot, current, renderPriorityLevel);
+ } else {
+ // Detach refs and call componentWillUnmount() on the whole subtree.
+ commitNestedUnmounts(finishedRoot, current, renderPriorityLevel);
+ }
+ const alternate = current.alternate;
+ detachFiber(current);
+ if (alternate !== null) {
+ detachFiber(alternate);
+ }
+}
+
+function commitWork(current: Fiber | null, finishedWork: Fiber): void {
+ if (!supportsMutation) {
+ switch (finishedWork.tag) {
+ case FunctionComponent:
+ case ForwardRef:
+ case MemoComponent:
+ case SimpleMemoComponent:
+ case Block: {
+ // 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 (
+ enableProfilerTimer &&
+ enableProfilerCommitHooks &&
+ finishedWork.mode & ProfileMode
+ ) {
+ try {
+ startLayoutEffectTimer();
+ commitHookEffectListUnmount(
+ HookLayout | HookHasEffect,
+ finishedWork,
+ );
+ } finally {
+ recordLayoutEffectDuration(finishedWork);
+ }
+ } else {
+ commitHookEffectListUnmount(HookLayout | HookHasEffect, finishedWork);
+ }
+ return;
+ }
+ case Profiler: {
+ return;
+ }
+ case SuspenseComponent: {
+ commitSuspenseComponent(finishedWork);
+ attachSuspenseRetryListeners(finishedWork);
+ return;
+ }
+ case SuspenseListComponent: {
+ attachSuspenseRetryListeners(finishedWork);
+ return;
+ }
+ case HostRoot: {
+ if (supportsHydration) {
+ const root: FiberRoot = finishedWork.stateNode;
+ if (root.hydrate) {
+ // We've just hydrated. No need to hydrate again.
+ root.hydrate = false;
+ commitHydratedContainer(root.containerInfo);
+ }
+ }
+ break;
+ }
+ }
+
+ commitContainer(finishedWork);
+ return;
+ }
+
+ switch (finishedWork.tag) {
+ case FunctionComponent:
+ case ForwardRef:
+ case MemoComponent:
+ case SimpleMemoComponent:
+ case Block: {
+ // 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 (
+ enableProfilerTimer &&
+ enableProfilerCommitHooks &&
+ finishedWork.mode & ProfileMode
+ ) {
+ try {
+ startLayoutEffectTimer();
+ commitHookEffectListUnmount(HookLayout | HookHasEffect, finishedWork);
+ } finally {
+ recordLayoutEffectDuration(finishedWork);
+ }
+ } else {
+ commitHookEffectListUnmount(HookLayout | HookHasEffect, finishedWork);
+ }
+ return;
+ }
+ case ClassComponent: {
+ return;
+ }
+ case HostComponent: {
+ 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) {
+ commitUpdate(
+ instance,
+ updatePayload,
+ type,
+ oldProps,
+ newProps,
+ finishedWork,
+ );
+ }
+ if (enableDeprecatedFlareAPI) {
+ const prevListeners = oldProps.DEPRECATED_flareListeners;
+ const nextListeners = newProps.DEPRECATED_flareListeners;
+ if (prevListeners !== nextListeners) {
+ updateDeprecatedEventListeners(nextListeners, finishedWork, null);
+ }
+ }
+ }
+ return;
+ }
+ case HostText: {
+ invariant(
+ finishedWork.stateNode !== null,
+ '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;
+ commitTextUpdate(textInstance, oldText, newText);
+ return;
+ }
+ case HostRoot: {
+ if (supportsHydration) {
+ const root: FiberRoot = finishedWork.stateNode;
+ if (root.hydrate) {
+ // We've just hydrated. No need to hydrate again.
+ root.hydrate = false;
+ commitHydratedContainer(root.containerInfo);
+ }
+ }
+ return;
+ }
+ case Profiler: {
+ return;
+ }
+ case SuspenseComponent: {
+ commitSuspenseComponent(finishedWork);
+ attachSuspenseRetryListeners(finishedWork);
+ return;
+ }
+ case SuspenseListComponent: {
+ attachSuspenseRetryListeners(finishedWork);
+ return;
+ }
+ case IncompleteClassComponent: {
+ return;
+ }
+ case FundamentalComponent: {
+ if (enableFundamentalAPI) {
+ const fundamentalInstance = finishedWork.stateNode;
+ updateFundamentalComponent(fundamentalInstance);
+ return;
+ }
+ break;
+ }
+ case ScopeComponent: {
+ if (enableScopeAPI) {
+ const scopeInstance = finishedWork.stateNode;
+ scopeInstance.fiber = finishedWork;
+ if (enableDeprecatedFlareAPI) {
+ const newProps = finishedWork.memoizedProps;
+ const oldProps = current !== null ? current.memoizedProps : newProps;
+ const prevListeners = oldProps.DEPRECATED_flareListeners;
+ const nextListeners = newProps.DEPRECATED_flareListeners;
+ if (prevListeners !== nextListeners || current === null) {
+ updateDeprecatedEventListeners(nextListeners, finishedWork, null);
+ }
+ }
+ return;
+ }
+ break;
+ }
+ }
+ invariant(
+ false,
+ 'This unit of work tag should not have side-effects. This error is ' +
+ 'likely caused by a bug in React. Please file an issue.',
+ );
+}
+
+function commitSuspenseComponent(finishedWork: Fiber) {
+ const newState: SuspenseState | null = finishedWork.memoizedState;
+
+ let newDidTimeout;
+ let primaryChildParent = finishedWork;
+ if (newState === null) {
+ newDidTimeout = false;
+ } else {
+ newDidTimeout = true;
+ primaryChildParent = finishedWork.child;
+ markCommitTimeOfFallback();
+ }
+
+ if (supportsMutation && primaryChildParent !== null) {
+ hideOrUnhideAllChildren(primaryChildParent, newDidTimeout);
+ }
+
+ 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) {
+ commitHydratedSuspenseInstance(suspenseInstance);
+ if (enableSuspenseCallback) {
+ const hydrationCallbacks = finishedRoot.hydrationCallbacks;
+ if (hydrationCallbacks !== null) {
+ const onHydrated = hydrationCallbacks.onHydrated;
+ if (onHydrated) {
+ onHydrated(suspenseInstance);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+function attachSuspenseRetryListeners(finishedWork: Fiber) {
+ // 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 wakeables: Set | null = (finishedWork.updateQueue: any);
+ if (wakeables !== null) {
+ finishedWork.updateQueue = null;
+ let retryCache = finishedWork.stateNode;
+ if (retryCache === null) {
+ retryCache = finishedWork.stateNode = new PossiblyWeakSet();
+ }
+ wakeables.forEach(wakeable => {
+ // Memoize using the boundary fiber to prevent redundant listeners.
+ let retry = resolveRetryWakeable.bind(null, finishedWork, wakeable);
+ if (!retryCache.has(wakeable)) {
+ if (enableSchedulerTracing) {
+ if (wakeable.__reactDoNotTraceInteractions !== true) {
+ retry = Schedule_tracing_wrap(retry);
+ }
+ }
+ retryCache.add(wakeable);
+ wakeable.then(retry, retry);
+ }
+ });
+ }
+}
+
+function commitResetTextContent(current: Fiber) {
+ if (!supportsMutation) {
+ return;
+ }
+ resetTextContent(current.stateNode);
+}
+
+export {
+ commitBeforeMutationLifeCycles,
+ commitResetTextContent,
+ commitPlacement,
+ commitDeletion,
+ commitWork,
+ commitLifeCycles,
+ commitAttachRef,
+ commitDetachRef,
+};
diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.new.js b/packages/react-reconciler/src/ReactFiberCompleteWork.new.js
new file mode 100644
index 0000000000000..b4ef64b2d9aed
--- /dev/null
+++ b/packages/react-reconciler/src/ReactFiberCompleteWork.new.js
@@ -0,0 +1,1314 @@
+/**
+ * Copyright (c) Facebook, Inc. and its 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 {ExpirationTime} from './ReactFiberExpirationTime';
+import type {
+ ReactFundamentalComponentInstance,
+ ReactScopeInstance,
+} from 'shared/ReactTypes';
+import type {FiberRoot} from './ReactInternalTypes';
+import type {
+ Instance,
+ Type,
+ Props,
+ Container,
+ ChildSet,
+} from './ReactFiberHostConfig';
+import type {
+ SuspenseState,
+ SuspenseListRenderState,
+} from './ReactFiberSuspenseComponent.new';
+import type {SuspenseContext} from './ReactFiberSuspenseContext.new';
+import {resetWorkInProgressVersions as resetMutableSourceWorkInProgressVersions} from './ReactMutableSource.new';
+
+import {now} from './SchedulerWithReactIntegration.new';
+
+import {
+ IndeterminateComponent,
+ FunctionComponent,
+ ClassComponent,
+ HostRoot,
+ HostComponent,
+ HostText,
+ HostPortal,
+ ContextProvider,
+ ContextConsumer,
+ ForwardRef,
+ Fragment,
+ Mode,
+ Profiler,
+ SuspenseComponent,
+ SuspenseListComponent,
+ MemoComponent,
+ SimpleMemoComponent,
+ LazyComponent,
+ IncompleteClassComponent,
+ FundamentalComponent,
+ ScopeComponent,
+ Block,
+} from './ReactWorkTags';
+import {NoMode, BlockingMode} from './ReactTypeOfMode';
+import {
+ Ref,
+ Update,
+ NoEffect,
+ DidCapture,
+ Deletion,
+} from './ReactSideEffectTags';
+import invariant from 'shared/invariant';
+
+import {
+ createInstance,
+ createTextInstance,
+ appendInitialChild,
+ finalizeInitialChildren,
+ prepareUpdate,
+ supportsMutation,
+ supportsPersistence,
+ cloneInstance,
+ cloneHiddenInstance,
+ cloneHiddenTextInstance,
+ createContainerChildSet,
+ appendChildToContainerChildSet,
+ finalizeContainerChildren,
+ getFundamentalComponentInstance,
+ mountFundamentalComponent,
+ cloneFundamentalInstance,
+ shouldUpdateFundamentalComponent,
+} from './ReactFiberHostConfig';
+import {
+ getRootHostContainer,
+ popHostContext,
+ getHostContext,
+ popHostContainer,
+} from './ReactFiberHostContext.new';
+import {
+ suspenseStackCursor,
+ InvisibleParentSuspenseContext,
+ hasSuspenseContext,
+ popSuspenseContext,
+ pushSuspenseContext,
+ setShallowSuspenseContext,
+ ForceSuspenseFallback,
+ setDefaultShallowSuspenseContext,
+} from './ReactFiberSuspenseContext.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,
+ popHydrationState,
+ resetHydrationState,
+} from './ReactFiberHydrationContext.new';
+import {
+ enableSchedulerTracing,
+ enableSuspenseCallback,
+ enableSuspenseServerRenderer,
+ enableDeprecatedFlareAPI,
+ enableFundamentalAPI,
+ enableScopeAPI,
+ enableBlocksAPI,
+} from 'shared/ReactFeatureFlags';
+import {
+ markSpawnedWork,
+ renderDidSuspend,
+ renderDidSuspendDelayIfPossible,
+ renderHasNotSuspendedYet,
+} from './ReactFiberWorkLoop.new';
+import {createFundamentalStateInstance} from './ReactFiberFundamental.new';
+import {Never} from './ReactFiberExpirationTime';
+import {resetChildFibers} from './ReactChildFiber.new';
+import {updateDeprecatedEventListeners} from './ReactFiberDeprecatedEvents.new';
+import {createScopeMethods} from './ReactFiberScope.new';
+
+function markUpdate(workInProgress: Fiber) {
+ // Tag the fiber with an update effect. This turns a Placement into
+ // a PlacementAndUpdate.
+ workInProgress.effectTag |= Update;
+}
+
+function markRef(workInProgress: Fiber) {
+ workInProgress.effectTag |= Ref;
+}
+
+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 (enableFundamentalAPI && node.tag === FundamentalComponent) {
+ appendInitialChild(parent, node.stateNode.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.child !== null) {
+ node.child.return = node;
+ node = node.child;
+ continue;
+ }
+ if (node === workInProgress) {
+ return;
+ }
+ while (node.sibling === null) {
+ if (node.return === null || node.return === workInProgress) {
+ return;
+ }
+ node = node.return;
+ }
+ node.sibling.return = node.return;
+ node = node.sibling;
+ }
+ };
+
+ updateHostContainer = function(workInProgress: Fiber) {
+ // Noop
+ };
+ updateHostComponent = function(
+ current: Fiber,
+ workInProgress: Fiber,
+ type: Type,
+ newProps: Props,
+ rootContainerInstance: Container,
+ ) {
+ // 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,
+ rootContainerInstance,
+ 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 (enableFundamentalAPI && node.tag === FundamentalComponent) {
+ let instance = node.stateNode.instance;
+ 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 === 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 === SuspenseComponent) {
+ if ((node.effectTag & Update) !== NoEffect) {
+ // Need to toggle the visibility of the primary children.
+ const newIsHidden = node.memoizedState !== null;
+ if (newIsHidden) {
+ const primaryChildParent = node.child;
+ if (primaryChildParent !== null) {
+ if (primaryChildParent.child !== null) {
+ primaryChildParent.child.return = primaryChildParent;
+ appendAllChildren(
+ parent,
+ primaryChildParent,
+ true,
+ newIsHidden,
+ );
+ }
+ const fallbackChildParent = primaryChildParent.sibling;
+ if (fallbackChildParent !== null) {
+ fallbackChildParent.return = node;
+ node = fallbackChildParent;
+ continue;
+ }
+ }
+ }
+ }
+ if (node.child !== null) {
+ // Continue traversing like normal
+ node.child.return = node;
+ node = node.child;
+ continue;
+ }
+ } else if (node.child !== null) {
+ node.child.return = node;
+ node = node.child;
+ continue;
+ }
+ // $FlowFixMe This is correct but Flow is confused by the labeled break.
+ node = (node: Fiber);
+ if (node === workInProgress) {
+ return;
+ }
+ while (node.sibling === null) {
+ if (node.return === null || node.return === workInProgress) {
+ return;
+ }
+ node = node.return;
+ }
+ 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 (enableFundamentalAPI && node.tag === FundamentalComponent) {
+ let instance = node.stateNode.instance;
+ 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 === 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 === SuspenseComponent) {
+ if ((node.effectTag & Update) !== NoEffect) {
+ // Need to toggle the visibility of the primary children.
+ const newIsHidden = node.memoizedState !== null;
+ if (newIsHidden) {
+ const primaryChildParent = node.child;
+ if (primaryChildParent !== null) {
+ if (primaryChildParent.child !== null) {
+ primaryChildParent.child.return = primaryChildParent;
+ appendAllChildrenToContainer(
+ containerChildSet,
+ primaryChildParent,
+ true,
+ newIsHidden,
+ );
+ }
+ const fallbackChildParent = primaryChildParent.sibling;
+ if (fallbackChildParent !== null) {
+ fallbackChildParent.return = node;
+ node = fallbackChildParent;
+ continue;
+ }
+ }
+ }
+ }
+ if (node.child !== null) {
+ // Continue traversing like normal
+ node.child.return = node;
+ node = node.child;
+ continue;
+ }
+ } else if (node.child !== null) {
+ node.child.return = node;
+ node = node.child;
+ continue;
+ }
+ // $FlowFixMe This is correct but Flow is confused by the labeled break.
+ node = (node: Fiber);
+ if (node === workInProgress) {
+ return;
+ }
+ while (node.sibling === null) {
+ if (node.return === null || node.return === workInProgress) {
+ return;
+ }
+ node = node.return;
+ }
+ node.sibling.return = node.return;
+ node = node.sibling;
+ }
+ };
+ updateHostContainer = function(workInProgress: Fiber) {
+ const portalOrRoot: {
+ containerInfo: Container,
+ pendingChildren: ChildSet,
+ ...
+ } = workInProgress.stateNode;
+ const childrenUnchanged = workInProgress.firstEffect === null;
+ 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,
+ rootContainerInstance: Container,
+ ) {
+ 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 = workInProgress.firstEffect === null;
+ 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,
+ rootContainerInstance,
+ 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,
+ rootContainerInstance,
+ 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(workInProgress: Fiber) {
+ // Noop
+ };
+ updateHostComponent = function(
+ current: Fiber,
+ workInProgress: Fiber,
+ type: Type,
+ newProps: Props,
+ rootContainerInstance: Container,
+ ) {
+ // Noop
+ };
+ updateHostText = function(
+ current: Fiber,
+ workInProgress: Fiber,
+ oldText: string,
+ newText: string,
+ ) {
+ // Noop
+ };
+}
+
+function cutOffTailIfNeeded(
+ renderState: SuspenseListRenderState,
+ hasRenderedATailFallback: boolean,
+) {
+ 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 completeWork(
+ current: Fiber | null,
+ workInProgress: Fiber,
+ renderExpirationTime: ExpirationTime,
+): Fiber | null {
+ const newProps = workInProgress.pendingProps;
+
+ switch (workInProgress.tag) {
+ case IndeterminateComponent:
+ case LazyComponent:
+ case SimpleMemoComponent:
+ case FunctionComponent:
+ case ForwardRef:
+ case Fragment:
+ case Mode:
+ case Profiler:
+ case ContextConsumer:
+ case MemoComponent:
+ return null;
+ case ClassComponent: {
+ const Component = workInProgress.type;
+ if (isLegacyContextProvider(Component)) {
+ popLegacyContext(workInProgress);
+ }
+ return null;
+ }
+ case HostRoot: {
+ popHostContainer(workInProgress);
+ popTopLevelLegacyContextObject(workInProgress);
+ resetMutableSourceWorkInProgressVersions();
+ const fiberRoot = (workInProgress.stateNode: FiberRoot);
+ 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);
+ }
+ }
+ updateHostContainer(workInProgress);
+ return null;
+ }
+ case HostComponent: {
+ popHostContext(workInProgress);
+ const rootContainerInstance = getRootHostContainer();
+ const type = workInProgress.type;
+ if (current !== null && workInProgress.stateNode != null) {
+ updateHostComponent(
+ current,
+ workInProgress,
+ type,
+ newProps,
+ rootContainerInstance,
+ );
+
+ if (enableDeprecatedFlareAPI) {
+ const prevListeners = current.memoizedProps.DEPRECATED_flareListeners;
+ const nextListeners = newProps.DEPRECATED_flareListeners;
+ if (prevListeners !== nextListeners) {
+ markUpdate(workInProgress);
+ }
+ }
+
+ if (current.ref !== workInProgress.ref) {
+ markRef(workInProgress);
+ }
+ } else {
+ if (!newProps) {
+ invariant(
+ workInProgress.stateNode !== null,
+ '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.
+ 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,
+ rootContainerInstance,
+ currentHostContext,
+ )
+ ) {
+ // If changes to the hydrated node need to be applied at the
+ // commit-phase we mark this as such.
+ markUpdate(workInProgress);
+ }
+ if (enableDeprecatedFlareAPI) {
+ const listeners = newProps.DEPRECATED_flareListeners;
+ if (listeners != null) {
+ updateDeprecatedEventListeners(
+ listeners,
+ workInProgress,
+ rootContainerInstance,
+ );
+ }
+ }
+ } else {
+ const instance = createInstance(
+ type,
+ newProps,
+ rootContainerInstance,
+ currentHostContext,
+ workInProgress,
+ );
+
+ appendAllChildren(instance, workInProgress, false, false);
+
+ // This needs to be set before we mount Flare event listeners
+ workInProgress.stateNode = instance;
+
+ if (enableDeprecatedFlareAPI) {
+ const listeners = newProps.DEPRECATED_flareListeners;
+ if (listeners != null) {
+ updateDeprecatedEventListeners(
+ listeners,
+ workInProgress,
+ rootContainerInstance,
+ );
+ }
+ }
+
+ // 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,
+ rootContainerInstance,
+ currentHostContext,
+ )
+ ) {
+ markUpdate(workInProgress);
+ }
+ }
+
+ if (workInProgress.ref !== null) {
+ // If there is a ref on a host node we need to schedule a callback
+ markRef(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') {
+ invariant(
+ workInProgress.stateNode !== null,
+ '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,
+ );
+ }
+ }
+ return null;
+ }
+ case SuspenseComponent: {
+ popSuspenseContext(workInProgress);
+ const nextState: null | SuspenseState = workInProgress.memoizedState;
+
+ if (enableSuspenseServerRenderer) {
+ if (nextState !== null && nextState.dehydrated !== null) {
+ if (current === null) {
+ const wasHydrated = popHydrationState(workInProgress);
+ invariant(
+ wasHydrated,
+ 'A dehydrated suspense component was completed without a hydrated node. ' +
+ 'This is probably a bug in React.',
+ );
+ prepareToHydrateHostSuspenseInstance(workInProgress);
+ if (enableSchedulerTracing) {
+ markSpawnedWork(Never);
+ }
+ return null;
+ } else {
+ // We should never have been in a hydration state if we didn't have a current.
+ // However, in some of those paths, we might have reentered a hydration state
+ // and then we might be inside a hydration state. In that case, we'll need to exit out of it.
+ resetHydrationState();
+ if ((workInProgress.effectTag & DidCapture) === NoEffect) {
+ // 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.effectTag |= Update;
+ return null;
+ }
+ }
+ }
+
+ if ((workInProgress.effectTag & DidCapture) !== NoEffect) {
+ // Something suspended. Re-render with the fallback children.
+ workInProgress.expirationTime = renderExpirationTime;
+ // Do not reset the effect list.
+ return workInProgress;
+ }
+
+ const nextDidTimeout = nextState !== null;
+ let prevDidTimeout = false;
+ if (current === null) {
+ if (workInProgress.memoizedProps.fallback !== undefined) {
+ popHydrationState(workInProgress);
+ }
+ } else {
+ const prevState: null | SuspenseState = current.memoizedState;
+ prevDidTimeout = prevState !== null;
+ if (!nextDidTimeout && prevState !== null) {
+ // We just switched from the fallback to the normal children.
+ // Delete the fallback.
+ // TODO: Would it be better to store the fallback fragment on
+ // the stateNode during the begin phase?
+ const currentFallbackChild: Fiber | null = (current.child: any)
+ .sibling;
+ if (currentFallbackChild !== null) {
+ // Deletions go at the beginning of the return fiber's effect list
+ const first = workInProgress.firstEffect;
+ if (first !== null) {
+ workInProgress.firstEffect = currentFallbackChild;
+ currentFallbackChild.nextEffect = first;
+ } else {
+ workInProgress.firstEffect = workInProgress.lastEffect = currentFallbackChild;
+ currentFallbackChild.nextEffect = null;
+ }
+ currentFallbackChild.effectTag = Deletion;
+ }
+ }
+ }
+
+ if (nextDidTimeout && !prevDidTimeout) {
+ // If this subtreee is running in blocking mode we can suspend,
+ // otherwise we won't suspend.
+ // 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 & BlockingMode) !== 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.
+ const hasInvisibleChildContext =
+ current === null &&
+ workInProgress.memoizedProps.unstable_avoidThisFallback !== true;
+ if (
+ hasInvisibleChildContext ||
+ hasSuspenseContext(
+ suspenseStackCursor.current,
+ (InvisibleParentSuspenseContext: SuspenseContext),
+ )
+ ) {
+ // If this was in an invisible tree or a new render, then showing
+ // this boundary is ok.
+ renderDidSuspend();
+ } else {
+ // Otherwise, we're going to have to hide content so we should
+ // suspend for longer if possible.
+ renderDidSuspendDelayIfPossible();
+ }
+ }
+ }
+
+ if (supportsPersistence) {
+ // TODO: Only schedule updates if not prevDidTimeout.
+ if (nextDidTimeout) {
+ // If this boundary just timed out, schedule an effect to attach a
+ // retry listener to the promise. This flag is also used to hide the
+ // primary children.
+ workInProgress.effectTag |= Update;
+ }
+ }
+ if (supportsMutation) {
+ // TODO: Only schedule updates if these values are non equal, i.e. it changed.
+ if (nextDidTimeout || prevDidTimeout) {
+ // If this boundary just timed out, schedule an effect to attach a
+ // retry listener to the promise. This flag is also used to hide the
+ // primary children. In mutation mode, we also need the flag to
+ // *unhide* children that were previously hidden, so check if this
+ // is currently timed out, too.
+ workInProgress.effectTag |= Update;
+ }
+ }
+ if (
+ enableSuspenseCallback &&
+ workInProgress.updateQueue !== null &&
+ workInProgress.memoizedProps.suspenseCallback != null
+ ) {
+ // Always notify the callback
+ workInProgress.effectTag |= Update;
+ }
+ return null;
+ }
+ case HostPortal:
+ popHostContainer(workInProgress);
+ updateHostContainer(workInProgress);
+ return null;
+ case ContextProvider:
+ // Pop provider fiber
+ popProvider(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);
+ }
+ return null;
+ }
+ case SuspenseListComponent: {
+ popSuspenseContext(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.
+ return null;
+ }
+
+ let didSuspendAlready =
+ (workInProgress.effectTag & DidCapture) !== NoEffect;
+
+ 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.effectTag & DidCapture) === NoEffect);
+ if (!cannotBeSuspended) {
+ let row = workInProgress.child;
+ while (row !== null) {
+ const suspended = findFirstSuspended(row);
+ if (suspended !== null) {
+ didSuspendAlready = true;
+ workInProgress.effectTag |= 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 thennables. Instead, we'll transfer its thennables 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 newThennables = suspended.updateQueue;
+ if (newThennables !== null) {
+ workInProgress.updateQueue = newThennables;
+ workInProgress.effectTag |= Update;
+ }
+
+ // Rerender the whole list, but this time, we'll force fallbacks
+ // to stay in place.
+ // Reset the effect list before doing the second pass since that's now invalid.
+ if (renderState.lastEffect === null) {
+ workInProgress.firstEffect = null;
+ }
+ workInProgress.lastEffect = renderState.lastEffect;
+ // Reset the child fibers to their original state.
+ resetChildFibers(workInProgress, renderExpirationTime);
+
+ // Set up the Suspense Context to force suspense and immediately
+ // rerender the children.
+ pushSuspenseContext(
+ workInProgress,
+ setShallowSuspenseContext(
+ suspenseStackCursor.current,
+ ForceSuspenseFallback,
+ ),
+ );
+ return workInProgress.child;
+ }
+ row = row.sibling;
+ }
+ }
+ } 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.effectTag |= 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 newThennables = suspended.updateQueue;
+ if (newThennables !== null) {
+ workInProgress.updateQueue = newThennables;
+ workInProgress.effectTag |= Update;
+ }
+
+ cutOffTailIfNeeded(renderState, true);
+ // This might have been modified.
+ if (
+ renderState.tail === null &&
+ renderState.tailMode === 'hidden' &&
+ !renderedTail.alternate
+ ) {
+ // We need to delete the row we just rendered.
+ // Reset the effect list to what it was before we rendered this
+ // child. The nested children have already appended themselves.
+ const lastEffect = (workInProgress.lastEffect =
+ renderState.lastEffect);
+ // Remove any effects that were appended after this point.
+ if (lastEffect !== null) {
+ lastEffect.nextEffect = null;
+ }
+ // We're done.
+ return null;
+ }
+ } else if (
+ // The time it took to render last row is greater than time until
+ // the expiration.
+ now() * 2 - renderState.renderingStartTime >
+ renderState.tailExpiration &&
+ renderExpirationTime > Never
+ ) {
+ // 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.effectTag |= 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. If we can show
+ // them, then they really have the same priority as this render.
+ // So we'll pick it back up the very next render pass once we've had
+ // an opportunity to yield for paint.
+
+ const nextPriority = renderExpirationTime - 1;
+ workInProgress.expirationTime = workInProgress.childExpirationTime = nextPriority;
+ if (enableSchedulerTracing) {
+ markSpawnedWork(nextPriority);
+ }
+ }
+ }
+ 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.
+ if (renderState.tailExpiration === 0) {
+ // Heuristic for how long we're willing to spend rendering rows
+ // until we just give up and show what we have so far.
+ const TAIL_EXPIRATION_TIMEOUT_MS = 500;
+ renderState.tailExpiration = now() + TAIL_EXPIRATION_TIMEOUT_MS;
+ // TODO: This is meant to mimic the train model or JND but this
+ // is a per component value. It should really be since the start
+ // of the total render or last commit. Consider using something like
+ // globalMostRecentFallbackTime. That doesn't account for being
+ // suspended for part of the time or when it's a new render.
+ // It should probably use a global start time value instead.
+ }
+ // Pop a row.
+ const next = renderState.tail;
+ renderState.rendering = next;
+ renderState.tail = next.sibling;
+ renderState.lastEffect = workInProgress.lastEffect;
+ 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 = setShallowSuspenseContext(
+ suspenseContext,
+ ForceSuspenseFallback,
+ );
+ } else {
+ suspenseContext = setDefaultShallowSuspenseContext(suspenseContext);
+ }
+ pushSuspenseContext(workInProgress, suspenseContext);
+ // Do a pass over the next row.
+ return next;
+ }
+ return null;
+ }
+ case FundamentalComponent: {
+ if (enableFundamentalAPI) {
+ const fundamentalImpl = workInProgress.type.impl;
+ let fundamentalInstance: ReactFundamentalComponentInstance<
+ any,
+ any,
+ > | null = workInProgress.stateNode;
+
+ if (fundamentalInstance === null) {
+ const getInitialState = fundamentalImpl.getInitialState;
+ let fundamentalState;
+ if (getInitialState !== undefined) {
+ fundamentalState = getInitialState(newProps);
+ }
+ fundamentalInstance = workInProgress.stateNode = createFundamentalStateInstance(
+ workInProgress,
+ newProps,
+ fundamentalImpl,
+ fundamentalState || {},
+ );
+ const instance = ((getFundamentalComponentInstance(
+ fundamentalInstance,
+ ): any): Instance);
+ fundamentalInstance.instance = instance;
+ if (fundamentalImpl.reconcileChildren === false) {
+ return null;
+ }
+ appendAllChildren(instance, workInProgress, false, false);
+ mountFundamentalComponent(fundamentalInstance);
+ } else {
+ // We fire update in commit phase
+ const prevProps = fundamentalInstance.props;
+ fundamentalInstance.prevProps = prevProps;
+ fundamentalInstance.props = newProps;
+ fundamentalInstance.currentFiber = workInProgress;
+ if (supportsPersistence) {
+ const instance = cloneFundamentalInstance(fundamentalInstance);
+ fundamentalInstance.instance = instance;
+ appendAllChildren(instance, workInProgress, false, false);
+ }
+ const shouldUpdate = shouldUpdateFundamentalComponent(
+ fundamentalInstance,
+ );
+ if (shouldUpdate) {
+ markUpdate(workInProgress);
+ }
+ }
+ return null;
+ }
+ break;
+ }
+ case ScopeComponent: {
+ if (enableScopeAPI) {
+ if (current === null) {
+ const type = workInProgress.type;
+ const scopeInstance: ReactScopeInstance = {
+ fiber: workInProgress,
+ methods: null,
+ };
+ workInProgress.stateNode = scopeInstance;
+ scopeInstance.methods = createScopeMethods(type, scopeInstance);
+ if (enableDeprecatedFlareAPI) {
+ const listeners = newProps.DEPRECATED_flareListeners;
+ if (listeners != null) {
+ const rootContainerInstance = getRootHostContainer();
+ updateDeprecatedEventListeners(
+ listeners,
+ workInProgress,
+ rootContainerInstance,
+ );
+ }
+ }
+ if (workInProgress.ref !== null) {
+ markRef(workInProgress);
+ markUpdate(workInProgress);
+ }
+ } else {
+ if (enableDeprecatedFlareAPI) {
+ const prevListeners =
+ current.memoizedProps.DEPRECATED_flareListeners;
+ const nextListeners = newProps.DEPRECATED_flareListeners;
+ if (
+ prevListeners !== nextListeners ||
+ workInProgress.ref !== null
+ ) {
+ markUpdate(workInProgress);
+ }
+ } else {
+ if (workInProgress.ref !== null) {
+ markUpdate(workInProgress);
+ }
+ }
+ if (current.ref !== workInProgress.ref) {
+ markRef(workInProgress);
+ }
+ }
+ return null;
+ }
+ break;
+ }
+ case Block:
+ if (enableBlocksAPI) {
+ return null;
+ }
+ break;
+ }
+ invariant(
+ false,
+ 'Unknown unit of work tag (%s). This error is likely caused by a bug in ' +
+ 'React. Please file an issue.',
+ workInProgress.tag,
+ );
+}
+
+export {completeWork};
diff --git a/packages/react-reconciler/src/ReactFiberContext.new.js b/packages/react-reconciler/src/ReactFiberContext.new.js
new file mode 100644
index 0000000000000..e96295be857a8
--- /dev/null
+++ b/packages/react-reconciler/src/ReactFiberContext.new.js
@@ -0,0 +1,338 @@
+/**
+ * Copyright (c) Facebook, Inc. and its 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 getComponentName from 'shared/getComponentName';
+import invariant from 'shared/invariant';
+import checkPropTypes from 'shared/checkPropTypes';
+
+import {createCursor, push, pop} from './ReactFiberStack.new';
+
+let warnedAboutMissingGetChildContext;
+
+if (__DEV__) {
+ warnedAboutMissingGetChildContext = {};
+}
+
+export const emptyContextObject = {};
+if (__DEV__) {
+ Object.freeze(emptyContextObject);
+}
+
+// A cursor to the current merged context object on the stack.
+const contextStackCursor: StackCursor