'Is there a way to test a component is part of another component react subtree?

I know i can use elementA.contains(elementB) to test if an element B is part of the Dom subtree of an element A. But when using a portal to display B this wont work anymore as B is not anymore in the Dom Subtree.

Is there any clean way to test that B is in the React subtree of A ?

Edit: codeSandbox example code https://codesandbox.io/s/nifty-surf-e1e8by?file=/src/App.js

Click the "open a dialog in portal" and then click the close button. It will fire the portal click event. I know in this case we could have bound the event with onClick props on the portal markup but this is not the solution i'm looking for. What I'm looking for is a way to test in the event handler that evt.currentTarget.contains(evt.target) as part of its react subtree not dom subtree. Hope this is more clear.



Solution 1:[1]

To borrow the principles of React Testing Library, tests should resememble how your software is used, not how it is built.

Unless you are building a library, the question of whether a component is part of another component's subtree probably isn't relevant – that's an implementation detail (which may change at some point!).

In this case, it sounds like you want to ensure that a particular component is clickable.

You could add a check within your event listener to see if the click came from the component in question:

if (evt.currentTarget.querySelector(".dialog") === null) return;

https://codesandbox.io/s/late-water-3ddf5p?file=/src/App.js:877-941

Or simply target .dialog directly. From the user perspective, does it matter that it is inside the #portal element?

Solution 2:[2]

@Daniel Grant here's the crappy solution I came up with for now :

    const getFiber = (component) => {
        const fiberKey = Object.keys(component).filter(k=>!!k.match(/reactFiber/))[0]
        return component[fiberKey]
    }
    const isInReactSubTreeOf = (maybeChildElementOrFiber, parentElement) => {
        const childFiber = maybeChildElementOrFiber instanceof HTMLElement ? getFiber(maybeChildElementOrFiber) : maybeChildElementOrFiber
        if (!childFiber.return) { // no more parent exit
            return false
        }
        if(childFiber.return?.stateNode === parentElement ) { // testing parentElement
            return true
        }
        // we continue to walk the tree from parent
        return isInReactSubTreeOf(childFiber.return, parentElement)
    }

then we can do

isInReactSubTreeOf(elementB, elementA)

This was interesting to find out but, this used unreliable apis so I was looking for a cleaner way to perform the same thing.

When I say unreliable, it's because it's clearly not intended to be used that way. Future implementation can break this hack without warning, as once again it's not intended to be used that way. This is internal api we should not mess with.

If you come with a better solution the bounty is still running ;)

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Daniel Grant
Solution 2 malko