packages/react-reconciler/src/ReactFiberApplyGesture.js JAVASCRIPT 1,350 lines View on github.com → Search inside
1/**2 * Copyright (c) Meta Platforms, Inc. and affiliates.3 *4 * This source code is licensed under the MIT license found in the5 * LICENSE file in the root directory of this source tree.6 *7 * @flow8 */910import type {ViewTransitionProps} from 'shared/ReactTypes';1112import type {Fiber, FiberRoot} from './ReactInternalTypes';1314import type {Instance, TextInstance, Props} from './ReactFiberConfig';1516import type {OffscreenState} from './ReactFiberOffscreenComponent';1718import type {ViewTransitionState} from './ReactFiberViewTransitionComponent';1920import {21  cloneMutableInstance,22  cloneMutableTextInstance,23  cloneRootViewTransitionContainer,24  removeRootViewTransitionClone,25  cancelRootViewTransitionName,26  restoreRootViewTransitionName,27  cancelViewTransitionName,28  applyViewTransitionName,29  appendChild,30  commitUpdate,31  commitTextUpdate,32  resetTextContent,33  supportsResources,34  supportsSingletons,35  unhideInstance,36  unhideTextInstance,37} from './ReactFiberConfig';38import {39  popMutationContext,40  pushMutationContext,41  viewTransitionMutationContext,42  trackHostMutation,43} from './ReactFiberMutationTracking';44import {45  MutationMask,46  Placement,47  Update,48  ContentReset,49  NoFlags,50  Visibility,51  ViewTransitionNamedStatic,52  ViewTransitionStatic,53  AffectedParentLayout,54} from './ReactFiberFlags';55import {56  HasEffect as HookHasEffect,57  Insertion as HookInsertion,58} from './ReactHookEffectTags';59import {60  FunctionComponent,61  ForwardRef,62  MemoComponent,63  SimpleMemoComponent,64  HostComponent,65  HostHoistable,66  HostSingleton,67  HostText,68  HostPortal,69  OffscreenComponent,70  ViewTransitionComponent,71} from './ReactWorkTags';72import {73  restoreEnterOrExitViewTransitions,74  restoreNestedViewTransitions,75  restoreUpdateViewTransitionForGesture,76  appearingViewTransitions,77  commitEnterViewTransitions,78  commitParentExitViewTransitions,79  measureNestedViewTransitions,80  measureUpdateViewTransition,81  viewTransitionCancelableChildren,82  pushViewTransitionCancelableScope,83  popViewTransitionCancelableScope,84} from './ReactFiberCommitViewTransitions';85import {86  commitHookEffectListMount,87  commitHookEffectListUnmount,88} from './ReactFiberCommitEffects';89import {90  getViewTransitionName,91  getViewTransitionClassName,92} from './ReactFiberViewTransitionComponent';9394import {95  enableProfilerTimer,96  enableComponentPerformanceTrack,97  enableViewTransitionParentEnterExit,98} from 'shared/ReactFeatureFlags';99import {trackAnimatingTask} from './ReactProfilerTimer';100import {scheduleGestureTransitionEvent} from './ReactFiberWorkLoop';101102// Used during the apply phase to track whether a parent ViewTransition component103// might have been affected by any mutations / relayouts below.104let viewTransitionContextChanged: boolean = false;105106function detectMutationOrInsertClones(finishedWork: Fiber): boolean {107  return true;108}109110const CLONE_UPDATE = 0; // Mutations in this subtree or potentially affected by layout.111const CLONE_EXIT = 1; // Inside a reappearing offscreen before the next ViewTransition or HostComponent.112const CLONE_UNHIDE = 2; // Inside a reappearing offscreen before the next HostComponent.113const CLONE_APPEARING_PAIR = 3; // Like UNHIDE but we're already inside the first Host Component only finding pairs.114const CLONE_UNCHANGED = 4; // Nothing in this tree was changed but we're still walking to clone it.115const INSERT_EXIT = 5; // Inside a newly mounted tree before the next ViewTransition or HostComponent.116const INSERT_APPEND = 6; // Inside a newly mounted tree before the next HostComponent.117const INSERT_APPEARING_PAIR = 7; // Inside a newly mounted tree only finding pairs.118type VisitPhase = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7;119120function applyViewTransitionToClones(121  name: string,122  className: ?string,123  clones: Array<Instance>,124  fiber: Fiber,125): void {126  // This gets called when we have found a pair, but after the clone in created. The clone is127  // created by the insertion side. If the insertion side if found before the deletion side128  // then this is called by the deletion. If the deletion is visited first then this is called129  // later by the insertion when the clone has been created.130  for (let i = 0; i < clones.length; i++) {131    applyViewTransitionName(132      clones[i],133      i === 0134        ? name135        : // If we have multiple Host Instances below, we add a suffix to the name to give136          // each one a unique name.137          name + '_' + i,138      className,139    );140  }141  if (enableProfilerTimer && enableComponentPerformanceTrack) {142    if (fiber._debugTask != null) {143      trackAnimatingTask(fiber._debugTask);144    }145  }146}147148function trackDeletedPairViewTransitions(deletion: Fiber): void {149  if (150    appearingViewTransitions === null ||151    appearingViewTransitions.size === 0152  ) {153    // We've found all.154    return;155  }156  const pairs = appearingViewTransitions;157  if ((deletion.subtreeFlags & ViewTransitionNamedStatic) === NoFlags) {158    // This has no named view transitions in its subtree.159    return;160  }161  let child = deletion.child;162  while (child !== null) {163    if (child.tag === OffscreenComponent && child.memoizedState !== null) {164      // This tree was already hidden so we skip it.165    } else {166      if (167        child.tag === ViewTransitionComponent &&168        (child.flags & ViewTransitionNamedStatic) !== NoFlags169      ) {170        const props: ViewTransitionProps = child.memoizedProps;171        const name = props.name;172        if (name != null && name !== 'auto') {173          const pair = pairs.get(name);174          if (pair !== undefined) {175            // Delete the entry so that we know when we've found all of them176            // and can stop searching (size reaches zero).177            pairs.delete(name);178            const className: ?string = getViewTransitionClassName(179              props.default,180              props.share,181            );182            if (className !== 'none') {183              // TODO: Since the deleted instance already has layout we could184              // check if it's in the viewport and if not skip the pairing.185              // It would currently cause layout thrash though so if we did that186              // we need to avoid inserting the root of the cloned trees until187              // the end.188189              // The "old" instance is actually the one we're inserting.190              const oldInstance: ViewTransitionState = pair;191              // The "new" instance is the already mounted one we're deleting.192              const newInstance: ViewTransitionState = child.stateNode;193              oldInstance.paired = newInstance;194              newInstance.paired = oldInstance;195              const clones = oldInstance.clones;196              if (clones !== null) {197                // If we have clones that means that we've already visited this198                // ViewTransition boundary before and we can now apply the name199                // to those clones. Otherwise, we have to wait until we clone it.200                applyViewTransitionToClones(name, className, clones, child);201              }202            }203            if (pairs.size === 0) {204              break;205            }206          }207        }208      }209      trackDeletedPairViewTransitions(child);210    }211    child = child.sibling;212  }213}214215function trackEnterViewTransitions(deletion: Fiber): void {216  if (deletion.tag === ViewTransitionComponent) {217    const props: ViewTransitionProps = deletion.memoizedProps;218    const name = getViewTransitionName(props, deletion.stateNode);219    const pair =220      appearingViewTransitions !== null221        ? appearingViewTransitions.get(name)222        : undefined;223    const className: ?string = getViewTransitionClassName(224      props.default,225      pair !== undefined ? props.share : props.enter,226    );227    if (className !== 'none') {228      if (pair !== undefined) {229        // TODO: Since the deleted instance already has layout we could230        // check if it's in the viewport and if not skip the pairing.231        // It would currently cause layout thrash though so if we did that232        // we need to avoid inserting the root of the cloned trees until233        // the end.234235        // Delete the entry so that we know when we've found all of them236        // and can stop searching (size reaches zero).237        // $FlowFixMe[incompatible-use]: Refined by the pair.238        appearingViewTransitions.delete(name);239        // The "old" instance is actually the one we're inserting.240        const oldInstance: ViewTransitionState = pair;241        // The "new" instance is the already mounted one we're deleting.242        const newInstance: ViewTransitionState = deletion.stateNode;243        oldInstance.paired = newInstance;244        newInstance.paired = oldInstance;245        const clones = oldInstance.clones;246        if (clones !== null) {247          // If we have clones that means that we've already visited this248          // ViewTransition boundary before and we can now apply the name249          // to those clones. Otherwise, we have to wait until we clone it.250          applyViewTransitionToClones(name, className, clones, deletion);251        }252      }253    }254    // Look for more pairs deeper in the tree.255    trackDeletedPairViewTransitions(deletion);256  } else if ((deletion.subtreeFlags & ViewTransitionStatic) !== NoFlags) {257    let child = deletion.child;258    while (child !== null) {259      trackEnterViewTransitions(child);260      child = child.sibling;261    }262  } else {263    trackDeletedPairViewTransitions(deletion);264  }265}266267function applyAppearingPairViewTransition(child: Fiber): void {268  // Normally these helpers do recursive calls but since insertion/offscreen is forked269  // we call this helper from those loops instead. This must be called only on270  // ViewTransitionComponent that has already had their clones filled.271  if ((child.flags & ViewTransitionNamedStatic) !== NoFlags) {272    const state: ViewTransitionState = child.stateNode;273    // If this is not yet paired, it doesn't mean that we won't pair it later when274    // we find the deletion side. If that's the case then we'll add the names to275    // the clones then.276    if (state.paired) {277      const props: ViewTransitionProps = child.memoizedProps;278      if (props.name == null || props.name === 'auto') {279        throw new Error(280          'Found a pair with an auto name. This is a bug in React.',281        );282      }283      const name = props.name;284      // Note that this class name that doesn't actually really matter because the285      // "new" side will be the one that wins in practice.286      const className: ?string = getViewTransitionClassName(287        props.default,288        props.share,289      );290      if (className !== 'none') {291        const clones = state.clones;292        // If there are no clones at this point, that should mean that there are no293        // HostComponent children in this ViewTransition.294        if (clones !== null) {295          applyViewTransitionToClones(name, className, clones, child);296        }297        scheduleGestureTransitionEvent(child, props.onGestureShare);298      }299    }300  }301}302303function applyExitViewTransition(placement: Fiber): void {304  // Normally these helpers do recursive calls but since insertion/offscreen is forked305  // we call this helper from those loops instead. This must be called only on306  // ViewTransitionComponent that has already had their clones filled.307  const state: ViewTransitionState = placement.stateNode;308  const props: ViewTransitionProps = placement.memoizedProps;309  const name = getViewTransitionName(props, state);310  const className: ?string = getViewTransitionClassName(311    props.default,312    // Note that just because we don't have a pair yet doesn't mean we won't find one313    // later. However, that doesn't matter because if we do the class name that wins314    // is the one applied by the "new" side anyway.315    state.paired ? props.share : props.exit,316  );317  if (className !== 'none') {318    // TODO: Ideally we could determine if this exit is in the viewport and319    // exclude it otherwise but that would require waiting until we insert320    // and layout the clones first. Currently wait until the view transition321    // starts before reading the layout.322    const clones = state.clones;323    // If there are no clones at this point, that should mean that there are no324    // HostComponent children in this ViewTransition.325    if (clones !== null) {326      applyViewTransitionToClones(name, className, clones, placement);327    }328    if (state.paired) {329      scheduleGestureTransitionEvent(placement, props.onGestureShare);330    } else {331      scheduleGestureTransitionEvent(placement, props.onGestureExit);332      if (enableViewTransitionParentEnterExit) {333        commitParentExitViewTransitions(placement, true);334      }335    }336  }337}338339function applyNestedViewTransition(child: Fiber): void {340  const state: ViewTransitionState = child.stateNode;341  const props: ViewTransitionProps = child.memoizedProps;342  const name = getViewTransitionName(props, state);343  const className: ?string = getViewTransitionClassName(344    props.default,345    props.update,346  );347  if (className !== 'none') {348    const clones = state.clones;349    // If there are no clones at this point, that should mean that there are no350    // HostComponent children in this ViewTransition.351    if (clones !== null) {352      applyViewTransitionToClones(name, className, clones, child);353    }354  }355}356357function applyUpdateViewTransition(current: Fiber, finishedWork: Fiber): void {358  const state: ViewTransitionState = finishedWork.stateNode;359  // Updates can have conflicting names and classNames.360  // Since we're doing a reverse animation the "new" state is actually the current361  // and the "old" state is the finishedWork.362  const newProps: ViewTransitionProps = current.memoizedProps;363  const oldProps: ViewTransitionProps = finishedWork.memoizedProps;364  const oldName = getViewTransitionName(oldProps, state);365  // This className applies only if there are fewer child DOM nodes than366  // before or if this update should've been cancelled but we ended up with367  // a parent animating so we need to animate the child too. Otherwise368  // the "new" state wins. Since "new" normally wins, that's usually what369  // we would use. However, since this animation is going in reverse we actually370  // want the props from "current" since that's the class that would've won if371  // it was the normal direction. To preserve the same effect in either direction.372  const className: ?string = getViewTransitionClassName(373    newProps.default,374    newProps.update,375  );376  if (className === 'none') {377    // If update is "none" then we don't have to apply a name. Since we won't animate this boundary.378    return;379  }380  const clones = state.clones;381  // If there are no clones at this point, that should mean that there are no382  // HostComponent children in this ViewTransition.383  if (clones !== null) {384    applyViewTransitionToClones(oldName, className, clones, finishedWork);385  }386}387388function recursivelyInsertNew(389  parentFiber: Fiber,390  hostParentClone: Instance,391  parentViewTransition: null | ViewTransitionState,392  visitPhase: VisitPhase,393): void {394  if (395    visitPhase === INSERT_APPEARING_PAIR &&396    parentViewTransition === null &&397    (parentFiber.subtreeFlags & (ViewTransitionNamedStatic | Placement)) ===398      NoFlags399  ) {400    // We're just searching for pairs or insertion effects but we have reached the end.401    return;402  }403  let child = parentFiber.child;404  while (child !== null) {405    recursivelyInsertNewFiber(406      child,407      hostParentClone,408      parentViewTransition,409      visitPhase,410    );411    child = child.sibling;412  }413}414415function recursivelyInsertNewFiber(416  finishedWork: Fiber,417  hostParentClone: Instance,418  parentViewTransition: null | ViewTransitionState,419  visitPhase: VisitPhase,420): void {421  switch (finishedWork.tag) {422    case FunctionComponent:423    case ForwardRef:424    case MemoComponent:425    case SimpleMemoComponent: {426      recursivelyInsertNew(427        finishedWork,428        hostParentClone,429        parentViewTransition,430        visitPhase,431      );432      if (finishedWork.flags & Update) {433        // Insertion Effects are mounted temporarily during the rendering of the snapshot.434        // This does not affect cloned Offscreen content since those would've been mounted435        // while inside the offscreen tree already.436        // Note that because we are mounting a clone of the DOM tree and the previous DOM437        // tree remains mounted during the snapshot, we can't unmount any previous insertion438        // effects. This can lead to conflicts but that is similar to what can happen with439        // conflicts for two mounted Activity boundaries.440        commitHookEffectListMount(HookInsertion | HookHasEffect, finishedWork);441      }442      break;443    }444    case HostHoistable: {445      // $FlowFixMe[constant-condition]446      if (supportsResources) {447        // TODO: Hoistables should get optimistically inserted and then removed.448        recursivelyInsertNew(449          finishedWork,450          hostParentClone,451          parentViewTransition,452          visitPhase,453        );454        break;455      }456      // Fall through457    }458    case HostSingleton: {459      // $FlowFixMe[constant-condition]460      if (supportsSingletons) {461        recursivelyInsertNew(462          finishedWork,463          hostParentClone,464          parentViewTransition,465          visitPhase,466        );467468        if (__DEV__) {469          // We cannot apply mutations to Host Singletons since by definition470          // they cannot be cloned. Therefore we warn in DEV if this commit471          // had any effect.472          if (finishedWork.flags & Update) {473            console.error(474              'startGestureTransition() caused something to render a new <%s>. ' +475                'This is not possible in the current implementation. ' +476                "Make sure that the swipe doesn't mount any new <%s> elements.",477              finishedWork.type,478              finishedWork.type,479            );480          }481        }482        break;483      }484      // Fall through485    }486    case HostComponent: {487      const instance: Instance = finishedWork.stateNode;488      // For insertions we don't need to clone. It's already new state node.489      if (visitPhase !== INSERT_APPEARING_PAIR) {490        appendChild(hostParentClone, instance);491        trackHostMutation();492        recursivelyInsertNew(493          finishedWork,494          instance,495          null,496          INSERT_APPEARING_PAIR,497        );498      } else {499        recursivelyInsertNew(finishedWork, instance, null, visitPhase);500      }501      if (parentViewTransition !== null) {502        if (parentViewTransition.clones === null) {503          parentViewTransition.clones = [instance];504        } else {505          parentViewTransition.clones.push(instance);506        }507      }508      break;509    }510    case HostText: {511      const textInstance: TextInstance = finishedWork.stateNode;512      // $FlowFixMe[invalid-compare]513      if (textInstance === null) {514        throw new Error(515          'This should have a text node initialized. This error is likely ' +516            'caused by a bug in React. Please file an issue.',517        );518      }519      // For insertions we don't need to clone. It's already new state node.520      if (visitPhase !== INSERT_APPEARING_PAIR) {521        appendChild(hostParentClone, textInstance);522        trackHostMutation();523      }524      break;525    }526    case HostPortal: {527      // TODO: Consider what should happen to Portals. For now we exclude them.528      break;529    }530    case OffscreenComponent: {531      const newState: OffscreenState | null = finishedWork.memoizedState;532      const isHidden = newState !== null;533      if (!isHidden) {534        // Only insert nodes if this tree is going to be visible. No need to535        // insert invisible content.536        // Since there was no mutation to this node, it couldn't have changed537        // visibility so we don't need to update visitPhase here.538        recursivelyInsertNew(539          finishedWork,540          hostParentClone,541          parentViewTransition,542          visitPhase,543        );544      }545      break;546    }547    case ViewTransitionComponent:548      const prevMutationContext = pushMutationContext();549      const viewTransitionState: ViewTransitionState = finishedWork.stateNode;550      // TODO: If this was already cloned by a previous pass we can reuse those clones.551      viewTransitionState.clones = null;552      let nextPhase: VisitPhase;553      if (visitPhase === INSERT_EXIT) {554        // This was an Enter of a ViewTransition. We now move onto inserting the inner555        // HostComponents and finding inner pairs.556        nextPhase = INSERT_APPEND;557      } else {558        nextPhase = visitPhase;559      }560      recursivelyInsertNew(561        finishedWork,562        hostParentClone,563        viewTransitionState,564        nextPhase,565      );566      // After we've inserted the new nodes into the "clones" set we can apply share567      // or exit transitions to them.568      if (visitPhase === INSERT_EXIT) {569        applyExitViewTransition(finishedWork);570      } else if (571        visitPhase === INSERT_APPEARING_PAIR ||572        visitPhase === INSERT_APPEND573      ) {574        applyAppearingPairViewTransition(finishedWork);575      }576      popMutationContext(prevMutationContext);577      break;578    default: {579      recursivelyInsertNew(580        finishedWork,581        hostParentClone,582        parentViewTransition,583        visitPhase,584      );585      break;586    }587  }588}589590function recursivelyInsertClonesFromExistingTree(591  parentFiber: Fiber,592  hostParentClone: Instance,593  parentViewTransition: null | ViewTransitionState,594  visitPhase: VisitPhase,595): void {596  let child = parentFiber.child;597  while (child !== null) {598    switch (child.tag) {599      case HostComponent: {600        const instance: Instance = child.stateNode;601        let nextPhase: VisitPhase;602        switch (visitPhase) {603          case CLONE_EXIT:604          case CLONE_UNHIDE:605          case CLONE_APPEARING_PAIR:606            // If this was an unhide, we need to keep going if there are any named607            // pairs in this subtree, since they might need to be marked.608            nextPhase =609              (child.subtreeFlags & ViewTransitionNamedStatic) !== NoFlags610                ? CLONE_APPEARING_PAIR611                : CLONE_UNCHANGED;612            break;613          default:614            // We've found any "layout" View Transitions at this point so we can bail.615            nextPhase = CLONE_UNCHANGED;616        }617        let clone: Instance;618        if (nextPhase !== CLONE_UNCHANGED) {619          // We might need a handle on these clones, so we need to do a shallow clone620          // and keep going.621          clone = cloneMutableInstance(instance, false);622          recursivelyInsertClonesFromExistingTree(623            child,624            clone,625            null,626            nextPhase,627          );628        } else {629          // If we have no mutations in this subtree, and we don't need a handle on the630          // clones, then we can do a deep clone instead and bailout.631          clone = cloneMutableInstance(instance, true);632          // TODO: We may need to transfer some DOM state such as scroll position633          // for the deep clones.634          // TODO: If there's a manual view-transition-name inside the clone we635          // should ideally remove it from the original and then restore it in mutation636          // phase. Otherwise it leads to duplicate names.637        }638        appendChild(hostParentClone, clone);639        if (parentViewTransition !== null) {640          if (parentViewTransition.clones === null) {641            parentViewTransition.clones = [clone];642          } else {643            parentViewTransition.clones.push(clone);644          }645        }646        if (visitPhase === CLONE_EXIT || visitPhase === CLONE_UNHIDE) {647          unhideInstance(clone, child.memoizedProps);648          trackHostMutation();649        }650        break;651      }652      case HostText: {653        const textInstance: TextInstance = child.stateNode;654        // $FlowFixMe[invalid-compare]655        if (textInstance === null) {656          throw new Error(657            'This should have a text node initialized. This error is likely ' +658              'caused by a bug in React. Please file an issue.',659          );660        }661        const clone = cloneMutableTextInstance(textInstance);662        appendChild(hostParentClone, clone);663        if (visitPhase === CLONE_EXIT || visitPhase === CLONE_UNHIDE) {664          unhideTextInstance(clone, child.memoizedProps);665          trackHostMutation();666        }667        break;668      }669      case HostPortal: {670        // TODO: Consider what should happen to Portals. For now we exclude them.671        break;672      }673      case OffscreenComponent: {674        const newState: OffscreenState | null = child.memoizedState;675        const isHidden = newState !== null;676        if (!isHidden) {677          // Only insert clones if this tree is going to be visible. No need to678          // clone invisible content.679          // TODO: If this is visible but detached it should still be cloned.680          // Since there was no mutation to this node, it couldn't have changed681          // visibility so we don't need to update visitPhase here.682          recursivelyInsertClonesFromExistingTree(683            child,684            hostParentClone,685            parentViewTransition,686            visitPhase,687          );688        }689        break;690      }691      case ViewTransitionComponent:692        const prevMutationContext = pushMutationContext();693        const viewTransitionState: ViewTransitionState = child.stateNode;694        // TODO: If this was already cloned by a previous pass we can reuse those clones.695        viewTransitionState.clones = null;696        // "Existing" view transitions are in subtrees that didn't update so697        // this is a "current". We normally clear this upon rerendering698        // but we use this flag to track changes from layout in the commit.699        // So we need it to be cleared before we do that.700        // TODO: Use some other temporary state to track this.701        child.flags &= ~Update;702        let nextPhase: VisitPhase;703        if (visitPhase === CLONE_EXIT) {704          // This was an Enter of a ViewTransition. We now move onto unhiding the inner705          // HostComponents and finding inner pairs.706          nextPhase = CLONE_UNHIDE;707          // TODO: Mark the name and find a pair.708        } else if (visitPhase === CLONE_UPDATE) {709          // If the tree had no mutations and we've found the top most ViewTransition710          // then this is the one we might apply the "layout" state too if it has changed711          // position. After we've found its HostComponents we can bail out.712          nextPhase = CLONE_UNCHANGED;713        } else {714          nextPhase = visitPhase;715        }716        recursivelyInsertClonesFromExistingTree(717          child,718          hostParentClone,719          viewTransitionState,720          nextPhase,721        );722        // After we've collected the cloned instances, we can apply exit or share transitions723        // to them.724        if (visitPhase === CLONE_EXIT) {725          applyExitViewTransition(child);726        } else if (727          visitPhase === CLONE_APPEARING_PAIR ||728          visitPhase === CLONE_UNHIDE729        ) {730          applyAppearingPairViewTransition(child);731        } else if (visitPhase === CLONE_UPDATE) {732          applyNestedViewTransition(child);733        }734        popMutationContext(prevMutationContext);735        break;736      default: {737        recursivelyInsertClonesFromExistingTree(738          child,739          hostParentClone,740          parentViewTransition,741          visitPhase,742        );743        break;744      }745    }746    child = child.sibling;747  }748}749750function recursivelyInsertClones(751  parentFiber: Fiber,752  hostParentClone: Instance,753  parentViewTransition: null | ViewTransitionState,754  visitPhase: VisitPhase,755) {756  const deletions = parentFiber.deletions;757  if (deletions !== null) {758    for (let i = 0; i < deletions.length; i++) {759      const childToDelete = deletions[i];760      trackEnterViewTransitions(childToDelete);761      // Normally we would only mark something as triggering a mutation if there was762      // actually a HostInstance below here. If this tree didn't contain a HostInstances763      // we shouldn't trigger a mutation even though a virtual component was deleted.764      trackHostMutation();765    }766  }767768  if (769    parentFiber.alternate === null ||770    (parentFiber.subtreeFlags & MutationMask) !== NoFlags771  ) {772    // If we have mutations or if this is a newly inserted tree, clone as we go.773    let child = parentFiber.child;774    while (child !== null) {775      insertDestinationClonesOfFiber(776        child,777        hostParentClone,778        parentViewTransition,779        visitPhase,780      );781      child = child.sibling;782    }783  } else {784    // Once we reach a subtree with no more mutations we can bail out.785    // However, we must still insert deep clones of the HostComponents.786    recursivelyInsertClonesFromExistingTree(787      parentFiber,788      hostParentClone,789      parentViewTransition,790      visitPhase,791    );792  }793}794795function insertDestinationClonesOfFiber(796  finishedWork: Fiber,797  hostParentClone: Instance,798  parentViewTransition: null | ViewTransitionState,799  visitPhase: VisitPhase,800) {801  const current = finishedWork.alternate;802  if (current === null) {803    // This is a newly mounted subtree. Insert any HostComponents and trigger804    // Enter transitions.805    recursivelyInsertNewFiber(806      finishedWork,807      hostParentClone,808      parentViewTransition,809      INSERT_EXIT,810    );811    return;812  }813814  const flags = finishedWork.flags;815  // The effect flag should be checked *after* we refine the type of fiber,816  // because the fiber tag is more specific. An exception is any flag related817  // to reconciliation, because those can be set on all fiber types.818  switch (finishedWork.tag) {819    case HostHoistable: {820      // $FlowFixMe[constant-condition]821      if (supportsResources) {822        // TODO: Hoistables should get optimistically inserted and then removed.823        recursivelyInsertClones(824          finishedWork,825          hostParentClone,826          parentViewTransition,827          visitPhase,828        );829        break;830      }831      // Fall through832    }833    case HostSingleton: {834      // $FlowFixMe[constant-condition]835      if (supportsSingletons) {836        recursivelyInsertClones(837          finishedWork,838          hostParentClone,839          parentViewTransition,840          visitPhase,841        );842        if (__DEV__) {843          // We cannot apply mutations to Host Singletons since by definition844          // they cannot be cloned. Therefore we warn in DEV if this commit845          // had any effect.846          if (flags & Update) {847            const newProps = finishedWork.memoizedProps;848            const oldProps = current.memoizedProps;849            const instance = finishedWork.stateNode;850            const type = finishedWork.type;851            const prev = pushMutationContext();852853            try {854              // Since we currently don't have a separate diffing algorithm for855              // individual properties, the Update flag can be a false positive.856              // We have to apply the new props first o detect any mutations and857              // then revert them.858              commitUpdate(instance, type, oldProps, newProps, finishedWork);859              if (viewTransitionMutationContext) {860                console.error(861                  'startGestureTransition() caused something to mutate <%s>. ' +862                    'This is not possible in the current implementation. ' +863                    "Make sure that the swipe doesn't update any state which " +864                    'causes <%s> to change.',865                  finishedWork.type,866                  finishedWork.type,867                );868              }869              // Revert870              commitUpdate(instance, type, newProps, oldProps, finishedWork);871            } finally {872              popMutationContext(prev);873            }874          }875        }876        break;877      }878      // Fall through879    }880    case HostComponent: {881      const instance: Instance = finishedWork.stateNode;882      let clone: Instance;883      if (finishedWork.child === null) {884        // This node is terminal. We still do a deep clone in case this has user885        // inserted content, text content or dangerouslySetInnerHTML.886        clone = cloneMutableInstance(instance, true);887        if (finishedWork.flags & ContentReset) {888          resetTextContent(clone);889          trackHostMutation();890        }891      } else {892        // If we have children we'll clone them as we walk the tree so we just893        // do a shallow clone here.894        clone = cloneMutableInstance(instance, false);895      }896897      if (flags & Update) {898        const newProps = finishedWork.memoizedProps;899        const oldProps = current.memoizedProps;900        const type = finishedWork.type;901        // Apply the delta to the clone.902        commitUpdate(clone, type, oldProps, newProps, finishedWork);903      }904905      if (visitPhase === CLONE_EXIT || visitPhase === CLONE_UNHIDE) {906        appendChild(hostParentClone, clone);907        unhideInstance(clone, finishedWork.memoizedProps);908        recursivelyInsertClones(909          finishedWork,910          clone,911          null,912          CLONE_APPEARING_PAIR,913        );914        trackHostMutation();915      } else {916        appendChild(hostParentClone, clone);917        recursivelyInsertClones(finishedWork, clone, null, visitPhase);918      }919      if (parentViewTransition !== null) {920        if (parentViewTransition.clones === null) {921          parentViewTransition.clones = [clone];922        } else {923          parentViewTransition.clones.push(clone);924        }925      }926      break;927    }928    case HostText: {929      const textInstance: TextInstance = finishedWork.stateNode;930      // $FlowFixMe[invalid-compare]931      if (textInstance === null) {932        throw new Error(933          'This should have a text node initialized. This error is likely ' +934            'caused by a bug in React. Please file an issue.',935        );936      }937      const clone = cloneMutableTextInstance(textInstance);938      if (flags & Update) {939        const newText: string = finishedWork.memoizedProps;940        const oldText: string = current.memoizedProps;941        commitTextUpdate(clone, newText, oldText);942        trackHostMutation();943      }944      appendChild(hostParentClone, clone);945      if (visitPhase === CLONE_EXIT || visitPhase === CLONE_UNHIDE) {946        unhideTextInstance(clone, finishedWork.memoizedProps);947        trackHostMutation();948      }949      break;950    }951    case HostPortal: {952      // TODO: Consider what should happen to Portals. For now we exclude them.953      break;954    }955    case OffscreenComponent: {956      const newState: OffscreenState | null = finishedWork.memoizedState;957      const isHidden = newState !== null;958      if (!isHidden) {959        // Only insert clones if this tree is going to be visible. No need to960        // clone invisible content.961        // TODO: If this is visible but detached it should still be cloned.962        let nextPhase: VisitPhase;963        if (visitPhase === CLONE_UPDATE && (flags & Visibility) !== NoFlags) {964          // This is the root of an appear. We need to trigger Enter transitions.965          nextPhase = CLONE_EXIT;966        } else {967          nextPhase = visitPhase;968        }969        recursivelyInsertClones(970          finishedWork,971          hostParentClone,972          parentViewTransition,973          nextPhase,974        );975        // $FlowFixMe[invalid-compare]976      } else if (current !== null && current.memoizedState === null) {977        // Was previously mounted as visible but is now hidden.978        trackEnterViewTransitions(current);979        // Normally we would only mark something as triggering a mutation if there was980        // actually a HostInstance below here. If this tree didn't contain a HostInstances981        // we shouldn't trigger a mutation even though a virtual component was hidden.982        trackHostMutation();983      }984      break;985    }986    case ViewTransitionComponent:987      const prevMutationContext = pushMutationContext();988      const viewTransitionState: ViewTransitionState = finishedWork.stateNode;989      // TODO: If this was already cloned by a previous pass we can reuse those clones.990      viewTransitionState.clones = null;991      let nextPhase: VisitPhase;992      if (visitPhase === CLONE_EXIT) {993        // This was an Enter of a ViewTransition. We now move onto unhiding the inner994        // HostComponents and finding inner pairs.995        nextPhase = CLONE_UNHIDE;996        // TODO: Mark the name and find a pair.997      } else {998        nextPhase = visitPhase;999      }1000      recursivelyInsertClones(1001        finishedWork,1002        hostParentClone,1003        viewTransitionState,1004        nextPhase,1005      );1006      if (viewTransitionMutationContext) {1007        // Track that this boundary had a mutation and therefore needs to animate1008        // whether it resized or not.1009        finishedWork.flags |= Update;1010      }1011      // After we've collected the cloned instances, we can apply exit or share transitions1012      // to them.1013      if (visitPhase === CLONE_EXIT) {1014        applyExitViewTransition(finishedWork);1015      } else if (1016        visitPhase === CLONE_APPEARING_PAIR ||1017        visitPhase === CLONE_UNHIDE1018      ) {1019        applyAppearingPairViewTransition(finishedWork);1020      } else if (visitPhase === CLONE_UPDATE) {1021        applyUpdateViewTransition(current, finishedWork);1022      }1023      popMutationContext(prevMutationContext);1024      break;1025    default: {1026      recursivelyInsertClones(1027        finishedWork,1028        hostParentClone,1029        parentViewTransition,1030        visitPhase,1031      );1032      break;1033    }1034  }1035}10361037// Clone View Transition boundaries that have any mutations or might have had their1038// layout affected by child insertions.1039export function insertDestinationClones(1040  root: FiberRoot,1041  finishedWork: Fiber,1042): void {1043  // We'll either not transition the root, or we'll transition the clone. Regardless1044  // we cancel the root view transition name.1045  const needsClone = detectMutationOrInsertClones(finishedWork);1046  if (needsClone) {1047    // Clone the whole root1048    const rootClone = cloneRootViewTransitionContainer(root.containerInfo);1049    root.gestureClone = rootClone;1050    recursivelyInsertClones(finishedWork, rootClone, null, CLONE_UPDATE);1051  } else {1052    root.gestureClone = null;1053    cancelRootViewTransitionName(root.containerInfo);1054  }1055}10561057function measureExitViewTransitions(placement: Fiber): void {1058  if (placement.tag === ViewTransitionComponent) {1059    // const state: ViewTransitionState = placement.stateNode;1060    const props: ViewTransitionProps = placement.memoizedProps;1061    const name = props.name;1062    if (name != null && name !== 'auto') {1063      // TODO: Find a pair1064    }1065  } else if ((placement.subtreeFlags & ViewTransitionStatic) !== NoFlags) {1066    // TODO: Check if this is a hidden Offscreen or a Portal.1067    let child = placement.child;1068    while (child !== null) {1069      measureExitViewTransitions(child);1070      child = child.sibling;1071    }1072  } else {1073    // We don't need to find pairs here because we would've already found and1074    // measured the pairs inside the deletion phase.1075  }1076}10771078function recursivelyRestoreNew(1079  finishedWork: Fiber,1080  nearestMountedAncestor: Fiber,1081): void {1082  // There has to be move a Placement AND an Update flag somewhere below for this1083  // pass to be relevant since we only apply insertion effects for new components here.1084  if (((Placement | Update) & finishedWork.subtreeFlags) !== NoFlags) {1085    let child = finishedWork.child;1086    while (child !== null) {1087      recursivelyRestoreNew(child, nearestMountedAncestor);1088      child = child.sibling;1089    }1090  }1091  switch (finishedWork.tag) {1092    case FunctionComponent:1093    case ForwardRef:1094    case MemoComponent:1095    case SimpleMemoComponent: {1096      const current = finishedWork.alternate;1097      if (current === null && finishedWork.flags & Update) {1098        // Insertion Effects are mounted temporarily during the rendering of the snapshot.1099        // We have now already takes a snapshot of the inserted state so we can now unmount1100        // them to get back into the original state before starting the animation.1101        commitHookEffectListUnmount(1102          HookInsertion | HookHasEffect,1103          finishedWork,1104          nearestMountedAncestor,1105        );1106      }1107      break;1108    }1109  }1110}11111112function recursivelyApplyViewTransitions(parentFiber: Fiber) {1113  const deletions = parentFiber.deletions;1114  if (deletions !== null) {1115    for (let i = 0; i < deletions.length; i++) {1116      const childToDelete = deletions[i];1117      commitEnterViewTransitions(childToDelete, true);1118    }1119  }11201121  if (1122    parentFiber.alternate === null ||1123    (parentFiber.subtreeFlags & MutationMask) !== NoFlags1124  ) {1125    // If we have mutations or if this is a newly inserted tree, clone as we go.1126    let child = parentFiber.child;1127    while (child !== null) {1128      const current = child.alternate;1129      if (current === null) {1130        measureExitViewTransitions(child);1131        recursivelyRestoreNew(child, parentFiber);1132      } else {1133        applyViewTransitionsOnFiber(child, current);1134      }1135      child = child.sibling;1136    }1137  } else {1138    // Nothing has changed in this subtree, but the parent may have still affected1139    // its size and position. We need to measure the old and new state to see if1140    // we should animate its size and position.1141    measureNestedViewTransitions(parentFiber, true);1142  }1143}11441145function applyViewTransitionsOnFiber(finishedWork: Fiber, current: Fiber) {1146  // The effect flag should be checked *after* we refine the type of fiber,1147  // because the fiber tag is more specific. An exception is any flag related1148  // to reconciliation, because those can be set on all fiber types.1149  switch (finishedWork.tag) {1150    case HostPortal: {1151      // TODO: Consider what should happen to Portals. For now we exclude them.1152      break;1153    }1154    case OffscreenComponent: {1155      const newState: OffscreenState | null = finishedWork.memoizedState;1156      const isHidden = newState !== null;1157      const wasHidden = current.memoizedState !== null;1158      if (!isHidden) {1159        if (wasHidden) {1160          measureExitViewTransitions(finishedWork);1161          recursivelyRestoreNew(finishedWork, finishedWork);1162        } else {1163          recursivelyApplyViewTransitions(finishedWork);1164        }1165      } else {1166        if (!wasHidden) {1167          // Was previously mounted as visible but is now hidden.1168          commitEnterViewTransitions(current, true);1169        }1170      }1171      break;1172    }1173    case ViewTransitionComponent: {1174      const prevContextChanged = viewTransitionContextChanged;1175      const prevCancelableChildren = pushViewTransitionCancelableScope();1176      viewTransitionContextChanged = false;1177      recursivelyApplyViewTransitions(finishedWork);11781179      if (viewTransitionContextChanged) {1180        finishedWork.flags |= Update;1181      }11821183      const inViewport = measureUpdateViewTransition(1184        current,1185        finishedWork,1186        true,1187      );11881189      if ((finishedWork.flags & Update) === NoFlags || !inViewport) {1190        // If this boundary didn't update, then we may be able to cancel its children.1191        // We bubble them up to the parent set to be determined later if we can cancel.1192        // Similarly, if old and new state was outside the viewport, we can skip it1193        // even if it did update.1194        if (prevCancelableChildren === null) {1195          // Bubbling up this whole set to the parent.1196        } else {1197          // Merge with parent set.1198          // $FlowFixMe[method-unbinding]1199          prevCancelableChildren.push.apply(1200            prevCancelableChildren,1201            viewTransitionCancelableChildren,1202          );1203          popViewTransitionCancelableScope(prevCancelableChildren);1204        }1205        // TODO: If this doesn't end up canceled, because a parent animates,1206        // then we should probably issue an event since this instance is part of it.1207      } else {1208        const props: ViewTransitionProps = finishedWork.memoizedProps;1209        scheduleGestureTransitionEvent(finishedWork, props.onGestureUpdate);1210        // If this boundary did update, we cannot cancel its children so those are dropped.1211        popViewTransitionCancelableScope(prevCancelableChildren);1212      }12131214      if ((finishedWork.flags & AffectedParentLayout) !== NoFlags) {1215        // This boundary changed size in a way that may have caused its parent to1216        // relayout. We need to bubble this information up to the parent.1217        viewTransitionContextChanged = true;1218      } else {1219        // Otherwise, we restore it to whatever the parent had found so far.1220        viewTransitionContextChanged = prevContextChanged;1221      }12221223      const viewTransitionState: ViewTransitionState = finishedWork.stateNode;1224      viewTransitionState.clones = null; // Reset1225      break;1226    }1227    default: {1228      recursivelyApplyViewTransitions(finishedWork);1229      break;1230    }1231  }1232}12331234// Revert insertions and apply view transition names to the "new" (current) state.1235export function applyDepartureTransitions(1236  root: FiberRoot,1237  finishedWork: Fiber,1238): void {1239  // First measure and apply view-transition-names to the "new" states.1240  viewTransitionContextChanged = false;1241  pushViewTransitionCancelableScope();12421243  recursivelyApplyViewTransitions(finishedWork);12441245  // Then remove the clones.1246  const rootClone = root.gestureClone;1247  if (rootClone !== null) {1248    root.gestureClone = null;1249    removeRootViewTransitionClone(root.containerInfo, rootClone);1250  }12511252  if (!viewTransitionContextChanged) {1253    // If we didn't leak any resizing out to the root, we don't have to transition1254    // the root itself. This means that we can now safely cancel any cancellations1255    // that bubbled all the way up.1256    const cancelableChildren = viewTransitionCancelableChildren;1257    if (cancelableChildren !== null) {1258      for (let i = 0; i < cancelableChildren.length; i += 3) {1259        cancelViewTransitionName(1260          cancelableChildren[i] as any as Instance,1261          cancelableChildren[i + 1] as any as string,1262          cancelableChildren[i + 2] as any as Props,1263        );1264      }1265    }1266    // We also cancel the root itself. First we restore the name to the documentElement1267    // and then we cancel it.1268    restoreRootViewTransitionName(root.containerInfo);1269    cancelRootViewTransitionName(root.containerInfo);1270  }1271  popViewTransitionCancelableScope(null);1272}12731274function recursivelyRestoreViewTransitions(parentFiber: Fiber) {1275  const deletions = parentFiber.deletions;1276  if (deletions !== null) {1277    for (let i = 0; i < deletions.length; i++) {1278      const childToDelete = deletions[i];1279      restoreEnterOrExitViewTransitions(childToDelete);1280    }1281  }12821283  if (1284    parentFiber.alternate === null ||1285    (parentFiber.subtreeFlags & MutationMask) !== NoFlags1286  ) {1287    // If we have mutations or if this is a newly inserted tree, clone as we go.1288    let child = parentFiber.child;1289    while (child !== null) {1290      restoreViewTransitionsOnFiber(child);1291      child = child.sibling;1292    }1293  } else {1294    // Nothing has changed in this subtree, but the parent may have still affected1295    // its size and position. We need to measure the old and new state to see if1296    // we should animate its size and position.1297    restoreNestedViewTransitions(parentFiber);1298  }1299}13001301function restoreViewTransitionsOnFiber(finishedWork: Fiber) {1302  const current = finishedWork.alternate;1303  if (current === null) {1304    restoreEnterOrExitViewTransitions(finishedWork);1305    return;1306  }13071308  const flags = finishedWork.flags;1309  // The effect flag should be checked *after* we refine the type of fiber,1310  // because the fiber tag is more specific. An exception is any flag related1311  // to reconciliation, because those can be set on all fiber types.1312  switch (finishedWork.tag) {1313    case HostPortal: {1314      // TODO: Consider what should happen to Portals. For now we exclude them.1315      break;1316    }1317    case OffscreenComponent: {1318      if (flags & Visibility) {1319        const newState: OffscreenState | null = finishedWork.memoizedState;1320        const isHidden = newState !== null;1321        if (!isHidden) {1322          restoreEnterOrExitViewTransitions(finishedWork);1323          // $FlowFixMe[invalid-compare]1324        } else if (current !== null && current.memoizedState === null) {1325          // Was previously mounted as visible but is now hidden.1326          restoreEnterOrExitViewTransitions(current);1327        }1328      }1329      break;1330    }1331    case ViewTransitionComponent:1332      restoreUpdateViewTransitionForGesture(current, finishedWork);1333      recursivelyRestoreViewTransitions(finishedWork);1334      break;1335    default: {1336      recursivelyRestoreViewTransitions(finishedWork);1337      break;1338    }1339  }1340}13411342// Revert transition names and start/adjust animations on the started View Transition.1343export function startGestureAnimations(1344  root: FiberRoot,1345  finishedWork: Fiber,1346): void {1347  restoreViewTransitionsOnFiber(finishedWork);1348  restoreRootViewTransitionName(root.containerInfo);1349}

Code quality findings 100

Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
i === 0
Use strict inequality (!==) to prevent type coercion bugs
info correctness loose-inequality
if (fiber._debugTask != null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
appearingViewTransitions === null ||
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
appearingViewTransitions.size === 0
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if ((deletion.subtreeFlags & ViewTransitionNamedStatic) === NoFlags) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
while (child !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (child.tag === OffscreenComponent && child.memoizedState !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
child.tag === ViewTransitionComponent &&
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
(child.flags & ViewTransitionNamedStatic) !== NoFlags
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (name != null && name !== 'auto') {
Use strict inequality (!==) to prevent type coercion bugs
info correctness loose-inequality
if (name != null && name !== 'auto') {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (pair !== undefined) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (className !== 'none') {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (clones !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (pairs.size === 0) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (deletion.tag === ViewTransitionComponent) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
appearingViewTransitions !== null
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
pair !== undefined ? props.share : props.enter,
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (className !== 'none') {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (pair !== undefined) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (clones !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
} else if ((deletion.subtreeFlags & ViewTransitionStatic) !== NoFlags) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
while (child !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if ((child.flags & ViewTransitionNamedStatic) !== NoFlags) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (props.name == null || props.name === 'auto') {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (className !== 'none') {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (clones !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (className !== 'none') {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (clones !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (className !== 'none') {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (clones !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (className === 'none') {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (clones !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
visitPhase === INSERT_APPEARING_PAIR &&
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
parentViewTransition === null &&
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
while (child !== null) {
Ensure all cases are handled or a default case is present
info correctness switch-without-default
switch (finishedWork.tag) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (visitPhase !== INSERT_APPEARING_PAIR) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (parentViewTransition !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (parentViewTransition.clones === null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (textInstance === null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (visitPhase !== INSERT_APPEARING_PAIR) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
const isHidden = newState !== null;
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (visitPhase === INSERT_EXIT) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (visitPhase === INSERT_EXIT) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
visitPhase === INSERT_APPEARING_PAIR ||
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
visitPhase === INSERT_APPEND
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
while (child !== null) {
Ensure all cases are handled or a default case is present
info correctness switch-without-default
switch (child.tag) {
Ensure all cases are handled or a default case is present
info correctness switch-without-default
switch (visitPhase) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
(child.subtreeFlags & ViewTransitionNamedStatic) !== NoFlags
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (nextPhase !== CLONE_UNCHANGED) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (parentViewTransition !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (parentViewTransition.clones === null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (visitPhase === CLONE_EXIT || visitPhase === CLONE_UNHIDE) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (textInstance === null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (visitPhase === CLONE_EXIT || visitPhase === CLONE_UNHIDE) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
const isHidden = newState !== null;
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (visitPhase === CLONE_EXIT) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
} else if (visitPhase === CLONE_UPDATE) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (visitPhase === CLONE_EXIT) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
visitPhase === CLONE_APPEARING_PAIR ||
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
visitPhase === CLONE_UNHIDE
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
} else if (visitPhase === CLONE_UPDATE) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (deletions !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
parentFiber.alternate === null ||
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
(parentFiber.subtreeFlags & MutationMask) !== NoFlags
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
while (child !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (current === null) {
Ensure all cases are handled or a default case is present
info correctness switch-without-default
switch (finishedWork.tag) {
Ensure try blocks have corresponding catch or finally blocks
info correctness try-without-catch
try {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (finishedWork.child === null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (visitPhase === CLONE_EXIT || visitPhase === CLONE_UNHIDE) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (parentViewTransition !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (parentViewTransition.clones === null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (textInstance === null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (visitPhase === CLONE_EXIT || visitPhase === CLONE_UNHIDE) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
const isHidden = newState !== null;
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (visitPhase === CLONE_UPDATE && (flags & Visibility) !== NoFlags) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
} else if (current !== null && current.memoizedState === null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (visitPhase === CLONE_EXIT) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (visitPhase === CLONE_EXIT) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
visitPhase === CLONE_APPEARING_PAIR ||
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
visitPhase === CLONE_UNHIDE
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
} else if (visitPhase === CLONE_UPDATE) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (placement.tag === ViewTransitionComponent) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (name != null && name !== 'auto') {
Use strict inequality (!==) to prevent type coercion bugs
info correctness loose-inequality
if (name != null && name !== 'auto') {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
} else if ((placement.subtreeFlags & ViewTransitionStatic) !== NoFlags) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
while (child !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (((Placement | Update) & finishedWork.subtreeFlags) !== NoFlags) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
while (child !== null) {
Ensure all cases are handled or a default case is present
info correctness switch-without-default
switch (finishedWork.tag) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (current === null && finishedWork.flags & Update) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (deletions !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
parentFiber.alternate === null ||
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
(parentFiber.subtreeFlags & MutationMask) !== NoFlags
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
while (child !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (current === null) {
Ensure all cases are handled or a default case is present
info correctness switch-without-default
switch (finishedWork.tag) {

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.