Uncategorized

Visualising a Tree Structure with React & Redux

A recent project of mine involved visualising a tree like data structure. Turns out React’s out-of-the-box support for recursive components makes this a really easy challenge. Here’s how you can build one.

For the impatient here’s what the end result looks like:

Let’s Start with the Data

The data itself is kept in memory as immutable objects using Immutable.JS. A reducer modifies the data according to two kinds of actions: “Add a new node” and “Expand/Collapse node”.

Each node is represented as an object with a text field, a list of children, expanded/collapsed state and index (called path). The index is kept as an array of indices from root down to the described node. An empty array is used as the index of the root item.

Initially we only have a single root node:

var initialState = Immutable.fromJS({
  text: "root", 
  childNodes: [],
  expanded: false,
  path: [],
});

And we can add more nodes via the reducer:

function reducer(state = Immutable.fromJS(initialState), action) {
  var path;
  switch(action.type) {
    case ADD:
      path = pathForUpdate(action.payload.path);
      return state.updateIn(path, 
                            (node) => node.update('childNodes', 
                                                  (list) => list.push(generateNode(action.payload, list.size))) );
    case TOGGLE:
      path = pathForUpdate(action.payload);
      return state.updateIn(path, (node) => node.update('expanded', (val) => ! val ));
      
    default:
      return state;
  }
}

function act_toggle(path) {
  return { type: TOGGLE, payload: path };
}

function act_add(text, path) {
  return { type: ADD, payload: { path: Immutable.fromJS(path), text: text }};  
}

/**
* Create the store and add some nodes.
* Note the second argument to act_add is the path to 
* the parent node
*/
var store = Redux.createStore(reducer);

store.dispatch(act_add("0", []));
store.dispatch(act_add("1", []));
store.dispatch(act_add("2", []));
store.dispatch(act_add("0_0", [0]));
store.dispatch(act_add("0_1", [0]));
store.dispatch(act_add("0_2", [0]));
store.dispatch(act_add("1_0", [1]));
store.dispatch(act_add("1_1", [1]));
store.dispatch(act_add("1_2", [1]));
store.dispatch(act_add("1_3", [1]));
store.dispatch(act_add("1_4", [1]));
store.dispatch(act_add("0_0_0", [0,0]));
store.dispatch(act_add("0_0_1", [0,0]));
store.dispatch(act_add("0_0_2", [0,0]));
store.dispatch(act_add("0_0_3", [0,0]));
store.dispatch(act_add("0_0_4", [0,0]));

Visualising With React Components

Now that all the data is in place we can proceed to creating the React components that show this tree. This is where React shines: A tree is a collection of nodes, so a single Node component will suffice to represent both container and children.

Recursion is baked into the framework, because we can use <Node /> inside the render() function of Node component to build the entire tree.

The code example assumes a toggle() property received from the parent to toggle the node’s state. This method is implemented in an external container component in order to decouple our Node from the Redux store. Below is the relevant code:

var Node = React.createClass({
  toggle: function(path) {
    this.props.toggle(path);
  },
  shouldComponentUpdate: function(nextProps, nextState) {
    return ! Immutable.is(nextProps.item, this.props.item);
  },
  divStyle: function(item) {
    var indent    = item.get('path').size;
    return {
      marginLeft: (indent * 10) + "px"
    };
  },  
  renderChild: function(item) {
    return <Node item={item} toggle={this.props.toggle} />
  },  
  render: function() {
    var item = this.props.item
    var disabled  = item.get('childNodes').size == 0;
    var collapsed = ! item.get('expanded');
    var text      = disabled ? "" : collapsed ? "+" : "-";
    
    return <div style={this.divStyle(item)}>
          <button disabled={disabled} 
                  onClick={this.toggle.bind(this, item.get('path'))}>{text}</button>
          <span>{item.get('text')}</span>
          {item.get('expanded') ? item.get('childNodes').map(this.renderChild, this) : false }
      </div>
  }
});

Redux Big Win: Performance

React and Redux are a great fit because immutable data makes it very easy to implement shouldComponentUpdate. In case you don’t remember, this function is called whenever an element’s state or props change and before rendering it. We can use it to prevent rendering if the change is not relevant to this element.

Working with Immutable data means a simple comparison of the represented data object is all we need in order to know if the node or any of its children have changed.

Note the simple implementation of shouldComponentUpdate:

  shouldComponentUpdate: function(nextProps, nextState) {
    return ! Immutable.is(nextProps.item, this.props.item);
  },

When any of the data objects change, all nodes from the root down to the modified data object will be re-rendered, and no other nodes are affected.

Scroll to the top of the post for the full example or head over and fork it directly in codepen:

frontend

Building a Generic Content Slider in React.js

React offers a simple way to build generic container components that control the behaviour of their children. In this tutorial we’ll build a generic content slider. The slider should be used in the following manner:

var App = React.createClass({
  render: function() {
    return <ContentSlider>
    <img src="https://pbs.twimg.com/media/CBgCOOQW8AA3OCq.png" />
    <img src="https://pbs.twimg.com/media/CINXyYQWwAA_hlT.jpg" />
    <img src="https://updatesfromthefield.files.wordpress.com/2010/12/lolcat2.jpg" />
    <p>Just remember that death is not the end</p>
    </ContentSlider>
  }
});

So clients of our code don’t have to know anything about sliding images and can just put any content inside. The result should look something like the following (click the buttons to swap photos):

Component Structure

The ContentSlider component has two parts: most of its area is dedicated to presenting an “active” child component, and at the bottom we have a bar with buttons to swap the active component.

Content area needs an outer div with overflow:hidden, and an inner div wide enough to hold all content items. Inside the inner div we’ll put all content items passed as children.

Let’s start with the CSS. A slider-outer class marks our outer div, a slider-inner is the inner div and slider-item is assigned to each content item in the slider. The react-slider__ prefix on the classes helps to prevent class names collisions.

.simple-slider__slider-outer {
  display:block;
  position:relative;
  overflow:hidden;
  width:200px;
  height:200px;  
}

.simple-slider__slider-inner {
  display:block;
  position:absolute;
  transition: all 0.5s;
}

.simple-slider__slider-item {
  display:inline-block;
  width:200px;
  height:200px;  
  vertical-align:top;
}

To leave the example simple I kept all sizes fixed. Most real world code will use percentages or JavaScript for size calculations.

But What About The Children?

You may remember the initial code snippet did not require the content items to have any special classes but rather used regular <img> and <p> tags.  That’s a good idea, but now we have to modify the DOM so elements are inserted with correct classes.

Children are passed to components as a property (this.props.children), and in React we generally don’t modify properties from within a component. When generic containers need to manipulate their children we clone the list using React.cloneElement and modify our cloned items:


    var newChildren = React.Children.map(this.props.children, function(child) {
      return React.cloneElement(child, { className: "simple-slider__slider-item"});
    });

React.cloneElement takes a second parameter with properties passed to the clone. That’s exactly what we need to modify the className in each cloned child.

Now when rendering we’ll just use our new array instead of this.props.children and we have a slider. Here’s the full render method from the code:

  render: function() {
    var childrenCount = React.Children.count(this.props.children);
    var newChildren = React.Children.map(this.props.children, function(child) {
      return React.cloneElement(child, { className: "simple-slider__slider-item"});
    });
    var innerStyle = {
      width: (childrenCount * 200) + "px",
      left: (this.state.activeItem * 200 * -1) + "px"
    };

    return <div>
      <div className="simple-slider__slider-outer" >
        <div className="simple-slider__slider-inner" style={innerStyle} >
          {newChildren}
        </div>
      </div>
        <div className="pager">
            {_.map(this.props.children, function(item,index) {
             return <button onClick={this.setActiveItem.bind(this,index)}>{index}</button>
            }, this)}
        </div>        
      </div>
  }

Generic Slider

While the code above shows how to modify child elements, it’s generally not the best way to implement a generic slider: Your content items may already have existing style and modifying className attributes may corrupt that.

Here’s a more generic version that doesn’t clone the elements but rather wraps them in <div>s: