Skip to content

Key functions for each/each-in - #321

Closed
mmun wants to merge 1 commit into
masterfrom
key-functions
Closed

mmun wants to merge 1 commit into
masterfrom
key-functions

Conversation

@mmun

@mmun mmun commented Mar 24, 2018 •

Copy link
Copy Markdown
Member

Rendered

Closes #306

@mmun mmun changed the title Key Functions for each/each-in Key functions for each/each-in Mar 24, 2018

If `key` is a function, then Glimmer's diffing algorithm will call this function to determine the item's key.

For `each`, the function would be called with arguments `keyFn(item, index)`.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seems strange because in an array the index is sort of like that key, which means in one case we're passing key, value and value, key in the other case 🤔

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The arguments are intended to match the order of the block params for those helpers.

For `each`, the function would be called with arguments `keyFn(item, index)`.
For `each-in`, the function would be called with arguments `keyFn(key, value)`.

The key function should not be invoked with a `this`.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what does that mean?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That glimmer should call the key function as keyFn(...args) as not to bind this to any particular value.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but with #308 would we then need to use key=this.someUnboundFunction? would seem strange if that was actually not bound to this in that case 🤔

@mmun mmun Mar 25, 2018 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's strange. For example,

{{some-component onClick=this.someUnboundFunction}}

is also not bound. This is generally true (albeit a bit annoying!) in Handlebars today. You have to write

{{some-component onClick=(action this.someUnboundFunction)}}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, true, I guess passing around this.someUnboundFunction in JS also doesn't have to mean that it's bound...

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The key function should not be invoked with a this.

I think due to #308 we should rephrase this as something like "The key function will not be bound to this automatically", since in the future we will have to call it with this if #308 gets merged.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 That phrasing is better.

@rwjblue rwjblue left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left a couple of small tweak suggestions inline, but I'm definitely 👍 on this...


## Motivation

The `key` argument to `each` and `each-in` determines the key used in the Glimmer diffing algorithm. Here is a description of the currently accepted values?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ending question mark seems pretty out of place here. Is this a description of the current support, or is the RFC asking for clarification?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I don't know what I was thinking?


## How we teach this

This feature just needs to be documented in the API docs and possibly the guides. We may want to reframe the existing (string-based) key documentation to be explained as shorthands for key functions.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer to remove "just" here, otherwise 👍


`key` argument | `each` key function | `each-in` key function
---------------|------------------------------------|-----------------------------------
`@identity` | `(item, index) => guidFor(item)` | `(key, value) => guidFor(value)`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One possible suggestion I have is to reverse the order of the arguments in one of the functions. I believe that the value should be in the same position in both.

key argument each key function each-in key function
@identity (item, index) => guidFor(item) (value, key) => guidFor(value)

or

key argument each key function each-in key function
@identity (index, item) => guidFor(item) (key, value) => guidFor(value)

I have the feeling that it would make the table simpler:

key argument key function for both
@identity (_, value) => guidFor(value)
@index/@key (ref, _) => ref
other strings (_, value) => get(value, path)

However I also see the convenience on {{each}} taking the arguments in the same order that .forEach and {{each-in}} having them as (key, value)

@mmun mmun Mar 31, 2018 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That order seems plausible since it's the order you get when calling the Array.prototype.entries and Map.prototype.entries iterators, however I think it still makes more sense to mirror the block param order.

Additionally, I don't like promoting array index to the first slot because you almost never want to use it as a key (unless you know the whole #each block is stateless and you know that no one will ever add a stateful component/helper into it in the future, see https://medium.com/@robinpokorny/index-as-a-key-is-an-anti-pattern-e0349aece318).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I'm not really opposed to the proposed order, I just thought that this one presented a "chance for unification", in case that was desirable or simplified the implementation in any form. I'm happy either way.

@bgentry

bgentry commented May 23, 2018

Copy link
Copy Markdown

I'm definitely a 👍 on this.

With ember-apollo-client, we're always just passing either POJOs wrapped in an EmberObject or NativeArrays. Out-of-the-box, the arrays do not work cleanly with Ember unless you pass key="id" or similar to avoid re-rendering the components for the entire list. That's because the POJOs themselves do not have the same guid, so Ember thinks they're different.

However, users typically already have to tell Apollo how to get a unique identifier from their objects by configuring a dataIdForObject() function. That gets used for cache normalization purposes and typically just defaults to obj => obj.id for apps with globally unique IDs. I would love to use this same function to make it easier for users to get correct behavior on re-renders. The suggestion from @mmun was to build an {{#apollo-each}} helper that gets AST transformed to reference the key function. This RFC would make that possible.

The proposal itself seems straightforward and I would love to see it built.

@wagenet

wagenet commented Jul 23, 2022

Copy link
Copy Markdown
Member

Seem like there has been some solid interest for this. I'm going to see if we can get some better review and a proper yay or nay on it.

@wagenet wagenet added S-Proposed In the Proposed Stage S-Exploring In the Exploring RFC Stage and removed S-Proposed In the Proposed Stage labels Dec 2, 2022
@wagenet wagenet added the FCP to close The core-team that owns this RFC has moved that this RFC be closed. label Jan 27, 2023
@wagenet

wagenet commented Jan 27, 2023

Copy link
Copy Markdown
Member

I'm moving this FCP to close since it's had virtually no activity in the last few years. If someone wants to pick this up and make sure the RFC is up to date, we can re-open.

@ef4 ef4 closed this Feb 3, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

FCP to close The core-team that owns this RFC has moved that this RFC be closed. Needs Champion S-Exploring In the Exploring RFC Stage T-framework RFCs that impact the ember.js library

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Allow passing a function as the key for {{each}}

8 participants