react-find-dom-node
Version:
React 19 removed ReactDOM.findDOMNode so this fixes that
250 lines (213 loc) • 5.65 kB
text/typescript
import type { Fiber } from "react-reconciler";
type WorkTag =
| 0
| 1
| 2
| 3
| 4
| 5
| 6
| 7
| 8
| 9
| 10
| 11
| 12
| 13
| 14
| 15
| 16
| 17
| 18
| 19
| 20
| 21
| 22
| 23
| 24
| 25
| 26
| 27
| 28
| 29
| 30
| 31;
const HostRoot = 3; // Root of a host tree. Could be nested inside another node.
const HostComponent = 5;
const HostText = 6;
const HostHoistable = 26;
const HostSingleton = 27;
const NoFlags = /* */ 0b0000000000000000000000000000000;
const Placement = /* */ 0b0000000000000000000000000000010;
const Hydrating = /* */ 0b0000000000000000001000000000000;
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: null | 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 (node.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;
}
function assertIsMounted(fiber: Fiber) {
if (getNearestMountedFiber(fiber) !== fiber) {
throw new Error("Unable to find node on an unmounted component.");
}
}
function findCurrentFiberUsingSlowPath(fiber: Fiber): Fiber | null {
const alternate = fiber.alternate;
if (!alternate) {
const nearestMounted = getNearestMountedFiber(fiber);
if (nearestMounted !== fiber) {
return null;
}
return fiber;
}
let a: Fiber = fiber;
let b: Fiber = alternate;
for (;;) {
const parentA = a.return;
if (parentA === null) {
break;
}
const parentB = parentA.alternate;
if (parentB === null) {
const nextParent = parentA.return;
if (nextParent !== null) {
a = b = nextParent;
continue;
}
break;
}
if (parentA.child === parentB.child) {
let child = parentA.child;
while (child) {
if (child === a) {
assertIsMounted(parentA);
return fiber;
}
if (child === b) {
assertIsMounted(parentA);
return alternate;
}
child = child.sibling;
}
throw new Error("Unable to find node on an unmounted component.");
}
if (a.return !== b.return) {
a = parentA;
b = parentB;
}
else {
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) {
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.tag !== HostRoot) {
throw new Error("Unable to find node on an unmounted component.");
}
if (a.stateNode.current === a) {
return fiber;
}
return alternate;
}
function findCurrentHostFiberImpl(node: Fiber): Fiber | null {
// Next we'll drill down this component to find the first HostComponent/Text.
const tag = node.tag as WorkTag;
if (
tag === HostComponent ||
tag === HostHoistable ||
tag === HostSingleton ||
tag === HostText
) {
return node;
}
let child = node.child;
while (child !== null) {
const match = findCurrentHostFiberImpl(child);
if (match !== null) {
return match;
}
child = child.sibling;
}
return null;
}
function findCurrentHostFiber(parent: Fiber): Fiber | null {
const currentParent = findCurrentFiberUsingSlowPath(parent);
return currentParent !== null
? findCurrentHostFiberImpl(currentParent)
: null;
}
export function findDOMNode(component: React.Component | React.PureComponent) {
const reactInternals = (component as unknown as { _reactInternals: Fiber | undefined })._reactInternals;
if (reactInternals === undefined) {
if (typeof component.render === "function") {
throw new Error("Unable to find node on an unmounted component.");
}
throw new Error(`Argument appears to not be a ReactComponent. Keys: ${Object.keys(component).join(",")}`);
}
const hostFiber = findCurrentHostFiber(reactInternals);
if (hostFiber === null) {
return null;
}
return hostFiber.stateNode;
}