How to determine which component is generated by the DOM node?

Is there a way to find out which component generated a particular DOM node ?, e.g.

<CustomDiv>a</CustomDiv> <div>b</div> <div>c</div> 

CustomDiv is just a wrapper that generates a <div /> element.

In the DOM, they 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 determine which of the DOM nodes was generated by the 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 should only modify DOMs that are created by the target component, i.e. it should exclude SomeOtherComponent output.

I need to find a way to distinguish the DOM that is dynamically generated inside the component ( {['a', 'b', 'c'].map((letter) => { return <p>{letter}</p> })} in this example) and the DOM that are generated by another component in the Foo component.

+7
reactjs
source share
4 answers

You can use the React Developer Tools to check which DOM nodes were rendered using the React component.

Also, check out ./src/renderers/dom/client/ReactMount.js , which is a react-dom Object used to manage DOM nodes and their relationship to the React components. Note that the data-reactid in the data-reactid source code is called ATTR_NAME .

+5
source share

I suggest you avoid direct manipulation of the DOM. This is possible with some precautions, but it is likely to reduce performance and may make it difficult to find errors. Let me suggest some ideas for solving your problem in a reaction space.

Let it begin with the simplest. I think you tried to do something like this:

 const Foo = (props) => ( <div> <SomeOtherComponent /> {['a', 'b', 'c'].map((letter) => { return <Decorator><p>{letter}</p></Decorator> })} </div> ); 

If you need to manipulate list items with one shot, you can wrap them as follows:

 const Foo = (props) => ( <Decorator> <SomeOtherComponent /> {['a', 'b', 'c'].map((letter) => { return <p>{letter}</p> })} </Decorator> ); 

The Decorator component will have access to the child’s response elements. We can, for example, make everything bold, except for an instance of SomeOtherComponent

 const Decorator = (props) => ( <div> { React.Children.toArray(props.children).map(child => child.type === SomeOtherComponent ? child : <b key={child.key}>{child}</b> )} </div> ); 

If you want to go further and decide which decorator to use closer to the root of the application, you can simply pass the Decorator component as a support

 <Foo decorator={Bolderator}/> const Foo = ({decorator: Decorator}) => ( <Decorator> <SomeOtherComponent /> {['a', 'b', 'c'].map((letter) => { return <p>{letter}</p> })} </Decorator> ); 
0
source share

The data about which component displayed what can be found breaking into the internal parts of the React instance, but you said that you want to take the DOM node and find out which React component displayed it, and this is a bit more complicated.

Do you really need to manipulate the DOM after React has reset it, or do you want to change the React element (which comes back from the JSX / React.createElement ) before rendering to the DOM? In any case, the capture intercept allows you to check the element and do what you need with it. Since child constituents do not expand to real DOM nodes until then, you only get the ones you need.

Here is an example decorator that will add a property to each element provided by this 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 leads to 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 a class has been added to each element, except for the ones that SomeOtherComponent displays - ul and li s.

If you want to set some special attribute on the DOM nodes so that you can determine if its Foo component is displayed, you can specify the data- attribute, and React will hide it until 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, each DOM node has a data-rendered-by="Foo" attribute, which allows you only from the DOM to indicate that the node was mapped to Foo .

Here is a working example in JSBin: https://jsbin.com/tuliqu/edit?js,output


This is a pretty powerful abstraction that can allow you to do what you want without even touching the DOM; for example, in React CSS Modules, the DOM is not affected - the components are simply expanded and the render is redefined .

0
source share

Your question seems to confuse some terms, so I need to find out first how I understand it.

I have a DecoratorComponent that wraps the render method of the component that it decorates. DecoratorComponent then modifies the resulting DOM.

The immediate decoration of the render method and the "change of the resulting DOM" are two different things. The render method does not return a DOM fragment, it creates a ReactElement . The decision about whether a ReactElement DOM element or a React component (in most cases) is quite simple - its type is String in the first case and something else (a Function or Class ) in the latter.

 function decorateRender(render) { const el = render(); el.props.children = React.Children.map( el.props.children, (child, idx) => (typeof child.type === 'string') ? <div className="wrapper">{child}</div> : child ); return el; } 

If, however, you do not directly decorate the render , and you are really trying to modify the resulting mounted DOM, it is not possible by default to associate DOM elements with their manufacturers. In the development environment, you can try h4ck React internals and find the link (see React Developer Tools ), but in the production environment there is no backlink. The DOM is just a product that happens earlier in React's “virtual DOM”. Of course, you can always extend React.Component to mark elements with the user data attribute in the render.

0
source share

All Articles