lit / lit

Lit is a simple library for building fast, lightweight web components.
https://lit.dev
BSD 3-Clause "New" or "Revised" License
18.54k stars 914 forks source link

repeat directive documentation #672

Closed jadjoubran closed 3 years ago

jadjoubran commented 5 years ago

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

justinfagnani commented 5 years ago

cc @arthurevans

jadjoubran commented 5 years ago

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

ruphin commented 5 years ago

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.

arthurevans commented 5 years ago

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.

jorenbroekema commented 5 years ago

@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 }

stramel commented 5 years ago

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

ernsheong commented 5 years ago

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?

pshihn commented 5 years ago

@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

jorenbroekema commented 5 years ago

@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'];
ernsheong commented 5 years ago

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.

lastmjs commented 5 years ago

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.

innerop commented 5 years ago

@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.

ruphin commented 5 years ago

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:

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.

innerop commented 5 years ago

@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.

arthurevans commented 4 years ago

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).

justinfagnani commented 3 years ago

The 2.0 site does a good job with this.