Things I learned while trying to make a fast Treeview (in React)

tl;dr:

  • Deeper DOM trees render slower (duh)
  • I can have views render faster by having a shallower DOM and mimic the nested structure with CSS
  • Breaking away from the mental representation of layouts could be useful

Say we’re to create a treeview, given some data that looks like the following:

const data = {
name: 'Node',
children: [
{name: 'Node'},
{name: 'Node', children: [{name: 'Node'}]}
]
}

Following my intuition

Boxes inside of boxes inside of boxes, eh

Whenever I visit any website, I picture the underlying layout of it inside my head almost instinctively. It probably comes from having spent countless hours writing HTML and CSS, and I’m sure this is true for all front-end developers. So cognitively, the path of least resistance is to nest child nodes inside parent nodes.

The React Component below follows that approach. It recursively references itself with a check to break the recursion when a node has no children. It’s quite straightforward and easy to understand, and I found that most solutions I could find on the internet were doing something similar.

import React from 'react';
class Tree extends React.Component {
render() {
const data = this.props.data;
if (!data) return null;
return (
<ul>
{data.map((item) => {
return (<li>
{item.name}
<Tree data={item.children} />
</li>)
})}
</ul>
)
}
}

The problem was, my dataset included about ~700 nodes and the component took about 300ms on average to render. Spending a good third of a second for just rendering a Treeview was not ideal, so I sought a better way.

Maybe the path of least resistance is not the best path

I knew that anything related to HTML and CSS is much slower than any JavaScript operations (except async stuff). So I was quite confident that the problem wasn’t my JavaScript. The simple fact was that there were too many elements to render.

So my next line of thinking was, how do I reduce the number of elements? I soon realized there isn’t much I can do about the total number of elements, and I had to think of something rather unusual. After a few too many cups of coffee and a healthy amount of self-pity, I finally came up with an idea (I’m sure it’s not a novel idea at all).

“But wait, I think I read somewhere that deeply nested elements take longer to render. Makes sense. So can I un-nest theses nodes and fake the nested appearance with CSS?”

It was worth a try. So first, I passed the nested object into a recursive function to get a flatter representation of the data as an array, that would look like the following:

const data = [{
name: 'Node',
depth: 1,
...
},{
name: 'Node',
depth: 2,
...
},{
name: 'Node',
depth: 2,
...
},{
name: 'Node',
depth: 3,
...
}];

Then I created a React Component that takes in that array and renders all nodes as siblings. The component adds styles to each node respective to its depth value, to visually mimic the nested structure of the nodes.

class Tree extends React.Component {
render() {
const data = this.props.data;
return (
<ul>
{data.map((item, i) => {
const styleObj = {paddingLeft: (item.depth - 1) * 20};
return (
<li key={i} style={styleObj}>{item.name}</li>
)
})}
</ul>
)
}
}

To my satisfaction, the second solution did prove to be much faster. On average, it rendered the entire treeview about 3 times faster than the first solution. Below are codepens that you can test out yourself:

Caveat

Although the flat DOM does render much faster, its actual usefulness is rather questionable. That is, I don’t think there would be many, if at all, web applications that need a treeview of over 700 nodes to be rendered all at once on pageload. In most cases, the treeview would probably only show the top-level nodes and have child nodes expandable through click events. So the total number of nodes that has to be rendered on pageload would not be anything close to 700, and the 3x increase in render speed becomes a difference of a few milliseconds. Also, writing click event handlers is slightly more complicated and less intuitive with a flat DOM.

All in all, it could be hard to justify having to move away from the familiar nested DOM structure that is much more intuitive and easy to work with.

However, I’ve learned that working with a flat array (even if the items represent data points that are nested within each other) can provide greater benefits outside of being able to render DOM nodes a little faster. I plan on discussing such benefits in my next post.

Thank you for reading.

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.