Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mixin to render in unordered mode (Like sets) #2346

Closed
nmn opened this issue Oct 14, 2014 · 10 comments
Closed

Mixin to render in unordered mode (Like sets) #2346

nmn opened this issue Oct 14, 2014 · 10 comments

Comments

@nmn
Copy link
Contributor

nmn commented Oct 14, 2014

Similar to #2296.

I would love to tell a REACT component : "I don't care in what order you render the elements, just minimize the DOM operations and maintain the elements by key"

In effect: whenever a re-render happens React removes the elements that are no longer there. And new elements are appended to the end. The order is never accounted for.

While ES6 Sets may not be available in many browsers, I would love a similar feature implemented with Objects now.

Explanation:

Right now, when passing an array of children with keys as property, their order is preserved. This is usually the correct behavior.

However, I have projects where I'm using the trick from famo.us to render elements using transform: translate values instead. In this case, all elements have position; absolute; top: 0; left: 0; anyway and their actual order on the DOM does not matter.

The major benefit is that with this trick, it eliminates almost all DOM operations (except updating styles) and any page reflow or paints for great performance.

The problem is that with React maintaining the order of the children, React, reorders the DOM elements anyway.

There are ways where I have to keep track of every render and manually reorder the data to prevent re-renders, but there has got to be a better way.

@syranide
Copy link
Contributor

Order does still matter unless you also force z-index or guarantee that there is never any overlap. I imagine this is best solved with a helper function that retains ordering between a previous and next array rather than add this to core, where we have to consider issues when going from map to set to map. It seems like such an edge case of an edge case, the cost of delegating to a helper function shouldn't be an issue (and probably what React has to do internally anyway).

@nmn
Copy link
Contributor Author

nmn commented Oct 16, 2014

@syranide Totally agree that this is an edge case. And a helper function will let me do this anyway. I just wanted to put it out there, just in case there was an easier way to do this. I haven't read all of React's source, but this seemed like less work to do. So I thought that it might help with performance.
If that is not the case or if this require anything more than a small amount of time to implement, I think we should close this issue.

As a side note: the translate values depend on the order of data that the elements are generated from, so no overlap is actually guaranteed. I've implemented a not very optimized version of this for naman34/react-infinity if you're interested.

@leebyron
Copy link
Contributor

FWIW, ES6 Set is actually ordered. It's ordered by insertion order. It also doesn't have the same problem as Objects where numeric keys mess with the ordering.

> s = new Set()
Set {}
> s.add(1)
Set {1}
> s.add(2)
Set {1, 2}
> s.add(-1)
Set {1, 2, -1}
> s.add(-3)
Set {1, 2, -1, -3}
> for (var x of s.values()){console.log(x)}
1
2
-1
-3
> s.delete(2)
true
> s.add(2)
Set {1, -1, -3, 2}
> for (var x of s.values()){console.log(x)}
1
-1
-3
2

Still a pretty interesting suggestion!

@nmn
Copy link
Contributor Author

nmn commented Oct 21, 2014

Thats very interesting. Anyway I've long agreed to close the issue unless this is trivial to add. There is a workaround to do this anyway.

@leebyron
Copy link
Contributor

I'm curious what your helper function looks like. Care to share a gist?

@nmn
Copy link
Contributor Author

nmn commented Oct 21, 2014

I'm not sure what the best way to do this is yet.

Firstly, I don't think there is a clean way to do this since props.children is immutable, and the map function won't work. So this can't be a simple transform function.

I am working on two possible approaches. Both approaches involve creating a helper React Component that tracks changes by using state and only accepts data with unique keys and another React Component to render that Data.

Approach 1 is to store an object with every possible unique key that has ever been rendered. Then in the render method I can do something like this:

render: function(){
  // I'm assuming data is an array of objects with a unique property called key
  for(var key in this.state.cache){
    this.state.cache[key] = false;
    // I'm setting all values to false, but not removing them, so the order is maintained.
  }
  this.props.data.forEach(function(elem){
      this.state.cache[elem.key] = elem;
  }.bind(this))

  var children = _.map(this.state.cache, function(elem){
    return this.props.component(elem);
  }.bind(this));
  // The elements should now be in an order that existing elements preserve their order and new elements are added at the end.

  return React.DOM.span(null, children);
}

This seems like the better way to do things. This is essentially two very fast loops with a time complexity of O(n). But if an element is removed and re-added, it will be inserted in its original position in the middle. I'm not sure if this has any meaningful performance difference when compared to all new DOM nodes being appended at the end.

The other way would be to always take the current set of elements being rendered and put them in the state object and then do a diffing every time to ensure new elements are added at the end. This would obviously be very similar in terms of the code.

Some things to assume. The data passed through the props has the data related to the order that they were originally sent in. And the component passed in knows how to display them correctly. Which is why the actual order of the DOM doesn't matter at all. (another thing to note here is that browsers are working on an API that would let you update transform values without updating strings on the elements, this would eliminate almost all DOM operations)

@leebyron
Copy link
Contributor

I'm not sure doing this is going to win you anything over the stock reconciliation pass. Have you tried this and actually measured significant performance wins in a real application?

@nmn
Copy link
Contributor Author

nmn commented Oct 21, 2014

Oh. No of course this is making it do more work in javascript, but it does minimise Dom operations which is what I'm trying to do. This also why I thought a mixin that makes react do less work would be more sensible.

im not sure how to test it properly yet.

@leebyron
Copy link
Contributor

If you don't care about the order and all children have keys, it might be more straight forward (and make render more idempotent) if you sorted children by key first. Then it would be very predictable where inserts and removes would occur.

@gaearon
Copy link
Collaborator

gaearon commented Oct 27, 2016

Closing as very old.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants