Repository: facebook/react
Pull request: [Fiber] getNearestMountedFiber should consider fibers with alternates as mounted

This is a 10 year old bug which can cause events to get dropped and other things.

To know if an instance is mounted (or which nearest instance is mounted) we can normally back track and see if a `.return` pointer goes all the way to the root or if it's `null` before that (which indicates a disconnected tree).

One exception to that is if we're looking at a new tree that has not been mounted yet. Its return pointers will point to the parents that rendered it. To detect this we check new tree if they have a Placement flag. If they do then we haven't yet mounted that tree and so it's considered not mounted.

However, the Placement flag is also used for reorders. It's only an insertion if it's on a fiber with no alternates. This algorithm didn't consider that the target might be have no alternates but a parent has alternates. Those same parents may be reordered. So if you reordered a parent without rerendering the child. The child could stop receiving events. This mainly is an issue if the rerender didn't commit. E.g. was suspended (or in this case was a gesture) which is likely why it infrequently would be observable and very hard to repro.

This fixes that by bailing when we find an alternate and then proceeding to check if that tree is still connected.

Needs tests.

Changed files:
filename: packages/react-reconciler/src/ReactFiberTreeReflection.js
patch: @@ -33,25 +33,24 @@ import {NoFlags, Placement, Hydrating} from './ReactFiberFlags';
 export function getNearestMountedFiber(fiber: Fiber): null | Fiber {
   let node = fiber;
   let nearestMounted: null | Fiber = fiber;
-  if (!fiber.alternate) {
-    // If there is no alternate, this might be a new tree that isn't inserted
-    // yet. If it is, then it will have a pending insertion effect on it.
-    let nextNode: Fiber = node;
-    do {
-      node = nextNode;
-      if ((node.flags & (Placement | Hydrating)) !== NoFlags) {
-        // This is an insertion or in-progress hydration. The nearest possible
-        // mounted fiber is the parent but we need to continue to figure out
-        // if that one is still mounted.
-        nearestMounted = node.return;
-      }
-      // $FlowFixMe[incompatible-type] we bail out when we get a null
-      nextNode = node.return;
-    } while (nextNode);
-  } else {
-    while (node.return) {
-      node = node.return;
+  // If there is no alternate, this might be a new tree that isn't inserted
+  // yet. If it is, then it will have a pending insertion effect on it.
+  let nextNode: Fiber = node;
+  while (nextNode && !nextNode.alternate) {
+    node = nextNode;
+    if ((node.flags & (Placement | Hydrating)) !== NoFlags) {
+      // This is an insertion or in-progress hydration. The nearest possible
+      // mounted fiber is the parent but we need to continue to figure out
+      // if that one is still mounted.
+      nearestMounted = node.return;
     }
+    // $FlowFixMe[incompatible-type] we bail out when we get a null
+    nextNode = node.return;
+  }
+  // After we've reached an alternate, go the rest of the way to see if the
+  // tree is still mounted. If it's not, its return pointer will be disconnected.
+  while (node.return) {
+    node = node.return;
   }
   if (node.tag === HostRoot) {
     // TODO: Check if this was a nested HostRoot when used with