packages/lit-dev-content/site/tutorials/content/working-with-lists/05.md
When using the map() directive or just providing an iterable within the
template, Lit creates a list of DOM nodes and updates them in the iteration
order, efficiently reusing any previously rendered nodes. This technique
should only be used when Lit controls all of the rendered state (as is commonly
the case).
However, when relying instead on the DOM nodes to maintain state that isn't
controlled by Lit, unexpected behavior can occur as the example below will
demonstrate. In this case, use the repeat() directive instead.
Take a look at this example where each task item is rendered as a <li> with a
checkbox using the map() directive. There are buttons to sort the list items
in ascending or descending alphabetical order. The label text is provided by a
template expression but there is no explicit component state to hold whether
the item is checked or not.
Check one of the items and change the sort order. Notice that the item's label text changes but not the position of the checked box. This is because the checkbox state, which is not controlled by Lit, stays with the DOM nodes. During update, Lit only updates the value of the text node holding the label without reordering the DOM nodes.
Use the repeat() directive to keep the list item and checkbox tied to the
specific item of the array. Using this directive with a key function lets Lit
maintain the key-to-DOM association between updates by moving the DOM nodes
when required.
Begin by importing the repeat() directive.
{% switchable-sample %}
// my-element.ts
import {repeat} from 'lit/directives/repeat.js';
// my-element.js
import {repeat} from 'lit/directives/repeat.js';
{% endswitchable-sample %}
Use repeat() in place of the map() directive, providing the iterable, a key
function that returns a unique identifier for a particular item, and the
template to render for each item.
{% switchable-sample %}
// my-element.ts
render() {
return html`
⋮
<ul>
${repeat(
this.tasks,
(task) => task.id,
(task) => html`
<li>
<label><input type="checkbox" />${task.label} (${task.id})</label>
</li>
`
)}
</ul>
`;
}
// my-element.js
render() {
return html`
⋮
<ul>
${repeat(
this.tasks,
(task) => task.id,
(task) => html`
<li>
<label><input type="checkbox" />${task.label} (${task.id})</label>
</li>
`
)}
</ul>
`;
}
{% endswitchable-sample %}
Confirm that this is working correctly by checking an item and changing the sort order. The check mark should now move to stay next to its original label.
{% aside "positive" %}
The key function provided must return a unique key for an item.
If you omit the key function—or provide a key function that simply returns the index—repeat() behaves exactly like map(). For more information, see the repeat
documentation. Also see
When to use map or repeat
for guidance on deciding when to use one or the other.
{% endaside %}