Lit-html: repeat directive documentation

Created on 30 Nov 2018  路  15Comments  路  Source: Polymer/lit-html

I learned from @kenchris that the repeat directive should only be used if we intend to "re-order" the items and that in other cases we can just use .map

I'm opening this issue to make the documentation with that regards clearer:
template-reference#repeat

I'd be happy to send a PR, but I'm not sure how to word it in a clearer way, so I'm opening an issue first to get feedback

docs Medium

Most helpful comment

Some content I posted on Slack earlier:

The core difference between map and repeat is that repeat maintains the state of rendered nodes. If you are shuffling components around that have state, like inputs or other web components, and you are not setting that state explicitly, map will cause problems, where repeat will not.

A simple example of this is a list of items with checkboxes. Unless the checked state is explicitly set in the template, using map will cause the checks not to re-order when the input array changes.

If your nodes do not have any kind of state, or your array is append-only, it is very likely that map is better than repeat performance-wise.

Maintaining the order association is an additional requirement, and maintaining it comes at a cost.

All 15 comments

cc @arthurevans

maybe make it clearer that there's an overhead of using it, to make it clearer when it's suitable

Some content I posted on Slack earlier:

The core difference between map and repeat is that repeat maintains the state of rendered nodes. If you are shuffling components around that have state, like inputs or other web components, and you are not setting that state explicitly, map will cause problems, where repeat will not.

A simple example of this is a list of items with checkboxes. Unless the checked state is explicitly set in the template, using map will cause the checks not to re-order when the input array changes.

If your nodes do not have any kind of state, or your array is append-only, it is very likely that map is better than repeat performance-wise.

Maintaining the order association is an additional requirement, and maintaining it comes at a cost.

Thanks @ruphin ... I added some generalities about performance in the docs:

https://lit-html.polymer-project.org/guide/writing-templates#repeating-templates-with-the-repeat-directive

However, I missed that point about stateful components, which is an important one.

@arthurevans Nice! I think there may be a typo in the employee example though:

const employeeList = (employees) => html` 
  <ul> 
    ${repeat(employees, (employee) => employee.id, (employee) =>
        html`<li>employee.familyName, employee.givenName</li>`}
  </ul>`

Missing closing ) before the closing }

@jorenbroekema, @FluorescentHallucinogen already put out a PR to address that typo. #698

I am observing something with the use of repeat, and I am not sure if it is a bug:

From the above example, if I do a re-sort of the employeeList, then modify the employees array (say, by deleting an item), and passing it back to the repeat directive, nothing happens, i.e. nothing gets deleted from the UI.

Does repeat behave correctly when elements are subsequently removed/added from the array by changing the array contents?

@ernsheong changing the items of the array doesn't necessary trigger a render. You can call this.requestUpdate() after you have changed the order/removed items

@ernsheong if you change the reference but not the value, a re-render is not triggered. This is a common issue with arrays and objects that people face.

You could do what pshihn suggested, or do the following, for example in a setter:

render() {
  return html`
    <h1>Hello world!</h1>
    <ul>
      ${this.employeeList.map(employee => html`<li>${employee}</li>`)}
    </ul>
  `;
}

set employeeList (value) {
  this._employeeList = value;
}

or to simply delete the first item without needing a setter like that

const [myOldItem, ...rest];
this.employeeList = [...rest];

Or adding

this.employeeList = [...this.employeeList, 'Joe'];

Thanks all, I am aware of values vs references. I was using https://github.com/SortableJS/Sortable, and I guess repeat (and lit) is just too fragile to have another library mess with the DOM at the same time.

For drag-and-drop my current conclusion is I have no choice but to handcraft down to the Drag-and-Drop API level following the example given here https://github.com/Polymer/lit-html/issues/460#issuecomment-420250615 in order to eliminate the weirdest of bugs.

I'm running into this problem: https://github.com/Polymer/lit-html/issues/877#issuecomment-511003245

I have tried using the repeat directive instead of map, but the values of my textareas are still messed up.

@justinfagnani

My own confusion with repeat and what brought me to this issue is that I think the word 'repeat' does not match its purpose. It's responsible for 1) reordering elements in a list and 2) mutating a list by adding/removing elements. I understand that both repeat and Array.map don't toss out the underlying DOM elements but repeat retains their state. Why don't we call it 'list' instead of repeat as it is really a stateful list structure.

I think that would clear a lot of confusion, at least for me.

Day 1 with lit-html.

Technically, Array.prototype.map also retains the state of the DOM elements.

The difference between Array.prototype.map and repeat is that repeat is _keyed_. In other words, in repeat each DOM element is associated with a unique _key_ in the source array item.

Practically this means repeat has the following behaviours:

  • If the source array re-arranges the items, the DOM elements will also re-arrange in the same way.
  • If an item in the source array is deleted and replaced with an item with a different key, the associated DOM element will also be deleted and replaced with a new one.

In these cases Array.prototype.map would retain all the original DOM elements (and their state) in the original order. It will only re-assign the dynamic values if appropriate.


Note that by default the _key_ in repeat is the index in the array, which effectively makes repeat behave exactly like a more computationally expensive Array.prototype.map. The key function is an optional argument in the repeat directive, but you should never use repeat without a key function.

@ruphin

You stated:

In these cases Array.prototype.map would retain all the original DOM elements (and their state) in the original order. It will only re-assign the dynamic values if appropriate.

But the docs seem to say something else:

In most cases, using loops or Array.map is an efficient way to build repeating templates. However, if you want to reorder a large list, or mutate it by adding and removing individual entries, this approach can involve recreating a large number of DOM nodes.

Can you please clarify?

I would think that due to absence of keys in case of Array.map the original DOM elements will have their state (e.g. value of input element) reset and new values are assigned.

Regardless, why do the docs say that Array.map would recreate the DOM nodes? I thought lit-html maintains a finite pool of DOM nodes that it recycles (to avoid excessive GC and element creation overhead)

Hmm.

Note to self: when updating repeat docs should also add a note about this issue: https://github.com/Polymer/lit-html/issues/1007 (which is related to directives in general, not repeat, although there's also a repeat-specific workaround).

Was this page helpful?
0 / 5 - 0 ratings

Related issues

gkjohnson picture gkjohnson  路  5Comments

justinfagnani picture justinfagnani  路  3Comments

Christian24 picture Christian24  路  4Comments

pietmichal picture pietmichal  路  4Comments

RyanAfrish7 picture RyanAfrish7  路  4Comments