In React, the ReactDOM.findDomNode method is used to get the DOM elements returned by the render method of a component instance. But what if, on the other hand, you want to get the component instance based on the DOM elements? I’m sure anyone familiar with React has seen the following code

1
2
3
4
5
6
7
8
9
/** 根据 DOM 节点查找其所在的 React 组件实例  */
export function findReactElement(node) {
    for (const key in node) {
        if (key.startsWith('__reactInternalInstance$') && node[key]._debugOwner) {
            return node[key]._debugOwner.stateNode;
        }
    }
    return null;
}

This is a tricky approach implemented in React 16 based on the features of the fiber mechanism. However, in React 17 this method fails. A simple debugging shows that node._reactInternals.child.stateNode on the node node property is what we want, so modify it to

1
2
3
4
5
6
7
8
9
/** 根据 DOM 节点查找其所在的 React 组件实例  */
export function findReactElement(node) {
    for (const key in node) {
        if (key.startsWith('_reactInternals') && node[key].child) {
            return node[key].child.stateNode;
        }
    }
    return null;
}

Test it, the effect is consistent with expectations. I thought it was so simple to complete the compatibility, but soon the test lady’s bug came to the door, and marked as a serious level. The state of mind at this point orz

Simulation of the test environment for debugging, found that node._reactInternals is actually no longer. Combined with search engine search and scenario simulation analysis, it turns out that _reactInternals only exists in development mode. In production mode, it was found that the component instance could be found from node.__reactFiber.return.stateNode.

After the above analysis, we came up with the following method to get the React component instance it is on based on the DOM node in React 17.

 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
/* 查找组件实例 */
const GetCompFiber = fiber => { 
    let parentFiber = fiber.return; 
    while (typeof parentFiber.type === 'string') { 
        parentFiber = parentFiber.return; 
    } 
    return parentFiber.stateNode; 
}
/** 根据 DOM 节点查找其所在的 React 组件实例  */
export function findReactElement(node) {
    if (!node) return null;
 
    for (const key in node) {
        // react17
        if (key.startsWith('_reactInternals') && node[key].child) {
            return node[key].child.stateNode;
        }
        if (key.startsWith('__reactFiber') && node[key]) {
            if (node[key]._debugOwner) return node[key]._debugOwner.stateNode;
            return GetCompFiber(node[key]);
        }
 
        // 兼容 react16
        if (key.startsWith('__reactInternalInstance$') && node[key]._debugOwner) {
            return node[key]._debugOwner.stateNode;
        }
    }
    return null;
}

As a final hint, in general, the main purpose of getting a component instance is to call its methods or properties. For this purpose, using state management or a simple event triggering mechanism is a more reasonable solution. Getting a component instance based on a DOM element is an unconventional trick, and since it is not a standard officially supported solution, it may pose some risks due to solution failure during version upgrades.