import type {ComponentType, LegacyRef} from 'react'; // https://github.com/facebook/react/blob/master/packages/shared/ReactWorkTag.js export enum Tag { FunctionComponent = 0, ClassComponent = 1, IndeterminateComponent = 2, HostRoot = 3, HostPortal = 4, HostComponent = 5, HostText = 6, Fragment = 7, Mode = 8, ContextConsumer = 9, ContextProvider = 10, ForwardRef = 11, Profiler = 12, SuspenseComponent = 13, MemoComponent = 14, SimpleMemoComponent = 15, LazyComponent = 16, IncompleteClassComponent = 17, DehydratedSuspenseComponent = 18, } export interface Fiber { tag: Tag; key: null | string; elementType: ComponentType | string | null; type: ComponentType | string | null; stateNode: any; return: Fiber | null; child: Fiber | null; sibling: Fiber | null; index: number; ref: LegacyRef; pendingProps: unknown; memoizedProps: unknown; memoizedState: unknown; } export interface ReactInstance { _reactInternalFiber: Fiber; } // Adapted from https://github.com/cfaester/enzyme-adapter-react-18/blob/87b97516f6cb942c9dad7a9813c0780d4795c1de/src/findCurrentFiberUsingSlowPath.ts#L43, // originally adapted from https://github.com/facebook/react/blob/b76103d66fdb7396cbfcc66a032b31a0cd8ad342/packages/react-reconciler/src/ReactFiberTreeReflection.js const Placement = 0b0000000000000000000000010; const Hydrating = 0b0000000000001000000000000; const NoFlags = 0b0000000000000000000000000; export function findCurrentFiberUsingSlowPath(fiber: any): Fiber | null { const alternate = fiber.alternate; if (!alternate) { // If there is no alternate, then we only need to check if it is mounted. const nearestMounted = getNearestMountedFiber(fiber); if (nearestMounted === null) { throw new Error('Unable to find node on an unmounted component.'); } if (nearestMounted !== fiber) { return null; } return fiber; } // If we have two possible branches, we'll walk backwards up to the root // to see what path the root points to. On the way we may hit one of the // special cases and we'll deal with them. let a = fiber; let b = alternate; while (true) { const parentA = a.return; if (parentA === null) { // We're at the root. break; } const parentB = parentA.alternate; if (parentB === null) { // There is no alternate. This is an unusual case. Currently, it only // happens when a Suspense component is hidden. An extra fragment fiber // is inserted in between the Suspense fiber and its children. Skip // over this extra fragment fiber and proceed to the next parent. const nextParent = parentA.return; if (nextParent !== null) { a = b = nextParent; continue; } // If there's no parent, we're at the root. break; } // If both copies of the parent fiber point to the same child, we can // assume that the child is current. This happens when we bailout on low // priority: the bailed out fiber's child reuses the current child. if (parentA.child === parentB.child) { let child = parentA.child; while (child) { if (child === a) { // We've determined that A is the current branch. // assertIsMounted(parentA); return fiber; } if (child === b) { // We've determined that B is the current branch. // assertIsMounted(parentA); return alternate; } child = child.sibling; } // We should never have an alternate for any mounting node. So the only // way this could possibly happen is if this was unmounted, if at all. throw new Error('Unable to find node on an unmounted component.'); } if (a.return !== b.return) { // The return pointer of A and the return pointer of B point to different // fibers. We assume that return pointers never criss-cross, so A must // belong to the child set of A.return, and B must belong to the child // set of B.return. a = parentA; b = parentB; } else { // The return pointers point to the same fiber. We'll have to use the // default, slow path: scan the child sets of each parent alternate to see // which child belongs to which set. // // Search parent A's child set let didFindChild = false; let child = parentA.child; while (child) { if (child === a) { didFindChild = true; a = parentA; b = parentB; break; } if (child === b) { didFindChild = true; b = parentA; a = parentB; break; } child = child.sibling; } if (!didFindChild) { // Search parent B's child set child = parentB.child; while (child) { if (child === a) { didFindChild = true; a = parentB; b = parentA; break; } if (child === b) { didFindChild = true; b = parentB; a = parentA; break; } child = child.sibling; } if (!didFindChild) { throw new Error( 'Child was not found in either parent set. This indicates a bug ' + 'in React related to the return pointer. Please file an issue.', ); } } } if (a.alternate !== b) { throw new Error( "Return fibers should always be each others' alternates. " + 'This error is likely caused by a bug in React. Please file an issue.', ); } } // If the root is not a host container, we're in a disconnected tree. I.e. // unmounted. if (a.tag !== Tag.HostRoot) { throw new Error('Unable to find node on an unmounted component.'); } if (a.stateNode.current === a) { // We've determined that A is the current branch. return fiber; } // Otherwise B has to be current branch. return alternate; } function getNearestMountedFiber(fiber: any) { let node = fiber; let nearestMounted = 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 = 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; } nextNode = node.return; } while (nextNode); } else { while (node.return) { node = node.return; } } if (node.tag === Tag.HostRoot) { // TODO: Check if this was a nested HostRoot when used with // renderContainerIntoSubtree. return nearestMounted; } // If we didn't hit the root, that means that we're in an disconnected tree // that has been unmounted. return null; }