text: /**
   * 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 {Fiber} from './ReactFiber';
  import type {ExpirationTime} from './ReactFiberExpirationTime';
  
  import getComponentName from 'shared/getComponentName';
  import {Placement, Deletion} from 'shared/ReactSideEffectTags';
  import {
    getIteratorFn,
    REACT_ELEMENT_TYPE,
    REACT_FRAGMENT_TYPE,
    REACT_PORTAL_TYPE,
    REACT_CHUNK_TYPE,
  } from 'shared/ReactSymbols';
  import {
    FunctionComponent,
    ClassComponent,
    HostText,
    HostPortal,
    Fragment,
    Chunk,
  } from 'shared/ReactWorkTags';
  import invariant from 'shared/invariant';
  import {warnAboutStringRefs, enableChunksAPI} from 'shared/ReactFeatureFlags';
  
  import {
    createWorkInProgress,
    resetWorkInProgress,
    createFiberFromElement,
    createFiberFromFragment,
    createFiberFromText,
    createFiberFromPortal,
  } from './ReactFiber';
  import {emptyRefsObject} from './ReactFiberClassComponent';
  import {
    getCurrentFiberStackInDev,
    getStackByFiberInDevAndProd,
  } from './ReactCurrentFiber';
  import {isCompatibleFamilyForHotReloading} from './ReactFiberHotReloading';
  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,
  ) {
    let 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 refs. ' +
              'Did you mean to use React.forwardRef()?',
          );
          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 <Component /> 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 <Component /> from render. ' +
          'Or maybe you meant to call this function rather than return it.',
      );
    }
  }
  
  // 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<string | number, Fiber> {
      // 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<string | number, Fiber> = 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,
      expirationTime: ExpirationTime,
    ): 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, expirationTime);
      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, expirationTime);
        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, expirationTime);
          existing.ref = coerceRef(returnFiber, current, element);
          existing.return = returnFiber;
          if (__DEV__) {
            existing._debugSource = element._source;
            existing._debugOwner = element._owner;
          }
          return existing;
        } else if (
          enableChunksAPI &&
          current.tag === Chunk &&
          element.type.$$typeof === REACT_CHUNK_TYPE &&
          element.type.render === current.type.render
        ) {
          // Same as above but also update the .type field.
          const existing = useFiber(current, element.props, expirationTime);
          existing.return = returnFiber;
          existing.type = element.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 || [], expirationTime);
        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, expirationTime);
        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<string | number, Fiber>,
      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<string> | null,
    ): Set<string> | 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 unsupported and will likely yield ' +
                'unexpected results. Convert it to a sequence/iterable 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, expirationTime);
        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,
                  expirationTime,
                );
                existing.return = returnFiber;
                if (__DEV__) {
                  existing._debugSource = element._source;
                  existing._debugOwner = element._owner;
                }
                return existing;
              }
              break;
            }
            case Chunk:
              if (enableChunksAPI) {
                if (
                  element.type.$$typeof === REACT_CHUNK_TYPE &&
                  element.type.render === child.type.render
                ) {
                  deleteRemainingChildren(returnFiber, child.sibling);
                  const existing = useFiber(child, element.props, expirationTime);
                  existing.type = element.type;
                  existing.return = returnFiber;
                  if (__DEV__) {
                    existing._debugSource = element._source;
                    existing._debugOwner = element._owner;
                  }
                  return existing;
                }
              }
            // We intentionally fallthrough here if enableChunksAPI 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, expirationTime);
                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 || [],
              expirationTime,
            );
            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,
      currentChild.expirationTime,
    );
    workInProgress.child = newChild;
  
    newChild.return = workInProgress;
    while (currentChild.sibling !== null) {
      currentChild = currentChild.sibling;
      newChild = newChild.sibling = createWorkInProgress(
        currentChild,
        currentChild.pendingProps,
        currentChild.expirationTime,
      );
      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;
    }
  }
  