Is there a way to tell which component generated a specific DOM node?, e.g.
<CustomDiv>a</CustomDiv>
<div>b</div>
<div>c</div>
CustomDiv
is a just a wrapper that generates <div />
element.
In DOM, these are represented as:
<div data-reactid=".0.0.$/=11">a</div>
<div data-reactid=".0.0.$/=12">b</div>
<div data-reactid=".0.0.$/=13">c</div>
Is there a way to tell which of the DOM nodes have been generated by CustomDiv
component?
Context:
I have a DecoratorComponent
that wraps the render
method of the component that it decorates. DecoratorComponent
then modifies the resulting DOM.
let Foo;
Foo = class extends React.Component {
render () {
return <div>
<SomeOtherComponent />
{['a', 'b', 'c'].map((letter) => { return <p>{letter}</p> })}
</div>;
}
};
Foo = DecoratorComponent(Foo);
However, DecoratorComponent
must modify DOM only thats produced by the target component, i.e. it should exclude the output of SomeOtherComponent
.
I need to find a way to distinguish DOM that has been dynamically generated within the component ({['a', 'b', 'c'].map((letter) => { return <p>{letter}</p> })}
in this example) and DOM thats generated by another component within the Foo
component.
You can use React Developer Tools to inspect which DOM nodes have been rendered by which React component.
In addition, look into ./src/renderers/dom/client/ReactMount.js
, which is the react-dom
Object
used to manage DOM nodes and their relation to React components. Note that data-reactid
attribute is referred to as ATTR_NAME
in the source code.
The data about which component rendered what can be found by digging into the React component instance internals, but you said you want to take a DOM node and figure out which React component rendered it, and this is a bit harder.
Do you really need to manipulate the DOM after React flushes it, or are you content to modify the React element (that gets returned from the JSX/React.createElement
call) before rendering to the DOM happens? Either way, intercepting render
yourself allows you to inspect the element and do what you want with it. Since child composite components don't get expanded into real DOM nodes until later, you'll only get the ones you care about.
Here's an example of a decorator that will add a property to every single element rendered by the given class:
function recursiveAddProps(elem, extraProps) {
if (!elem) return;
if (typeof elem === "string") {
// strings get turned into spans
return React.createElement("span", extraProps, elem);
}
const props = elem.props || {};
const newChildren = React.Children.map(props.children, child =>
recursiveAddProps(child, extraProps)
)
return React.cloneElement(elem, extraProps, newChildren);
}
function DecoratorComponent(klass) {
const oldRender = klass.prototype.render;
klass.prototype.render = function() {
const elem = oldRender.apply(this, arguments);
const name = this.constructor.displayName || this.constructor.name;
return recursiveAddProps(elem, {
className: "myOverriddenClass" // this will set className on every element
});
};
return klass;
}
Using these components
class Foo extends React.Component {
render() {
return (
<div>
<div>Hi!</div>
<SomeOtherComponent />
{['a', 'b', 'c'].map(letter => <div>{letter}</div> )}
</div>
);
}
}
Foo = DecoratorComponent(Foo);
class SomeOtherComponent extends React.Component {
render() {
return (
<ul>
<li>Some</li>
<li>Other</li>
<li>Component</li>
</ul>
);
}
}
the code results in the DOM:
<div class="myOverriddenClass" data-reactid=".0">
<div class="myOverriddenClass" data-reactid=".0.$/=10">
<span class="myOverriddenClass" data-reactid=".0.$/=10.$/=10">Hi!</span>
</div>
<ul data-reactid=".0.$/=11">
<li data-reactid=".0.$/=11.0">Some</li>
<li data-reactid=".0.$/=11.1">Other</li>
<li data-reactid=".0.$/=11.2">Component</li>
</ul>
<div class="myOverriddenClass" data-reactid=".0.$/=12=20">
<span class="myOverriddenClass" data-reactid=".0.$/=12=20.$/=10">a</span>
</div>
<div class="myOverriddenClass" data-reactid=".0.$/=12=21">
<span class="myOverriddenClass" data-reactid=".0.$/=12=21.$/=10">b</span>
</div>
<div class="myOverriddenClass" data-reactid=".0.$/=12=22">
<span class="myOverriddenClass" data-reactid=".0.$/=12=22.$/=10">c</span>
</div>
</div>
You can see that the class was added to every element except those rendered by SomeOtherComponent
— the ul
and li
s.
If you wanted to set some special attribute on the DOM nodes so that you could identify whether or not the Foo
component rendered it, you can specify a data-
attribute and React will flush it to the DOM.
<div data-rendered-by="Foo" data-reactid=".0">
<div data-rendered-by="Foo" data-reactid=".0.$/=10">
<span data-rendered-by="Foo" data-reactid=".0.$/=10.$/=10">Hi!</span>
</div>
<ul data-reactid=".0.$/=11">
<li data-reactid=".0.$/=11.0">Some</li>
<li data-reactid=".0.$/=11.1">Other</li>
<li data-reactid=".0.$/=11.2">Component</li>
</ul>
<div data-rendered-by="Foo" data-reactid=".0.$/=12=20">
<span data-rendered-by="Foo" data-reactid=".0.$/=12=20.$/=10">a</span>
</div>
<div data-rendered-by="Foo" data-reactid=".0.$/=12=21">
<span data-rendered-by="Foo" data-reactid=".0.$/=12=21.$/=10">b</span>
</div>
<div data-rendered-by="Foo" data-reactid=".0.$/=12=22">
<span data-rendered-by="Foo" data-reactid=".0.$/=12=22.$/=10">c</span>
</div>
</div>
Here every DOM node has a data-rendered-by="Foo"
attribute, allowing you to tell from the DOM alone that the node was rendered by Foo
.
Here's a working example on JSBin: https://jsbin.com/tuliqu/edit?js,output
This is a pretty powerful abstraction which may allow you to do what you want without even touching the DOM; for example, in React CSS Modules, the DOM isn't touched — the components are simply extended and render is overridden.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With