Ismailp
Ismailp

Reputation: 2383

ReactJS: Is it possible to include a parent component in its child component?

I have a component which includes another component in ReactJS like so:

var Child = require('Child');

var Parent = React.createClass({
  render: function() {
    return (
      <div>
         <Child key={someKey} data={someData} />
      </div>
    );   
  },    
});

module.exports = Parent;

Then In my child component I want to include both the parent and child again. Sort of like nesting divs in each other.

Code for the child component below:

var Parent = require('Parent');

var Child = React.createClass({
  render: function() {
    return (
      <div>
        <Parent key={someOtherKey} data={someOtherData} />
      </div>
    );
  }
});

module.exports = Child;

Although I'm getting the following error: Uncaught TypeError: e.toUpperCase is not a function

Is this behaviour allowed in React? If not, how is it supposed to be structured?

Upvotes: 2

Views: 585

Answers (4)

Mattias Farnemyhr
Mattias Farnemyhr

Reputation: 4238

The first problem is that you can't import both modules into each other. The second problem is that you need to check whether or not to create the second parent object otherwise you will get an infinite loop. Something like this:

var Child = require('Child');

var Parent = React.createClass({
  render: function() {
    return (
      <div>
         <Child key={someKey} data={someData} />
      </div>
    );   
  },    
});

module.exports = Parent;

And then:

var Child = React.createClass({
  render: function() {
    var parent = nil;
    if ({hasNestedParent}) {
        var Parent = require('Parent');
        parent = (
            <Parent key={someOtherKey} data={someOtherData} />
        );
    }
    return (
      <div>{parent}</div>
    );
  }
});

module.exports = Child;

Notice that the Parent module is not imorted in the Child module unless it's needed.

Upvotes: 0

Sebastien Lorber
Sebastien Lorber

Reputation: 92150

What you want to do is ok but you have to take care of not creating an infinite recursion, so you need a stop condition.

Here is a JsFiddle executable example

function getOffset(depth) {
  var prefix = "";
  for (var i=0;i<depth;i++) {
      prefix = prefix + "-";
  }
  return prefix;
}

var Parent = React.createClass({
    render: function() {
        var offset = getOffset(this.props.depth);
        return <div>{offset}Parent=[<Child depth={this.props.depth}/>{offset}]</div>;
    }
});


var Child = React.createClass({
    render: function() {
        if ( this.props.depth >= 5 ) {
            return false;
        }
        var newDepth = this.props.depth +1;
        var offset = getOffset(this.props.depth);
        return <div>{offset}Child=(<Parent depth={newDepth}/>{offset})</div>;
    }
});

React.render(<Parent depth={0} />, document.body);

The output is

Parent=[
Child=(
-Parent=[
-Child=(
--Parent=[
--Child=(
---Parent=[
---Child=(
----Parent=[
----Child=(
-----Parent=[-----]
----)
----]
---)
---]
--)
--]
-)
-]
)
]

This is funny :)

Upvotes: 0

J. Mark Stevens
J. Mark Stevens

Reputation: 4945

Here is a recursive pattern with getTreeNode as the shared element.

    import React, {Component} from 'react';
    import lodash from 'lodash';

    var TreeRootSty = {lineHeight: '120%'}
    var liSty = {listStyleType: 'none'};
    var ulSty = {height: 'inherit', WebkitPaddingStart: '16px'};
    var ulStyle = {height: 'inherit', WebkitPaddingStart: '16px'};
    var iconSty = {marginRight: '10px', width: '16px'};
    var titleSty = {color: '#afac87', marginTop: '2px'};

    var nottogglable = {
        color: '#FFF',
        cursor: 'pointer',
        margin: '0 0 0 .8em'
    };

    var togglable = {
        color: '#815C7C',
        cursor: 'pointer',
        margin: '0'
    };

    var options = {};

    var getTreeNode = function(child, index) {
        return <li key={index} style={liSty}><JTreeViewNode node={child} iconClick={this.props.iconClick} titleClick={this.props.titleClick} /></li>;
    };

    class JTreeViewNodeRender extends Component {
        binder(...methods) { methods.forEach( (method) => this[method] = this[method].bind(this) ); }

        render() {
            var childNodes;
            var pSty = nottogglable;
            if (lodash.has(this.props.node, 'children') && this.props.node.children.length > 0) {
                childNodes = this.props.node.children.map(getTreeNode, this);

                titleSty.color = this.props.node.selected ? '#7BB53B' : '#AF90A5';
            } else {
                titleSty.color = this.props.node.selected ? '#b58900' : '#afac87';
            }

            var isClosed = true;
            if (lodash.has(this.props.node, 'closed')) isClosed = this.props.node.closed;
            ulStyle.display = isClosed ? 'none' : 'inline-block';
            var props = this.props;
            var iconType = lodash.get(props, options.typeName);
            if (iconType == options.icon.sun) iconSty.background = "url('./img/sun.ico') 0/16px no-repeat !important";
            else if (iconType == options.icon.leaf) iconSty.background = "url('./img/leaf.ico') 0/16px no-repeat !important";
            else if (iconType == options.icon.snow) iconSty.background = "url('./img/snow.ico') 0/16px no-repeat !important";
            else iconSty.background = "url('./img/sun.ico') 0/16px no-repeat !important";

            return (
                <div id='TreeNode'>
                    <div id='pSty' style={pSty} className='FlexBox'>
                        <div id='iconSty' onClick={this.iconHandler} style={iconSty}>&nbsp;</div>
                        <div id='titleSty' onClick={this.clickHandler} style={titleSty} >{this.props.node.title}</div>
                    </div>
                    <ul id='ulStyle' style={ulStyle}>
                        {childNodes}
                    </ul>
                </div>
            );
        }
    }

    class JTreeViewNode extends JTreeViewNodeRender {
        constructor() { 
            super();
            this.binder('clickHandler', 'iconHandler');
        }
        iconHandler() { 
            if (lodash.has(this.props.node, 'children') && this.props.node.children.length > 0) {
                this.props.iconClick(this.props.node); 
            } else {
                this.clickHandler();
            }
        }
        clickHandler() { this.props.titleClick(this.props.node); }
    }

    class JTreeViewRender extends Component {
        render() {
            options = this.props.options;
            var childNodes = this.props.data.map(getTreeNode, this);
            return (
                <div id='TreeRootSty' style={TreeRootSty}>
                    <ul id='ulSty' style={ulSty}>
                            {childNodes}
                    </ul>
                </div>
            );
        }
    }

    export default class JTreeView extends JTreeViewRender {}

Upvotes: 0

Pierre Criulanscy
Pierre Criulanscy

Reputation: 8686

First, you have a circular dependency in your code, both Parent and Child require each other.

In order to avoid infinite loop when requiring modules, CommonJS modules act like this :

Parent.js requires Child.js, when Child component calls require('Parent'), the exported value of Parent.js is an empty object. So you get the error e.toUpperCase is not a function since e (Parent) is an empty object.

You should require after the module.exports statement :

var Parent = React.createClass({
  render: function() {
    return (
      <div>
         <Child key={someKey} data={someData} />
      </div>
    );   
  },    
});

module.exports = Parent;

var Child = require('Child');

But event if this solves the circular dependency, I don't understand what you're trying to achieve, it's an infinite loop actually.

Upvotes: 2

Related Questions