Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Performance issues with a tree structure and shouldComponentUpdate in React / Redux

I'm fairly new to React, Redux and ImmutableJS, and have run into some performance issues.

I have a large tree structure of data, which I'm currently storing as a flat list in this structure:

new Map({
  1: new Node({
    id: 1,
    text: 'Root',
    children: [2,3]
  }),
  2: new Node({
    id: 2,
    text: 'Child 1',
    children: [4]
  }),
  3: new Node({
    id: 3,
    text: 'Child 2',
    children: []
  }),
  4: new Node({
    id: 4,
    text: 'Child of child 1',
    children: []
  })
});

While structuring it as a flat list makes updating nodes easy, I'm finding that interaction gets sluggish as the tree grows. Interaction includes being able to select one or more nodes, toggle the visibility of their child nodes, update the text, and so on. It looks like a key reason for the the sluggish UI is that the entire tree is being redrawn for each interaction.

I want to use shouldComponentUpdate such that if I update node 3, nodes 2 and 4 do not update. This would be easy if the data was stored as a tree (I could simply check whether this.props !== nextProps), but as the data is stored in a flat list the check would be substantially more complex.

How should I being storing the data and using shouldComponentUpdate (or other methods) to support a smooth UI with hundreds or thousands of tree nodes?

Edit

I've been connecting the store at the top level, and then having to pass the entire store down to subcomponents.

My structure is:

<NodeTree> 
  <NodeBranch>
    <Node>{{text}}</Node>
    <NodeTree>...</NodeTree>
  </NodeBranch>
  <NodeBranch>
    <Node>{{text}}</Node>
    <NodeTree>...</NodeTree>
  </NodeBranch>
  ...
</NodeTree>

The <Node> can do a simple check with shouldComponentUpdate to see if the title has changed, but I have no similar solution to use on the <NodeTree> or <NodeBranch> given the recursive nature of the tree.

It looks like the best solution (thanks @Dan Abramov) would be to connect each <NodeBranch>, rather just connecting at the top level. I'll be testing this this evening.

like image 572
Paddy Mann Avatar asked Jan 24 '16 21:01

Paddy Mann


People also ask

Does Redux affect performance?

In fact, React Redux in particular is heavily optimized to cut down on unnecessary re-renders, and React-Redux v5 shows noticeable improvements over earlier versions. Redux may not be as efficient out of the box when compared to other libraries.

What makes Reactjs performance faster virtual DOM or original DOM?

Once the difference is calculated the real DOM will update only the new components that have actually changed. This is called Reconciliation. Virtual DOM is faster and more effective than Real DOM as it just focuses on the updated components instead of updating the entire DOM.

Does Redux prevent prop drilling?

​ Both Redux and React's Context API deal with "prop drilling". That said, they both allow you to pass data without having to pass the props through multiple layers of components.


2 Answers

I just added a new example showing just that.
You can run it like this:

git clone https://github.com/rackt/redux.git

cd redux/examples/tree-view
npm install
npm start

open http://localhost:3000/
like image 95
Dan Abramov Avatar answered Oct 30 '22 23:10

Dan Abramov


Dan Abramov solution is fine in 99% of usual cases.

But the computation time required on each increment is proportional O(n) to the number of nodes in the tree , because each connected node HOC has to execute a little bit of code (like identity comparison...). This code is pretty fast to execute however.

If you test Dan Abramov solution, with 10k nodes on my laptop I start to see some latency when trying to increment the counter (like 100ms latency). It is probably much worse on cheap mobile devices.

One could argue that you should not try to render 10k items in the DOM at once, and I totally agree with that, but if you really want to display a lot of items and connect them it is not that simple.

If you want to turn this O(n) to O(1), then you can implement your own connect system where the HOC's (Higher Order Component) subscription is only triggered when the underlying counter is updated, instead of all HOC's subscriptions.

I've covered how to do so in this answer.

I created an issue on react-redux so that connect becomes more flexible and permit easily to customize the store's subscription through options. The idea is that you should be able to reuse connect code and subscribe efficiently to state slices changes instead of global state changes (ie store.subscribe()).

const mapStateToProps = (state,props) => {node: selectNodeById(state,props.nodeId)}

const connectOptions = {
   doSubscribe: (store,props) => store.subscribeNode(props.nodeId)
}

connect(mapStateToProps, undefined,connectOptions)(ComponentToConnect)

It is still your own responsability to create a store enhancer with a subscribeNode method.

like image 32
Sebastien Lorber Avatar answered Oct 30 '22 22:10

Sebastien Lorber