Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to tell which component generated DOM node?

Tags:

reactjs

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.

like image 789
Gajus Avatar asked Dec 31 '15 18:12

Gajus


2 Answers

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.

like image 85
jtschoonhoven Avatar answered Nov 19 '22 06:11

jtschoonhoven


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 lis.

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.

like image 40
Michelle Tilley Avatar answered Nov 19 '22 06:11

Michelle Tilley