Computed properties/selectors in array items
See original GitHub issueAn interesting problem I’ve run into is rendering arrays from the store. Take, for example:
var stateToComputed = (state) => {
return {
users: state.users.map((user) => ({
fullName: `${user.firstName} ${user.lastName}`
});
};
};
export default connect(stateToComputed)(Ember.Component.extend({
layout: hbs`
{{#each users as |user|}}
{{user-item user=user}}
{{/each}}
`
}));
Any time a user is added or removed to the state tree, stateToComputed
must be recomputed, which produces an array of all new objects due to the map. Since all of the objects are new, Ember must re-render the complete loop and all of the user-item
components in it.
In current releases of Ember 2.x, even if you set users
to a new array, it will only re-render items in the each that are actual new objects. For example, this.set('users', [...this.get('users'), User.create()])
would only require the rendering of the single new user-item
component for the new user.
I have played with using https://github.com/reactjs/reselect along with ember-redux
, however this doesn’t solve the issue for adding/removing items to arrays, since reselect
will recompute since the array object itself is new, and therefore Ember will have to re-render the entire array and every item within it.
Is this an issue you’ve run into? Any other ideas for approaches to computed properties for items within an array? I seem to come across this pattern of rendering data from an array relatively often.
Issue Analytics
- State:
- Created 7 years ago
- Comments:7 (2 by maintainers)
Top GitHub Comments
@brettburley the last few days I’ve been thinking “how can we make this issue something action-able” and I was curious if we could implement the glimmer 2 demo (from the 2016 EmberConf keynote) with ember-redux to see how it performs. I found a demos directory but I couldn’t find the raw source code for that react/ember app. You have any available bandwidth in the next 30 days to help hunt down the source for that app w/ tons of components so we could measure the effort required to build it with ember-redux?
https://github.com/tildeio/glimmer/tree/master/demos
https://github.com/emberjs/ember.js/pull/10501 is a relatively good description of Glimmer (as it works now?). It’s based on watching values and updating only the necessary parts of the DOM, rather than diff’ing a virtual DOM. Admittedly, I don’t fully understand the details of Glimmer or its roadmap with references, validators, etc.
I made a demo app in Ember 2.5.1 to play around with this a bit more. Here’s the primary
stateToComputed
type of transformation (adding afullName
property tousers
) that I was testing:I’m rendering users in an each with a component:
Here are the three variations of rendering I tried:
{{#each users as |user|}}
: Standard each helper.{{#each users key="id" as |user|}}
: Standard each helper using the key attribute. This serves as a hint of when the object is the same so that Ember can use the same each instance.{{#each memoizedUsers as |user|}}
: Standard each helper iterating over a memoized version of the array. I created a simplememoizedMap
function which will perform amap
on an array of objects, but re-use previous results for objects that have already been transformed. This way, the result objects in the mapped array will be the same if the input objects are the same.To instrument what was being re-rendered, I added a global counter on
init
of the component. I also created arender-counter
Ember helper which uses a global counter as well, this way I could count how many times Ember is calling this helper. I dropped this in as{{render-counter}}
to see when an entire component is being re-rendered, and dropped it in with{{render-counter user.fullName}}
to see when Ember had to re-render due to theuser.fullName
property “changing”.Here are the results I saw:
For those three variations outlined above, here are the results I saw:
user.fullName
(likely because the user object itself changed).The result in the last case would seem to be the most ideal outcome. However, in order to achieve that, I had to create and use a memoized array map helper. I haven’t come across this pattern in React apps (even though they do use memoization via
reselect
for simpler transformations), but it might be that they don’t need that incremental performance since they’re diff’ing at the DOM level.