lustre/element/keyed

Lustre uses something called a virtual DOM to work out what has changed between renders and update the DOM accordingly. That means when you render items in a list, Lustre will walk through the list of items and compare them in order to see if they have changed.

This is often fine but it can be cause problems in cases where we’d like Lustre to reuse existing DOM nodes more efficiently. Consider the example in the quickstart guide: each time the counter is incremented, we insert a new image at the start of the list.

Let’s see how the virtual DOM handles this:

Increment ->                 Increment ->
             <img src="a">   -- update ->  <img src="b">
                             -- insert ->  <img src="a">

Beacuse the virtual DOM compares elements in order, it sees that the first element has its src attribute changed from "a" to "b" and then sees that a new element has been added to the end of the list.

Intuitively, we know that what really happened is that an element was inserted at the front of the list and ideally the first <img /> should be left untouched.

The solution is to assign a unique key to each child element. This gives Lustre enough information to reuse existing DOM nodes and avoid unnecessary updates.

Keyed elements in Lustre work exactly like regular elements, but their child list is a tuple of a unique key and the child itself:

keyed.div([], list.map(model.cats, fn(cat) {
  #(cat.id, html.img([attribute.src(cat.url)]))
}))

Let’s see how the virtual DOM now handles this:

Increment ->                 Increment ->
                             -- insert ->  <img src="b">
             <img href="a">  --        ->  <img src="a">

We can see that Lustre has correctly recognised that the only change is a new image being inserted at the front of the list. The first image is left untouched!

Values

pub fn div(
  attributes: List(Attribute(a)),
  children: List(#(String, Element(a))),
) -> Element(a)
pub fn dl(
  attributes: List(Attribute(a)),
  children: List(#(String, Element(a))),
) -> Element(a)
pub fn element(
  tag: String,
  attributes: List(Attribute(a)),
  children: List(#(String, Element(a))),
) -> Element(a)

Render a keyed element with the given tag. Each child is assigned a unique key, which Lustre uses to identify the element in the DOM. This is useful when a single child can be moved around such as in a to-do list, or when elements are frequently added or removed.

Note: the key for each child must be unique within the list of children, but it doesn’t have to be unique across the whole application. It’s fine to use the same key in different lists.

pub fn fragment(
  children: List(#(String, Element(a))),
) -> Element(a)

Render a keyed fragment. Each child is assigned a unique key, which Lustre uses to identify the element in the DOM. This is useful when a single child can be moved around such as in a to-do list, or when elements are frequently added or removed.

Note: the key for each child must be unique within the list of children, but it doesn’t have to be unique across the whole application. It’s fine to use the same key in different lists.

pub fn namespaced(
  namespace: String,
  tag: String,
  attributes: List(Attribute(a)),
  children: List(#(String, Element(a))),
) -> Element(a)

Render a keyed element with the given namespace and tag. Each child is assigned a unique key, which Lustre uses to identify the element in the DOM. This is useful when a single child can be moved around such as in a to-do list, or when elements are frequently added or removed.

Note: the key for each child must be unique within the list of children, but it doesn’t have to be unique across the whole application. It’s fine to use the same key in different lists.

pub fn ol(
  attributes: List(Attribute(a)),
  children: List(#(String, Element(a))),
) -> Element(a)
pub fn tbody(
  attributes: List(Attribute(a)),
  children: List(#(String, Element(a))),
) -> Element(a)
pub fn ul(
  attributes: List(Attribute(a)),
  children: List(#(String, Element(a))),
) -> Element(a)
Search Document