question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Computed properties/selectors in array items

See original GitHub issue

An 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:closed
  • Created 7 years ago
  • Comments:7 (2 by maintainers)

github_iconTop GitHub Comments

1reaction
toranbcommented, Jul 14, 2016

@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

1reaction
brettburleycommented, Apr 30, 2016

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 a fullName property to users) that I was testing:

const stateToComputed = (state) => ({
  users: state.users.map((user) => Object.assign({
    fullName: `${user.firstName} ${user.lastName}`
  }, user))
});

I’m rendering users in an each with a component:

{{each users as |user|}}
  {{user-list-item user=user}}
{{/each}}

Here are the three variations of rendering I tried:

  1. {{#each users as |user|}}: Standard each helper.
  2. {{#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.
  3. {{#each memoizedUsers as |user|}}: Standard each helper iterating over a memoized version of the array. I created a simple memoizedMap function which will perform a map 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 a render-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 the user.fullName property “changing”.

Here are the results I saw: olhvq6vghz

For those three variations outlined above, here are the results I saw:

  1. New components were instantiated when a new user was added. This is the most expensive outcome, as the old component has to be taken down, a new one set up, and completely rendered.
  2. Old components were re-used and the only part that was re-rendered were places relying on user.fullName (likely because the user object itself changed).
  3. Old components were re-used and nothing had to be re-rendered. Only the newly added user had to be rendered.

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.

Read more comments on GitHub >

github_iconTop Results From Across the Web

JavaScript computed properties - arrays - Stack Overflow
If the array item exists as a key in the accumulator, then return its value; If the array item does not exist as...
Read more >
Using Vue computed properties - Learn web development
In this article we'll add a counter that displays the number of completed todo items, using a feature of Vue called computed properties....
Read more >
Observers and computed properties - Polymer Project
Computed properties are virtual properties based on one or more pieces of the element's data. A computed property is generated by a computing...
Read more >
Computed property for array element - Get Help - Vue Forum
This is a common practice to use a computed property to return a new result from an original source.
Read more >
Select Page Elements | Basic Guides - TestCafe
When you pass selector properties instead of values, TestCafe enables Smart ... In addition, you can obtain the component props, state and computed...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found