14

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.

Paddy Mann
  • 1,169
  • 12
  • 18
  • `shouldComponentUpdate` has to do with if your React component should re-render, not with the logic of updating/editing the data in your store. Show us the code you use to update the store. – Matthew Herbst Jan 24 '16 at 22:18
  • How are you passing your data from the store into your tree component? How is your tree component structured - is it all a single component, is it nested components, nested connected components... ? – markerikson Jan 25 '16 at 00:55

2 Answers2

20

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/
Dan Abramov
  • 264,556
  • 84
  • 409
  • 511
  • Thanks so much for the example - I'd read https://github.com/rackt/redux/issues/815 but not quite got my head around how it'd work in practice. I'll give it a shot this evening and expect it'll sort the problem :) – Paddy Mann Jan 25 '16 at 07:28
  • 1
    This has sufficiently solved my issue for our MVP, so accepting the solution. Great to see lots of positive feedback on GitHub too. Thanks Dan :) – Paddy Mann Jan 25 '16 at 18:52
  • 2
    The links are 404ing. [Is this the link now?](https://github.com/reactjs/redux/tree/master/examples/tree-view) – NickL Dec 10 '16 at 16:46
  • 1
    Thanks for making the repo, I think that the link suggested by @NickL is the right one but it would be good to see a description of the overall process here too, to avoid a link-only answer. – Tom Fenech Aug 01 '17 at 07:34
  • I believe this was the link, https://github.com/reduxjs/redux/pull/1269 – dannyshaw Jan 04 '19 at 04:06
7

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.

Community
  • 1
  • 1
Sebastien Lorber
  • 89,644
  • 67
  • 288
  • 419
  • Thanks for the info. Just confirming that HOC means Higher Order Component? Thanks. – AJP Jan 25 '16 at 12:30
  • We could get in the thousands of nodes territory in future, and as our tree expands both horizontally and vertically an infinity solution is going to be tricky! Due to be limited time with React, I'm struggling to fully understand your proposed solution, so would love to see a gist of how you'd apply your custom connect to Dan's solution. – Paddy Mann Jan 25 '16 at 16:37
  • 1
    @PaddyMann I'll try to come back with a solution. I'd like to make a PR to react-redux first so that it's easy to do without having to duplicate most of react-redux's connect code. – Sebastien Lorber Jan 25 '16 at 17:09
  • 1
    Fantastic @SebastienLorber. Fancy creating an issue on GitHub so I/others can follow progress? (I ask as you'll probably explain the issue better, and I don't want to overstate your intentions) – Paddy Mann Jan 25 '16 at 18:57