Detuned
Detuned

Reputation: 3758

Be able to reconstruct or reference React's tree to see parent-child relationships between elements

I have a need to be able to know how child nodes are associated with their parents and to be able to reference them...

For example:

```
<CustomComponent>
  <h1>Hello, World!</h1>

  <CustomComponent>
    <h1>One</h1>
  </CustomComponent>

  <CustomComponent>
    <h1>Two</h1>

    <CustomComponent>
      <h1>Two One</h1>
    </CustomComponent>

    <CustomComponent>
      <h1>Two Two</h1>
    </CustomComponent>
  </CustomComponent>

</CustomComponent>
```

As you can see above, there is an inherent tree structure to these components (composite or host) and I'd like to be able to know, at the very minimum, how CustomComponents are nested throughout an entire React application.

I tried a naive solution with the following:

```
<CustomComponent id="0">
  <h1>Hello, World!</h1>

  <CustomComponent id="0:1">
    <h1>One</h1>
  </CustomComponent>

  <CustomComponent id="0:2">
    <h1>Two</h1>

    <CustomComponent id="0:2:1">
      <h1>Two One</h1>
    </CustomComponent>

    <CustomComponent id="0:2:2">
      <h1>Two Two</h1>
    </CustomComponent>
  </CustomComponent>

</CustomComponent>
```

Obviously this is not an optimal approach as the user would have to define the entire tree structure with ids and in addition, if the order in which these components mount into the DOM is not linear so it would be hard to reconstruct the tree and be able to perform logic on their relationship to one another.

Any ideas on how to go about this? Thank you for your time and assistance.

Upvotes: 3

Views: 587

Answers (1)

Sulthan
Sulthan

Reputation: 130132

While I think it's an abuse of react, you can use context to maintain parent-child relationships:

let componentId = 0

class CustomComponent extends React.Component {
   constructor(props, context) {
      super(props, context);

      this.state = {componentId: componentId++};
   }

   getChildContext() {
      return {
         parentId: this.state.componentId
      };
   }

   render() {
      return (
          <div>
              Parent ID: {this.context.parentId}
              {this.props.children}
          </div>
      );
   }
}

CustomComponent.contextTypes = {
    parentId: React.PropTypes.number
}
CustomComponent.childContextTypes = {
    parentId: React.PropTypes.number
};

You can then use that to generate id or a className to search for the component.

You can probably use that to create increasing identifiers in the form 1-2-3... but that's just an extension of the idea.

You can also use a pure HTML method. In your componentDidMount you can find all direct descendants with a specific class, e.g. using xPath (document.evaluate) but that solution will be a bit slow.

Another possibility is to map the children in render, adding the index as property manually. An example (I hope it covers all possibilities).

class CustomComponent extends React.Component {
    mapChildren(children) {
        const thisIndex = this.props.index;
   
        return React.Children.map(children, child => {
            if (child.type === CustomComponent) {
                return React.cloneElement(child, {
                    index: thisIndex + "-" + this.childIndex++
                });            
            } else if (child.props && child.props.children) {
                return React.cloneElement(child, {
                   children: this.mapChildren(child.props.children)
                });            
            } else {
                return child;
            }
        });
    }      

    render() {
        this.childIndex = 1;
        const newChildren = this.mapChildren(this.props.children);
   
        return (
           <div>              
              This ID: {this.props.index}
              {newChildren}
          </div>
        );
    }
}

CustomComponent.propTypes = {
    index: React.PropTypes.string
};
CustomComponent.defaultProps = {
    index: "1"
};

ReactDOM.render(
  <CustomComponent>
     <h1>Title 1</h1>
     <CustomComponent>
         <h1>Title 2</h1>
      </CustomComponent>
      <CustomComponent>
         <h1>Title 3</h1>
         <CustomComponent>
            <h1>Title 4</h1>
         </CustomComponent>         
         <CustomComponent>
            <h1>Title 5</h1>
         </CustomComponent>        
         <div>
            Wrapping div
            <CustomComponent>
               <h1>Title 6</h1>
            </CustomComponent>         
         </div>
      </CustomComponent>
  </CustomComponent>,
  document.getElementById('container')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>

<div id="container">
    <!-- This element's contents will be replaced with your component. -->
</div>

However, this could have some performance implications.

Upvotes: 1

Related Questions