Element Rendering issue

Hello,
I am working on TreeView using ReactJs. I have created a tree. When I create a tree, I am expanding all the elements in my code. Because that is my requirement. But when tree is rendered, only parents are shown. But, most interesting part is, when I click on the area of the tree, it gets expanded for all the elements. I am not sure, how and why this is happening.
Do you have any idea about this weird behavior?
Here is tree before and after the click on tree:

Thank you in advance!

Show us the code?

This is the TreeView component:

import React, { Component } from 'react'
import PropTypes from 'prop-types'
import GlobalData from '../GlobalData'
import VisibleTreeNode from '../containers/VisibleTreeNode'
import NativeListener from 'react-native-listener'
class TreeNode extends Component {
    constructor(props) {
      super(props);                      
    }
    
    render() {
      var node = this.props.node;
      var options = this.props;
      var treeType = this.props.type;
      var nodeClassName = "";      
            
      var style = {};
      if (!this.props.visible) {  
        style = { 
          display: 'none' 
        };
      }
      else {
  
        if (this.props.selected) {
          nodeClassName = "list-group-item tree-node-item element-active";
        }
        else {
          nodeClassName = "list-group-item tree-node-item";
        }
  
        if (!options.showBorder) {
          style.border = 'none';
        }
        else if (options.borderColor) {
          style.border = '1px solid ' + options.borderColor;
        }
      } 
  
      var indents = [];
      for (var i = 0; i < this.props.nodeLevel-1; i++) {
        indents.push(<span key={i} className='indent'></span>);
      }
  
      var expandCollapseIcon;      
      if (node.nodes) {              
        if (!this.props.expanded) {          
          expandCollapseIcon = (
            <NativeListener onClick={(e) => this.props.onExpand(e,node.elementId,treeType)}>
              <span className={options.expandIcon}                  
                    >
              </span>
            </NativeListener>
          );
        }
        else {                    
          expandCollapseIcon = (
            <NativeListener onClick={(e)=>this.props.onCollapse(e,
              node.elementId,
              treeType,
              this.props.level,
              this.props.selected)}>
              <span className={options.collapseIcon}>                                
              </span>
            </NativeListener>
          );
        }
      }
      else {
        expandCollapseIcon = (
          <NativeListener onClick={(e) => this.props.onExpand(e,node.elementId,treeType)}>
            <span           
            className={options.emptyIcon}></span>
          </NativeListener>
        );
      }
  
      var nodeIcon = (
        <span className='icon tree-node-icon'>          
          <img src={this.props.icon} />
        </span>
      );
  
      var nodeText;
      if (options.enableLinks) {
        nodeText = (
          <a href={node.href} >
            {node.text}
          </a>
        );
      }
      else {
        nodeText = (
          <span className="node-text">{node.text}</span>
        );
      }
  
      var badges;
      if (options.showTags && node.tags) {
        badges = node.tags.map(function (tag) {
          return (
            <span className='badge'>{tag}</span>
          );
        });
      }
  
      var children = [];
      if (node.nodes) {
        var _this = this;
        var currentNode = node;
        node.nodes.forEach(function (node,i) {
          children.push(<VisibleTreeNode node={node}
                                  key={GlobalData.getGuid()} 
                                  type={_this.props.type} 
                                  level={"child"} 
                                  icon={node.icon}
                                  nodeLevel={_this.props.nodeLevel+1}                                                                                                
                                  visible={_this.props.expanded && _this.props.visible}
                                  expanded={node.expanded}
                                  selected={node.selected}/>);
        });
      }
    
      return (
        <NativeListener onClick={(e)=>this.props.onClick(e,
          node.elementId,
          node.elementText,
          this.props.type,
          this.props.level)}>
          <li className={nodeClassName}
              style={style}            
              id={node.elementId}              
              key={node.nodeId}>        
            {indents}
            {expandCollapseIcon}
            {nodeIcon}
            {nodeText}
            {badges}                                                                   
          </li>          
          {children}                    
        </NativeListener>         
      );
    }
  }

  TreeNode.propTypes = {
    levels: PropTypes.number,
  
    expandIcon: PropTypes.string,
    collapseIcon: PropTypes.string,
    emptyIcon: PropTypes.string,
    nodeIcon: PropTypes.string,
  
    color: PropTypes.string,
    backColor: PropTypes.string,
    borderColor: PropTypes.string,
    onhoverColor: PropTypes.string,
    selectedColor: PropTypes.string,
    selectedBackColor: PropTypes.string,
  
    enableLinks: PropTypes.bool,
    highlightSelected: PropTypes.bool,
    showBorder: PropTypes.bool,
    showTags: PropTypes.bool,
    nodes: PropTypes.arrayOf(PropTypes.object)  
  }
  
  TreeNode.defaultProps = {
      levels: 2,
  
      expandIcon: 'glyphicon glyphicon-plus',
      collapseIcon: 'glyphicon glyphicon-minus',
      emptyIcon: 'glyphicon glyphicon-plus',
      nodeIcon: undefined,
  
      color: undefined,
      backColor: undefined,
      borderColor: '#336699',
      onhoverColor: '#F5F5F5', 
      selectedColor: '#FFFFFF',
      selectedBackColor: '#428bca',
  
      enableLinks: false,
      highlightSelected: true,
      showBorder: true,
      showTags: false,
  
      nodes: []
  }

  export default TreeNode;

Mkay… and where do you invoke this code?

Here:

import React from 'react'
import VisibleTreeNode from '../containers/VisibleTreeNode'
import GlobalData from '../GlobalData'

function UpstreamImpactTree(props) {      
    return (
        <div className="coverage-tree">         
            <div className="treeContainer">      
                <div id="centralTree row">     
                    <div id='treeview' className='treeview'>
                        <ul 
                        id="treeview-wrapper" 
                        type="IMPACT_UT" 
                        className='list-group'>  
                            {props.data.map((node) =>      
                                <VisibleTreeNode
                                key={GlobalData.getGuid()}
                                visible={node.visible}
                                expanded={node.expanded}
                                selected={node.selected}
                                icon={node.icon}
                                type="IMPACT_UT" 
                                level={"root"}
                                nodeLevel={1}
                                node={node}/>
                            )}
                            </ul>
                          </div>
                      </div>   
                  </div>
                </div>
    )    
  }

  export default UpstreamImpactTree

And here is VisibleTreeNode:

import { connect } from 'react-redux'
import TreeNode from '../components/TreeNode'
import {fetchCentralRootChildren,
  fetchUpstreamAndDownstreamRoots,
  fetchUpstreamRootChildren,
  fetchDownstreamRootChildren,
  collapseNode,
  showElementText,
  highlightCurrentSelection,
  emptyUpstreamAndDownstream,
  fetchDownstreamAndUpstreamImpactTree,
  fetchImpactViewCentralRootChildren,
  showImpactViewElementText} from '../actions'
import GlobalData from '../GlobalData';

const mapStateToProps = (state,ownProps) => {
    return ownProps;
  }

  const mapDispatchToProps = dispatch => {
    return {
      onClick: (event,nodeElementId,elementText,type,nodeLevel) => {        
        event.stopPropagation();                                   
        var showText = "";
        if(elementText === undefined ||
        elementText === null ||
        elementText === "undefined") {
          showText = "";
        } else {
          showText = elementText;
        }

        if(type.includes("IMPACT")) {
          dispatch(showImpactViewElementText(showText));          
        } else {
          dispatch(showElementText(showText));          
        }        

        switch(type) {
          case "CT": 
          if(nodeLevel !== 'root') {
            fetchUpstreamAndDownstreamItems(nodeElementId,dispatch).then(() => {
                dispatch(highlightCurrentSelection(nodeElementId,type));              
              })     
           }
            break;
          case "IMPACT_CT":
            if(nodeLevel !== 'root') {
              fetchImpactViewUpstreamAndDownstreamItems(nodeElementId,dispatch).then(() => {
                dispatch(highlightCurrentSelection(nodeElementId,type));              
              })     
            }
            break;          
          default: dispatch(highlightCurrentSelection(nodeElementId,type));
        }                     
      },
      onExpand: (event,nodeElementId,type) => {
        event.stopPropagation(); 
        
        switch(type) {
          case "CT":dispatch(fetchCentralRootChildren(nodeElementId));
                    break;
          case "UT":dispatch(fetchUpstreamRootChildren(nodeElementId,"coverageView"));
                    break;   
          case "DT":dispatch(fetchDownstreamRootChildren(nodeElementId,"coverageView"));
                    break;    
          case "IMPACT_CT": dispatch(fetchImpactViewCentralRootChildren(nodeElementId));
                    break;    
          case "IMPACT_UT":dispatch(fetchUpstreamRootChildren(nodeElementId,"impactView"));
                    break;   
          case "IMPACT_DT":dispatch(fetchDownstreamRootChildren(nodeElementId,"impactView"));
                    break;                                                                                  
        }        
      },
      onCollapse: (event,nodeElementId,type,nodeLevel,isSelected) => {
        event.stopPropagation();              
        dispatch(collapseNode(event,nodeElementId,type))

        if(type === 'CT' && nodeLevel === 'root' && isSelected === false) {
          dispatch(emptyUpstreamAndDownstream());
        }
      },
    }
  }

  const fetchUpstreamAndDownstreamItems = (nodeElementId,dispatch) => new Promise((resolve, reject) => {
    dispatch(fetchUpstreamAndDownstreamRoots(nodeElementId));
    resolve();
  })

  const fetchImpactViewUpstreamAndDownstreamItems = (nodeElementId,dispatch) => new Promise((resolve, reject) => {
    dispatch(fetchDownstreamAndUpstreamImpactTree(nodeElementId));
    resolve();
  })

  const VisibleTreeNode = connect(
    mapStateToProps ,
    mapDispatchToProps      
  )(TreeNode)

  export default VisibleTreeNode;

Before I start answering, this component has a lot going on in it and I’m not going to debug it for you, especially since it’s not built on standard React practices making it very hard for me to read. So, I’m going to give you some tips based on a few things I pulled out to help get you into standard practices. Hopefully in doing so, you can figure out where your issue is.


You should be using const and let, there’s no good reason to use var if you’re using other ES6 features. Just forget var exists. It’s only good for hoisting. You’ll most likely never need or want your variables hoisted if your code is designed right.


Your render function should be very very light. Building other methods that render is fine, it would look something like this:

class TreeNode extends Component {
    someOtherThing = () => {
        return <span>Hello World</span>
    }

    render() {
        return <div>{this.someOtherThing()}</div>
    }
}

You should not be doing all the above in the render method. But, honestly with how much you have going on in this you should really be moving most of this to their own components to simplify your code. More components are better than big components. They are easier to read, support, and expand. Don’t be afraid of making new components, be afraid of shoving too much into one.


Below is not the way you should be conditionally rendering in React. Don’t feel bad though I even catch myself doing this sometimes without thinking.

if (!this.props.visible) {  
    style = { 
        display: 'none' 
    };
}

The way you’d do this in react, is to just not render the element via a little bit of inline JSX. So, taking the short example class I did above you’d do something like this:

render() {
  return (
    <div>
      {this.props.visible && this.someOtherThing()}
    </div>
  )
}

This works because if the left side of the boolean argument is false, then the right side will not be called thus not rendering the component. This the most preferred way to do it in React, because it’s the cleanest.


Here, you can use destructuring:

var node = this.props.node;
var options = this.props;
var treeType = this.props.type;

Should be:

const {node, type, ...rest} = this.props

then you can use the variables just like you normally would. The rest variable contains all the things not explicitly pulled out of this.props, which replaces your options variable. The ... is called the spread operator. It works both ways. It’s very cool and incredibly useful.


Also, classnames might be a useful library for you to use. It will keep you from doing the gross if/else statements to conditionally set classes and automatically handles undefined and null values, where as normally the actual words undefined/null would be passed to the className prop. It’s used by most React apps that use classes.

4 Likes

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.