Skip to main content
Version: Next

Lists

Iterators

There are 4 ways to build HTML from iterators:

The main approach is to use for loops, the same for loops that already exist in Rust, with one key difference: unlike standard for loops which can't return anything, for loops in html! are converted to a list of nodes.

use yew::prelude::*;

html! {
for i in 0 .. 10 {
<span>{i}</span>
}
};

for loop bodies accept Rust statements before the html children. Any terminated statement works: let bindings, expression statements ending in ;, item definitions (fn, struct, use, ...), and macro invocations with ;.

use yew::prelude::*;

html! {
for item in items {
let label = format!("{}: {}", item.id, item.name);
let class = if item.active { "active" } else { "inactive" };
<div class={class}>{label}</div>
}
};

break and continue work as in ordinary Rust loops and affect the emitted node list. continue skips the current iteration without producing a node, break stops iteration early:

use yew::prelude::*;

html! {
for i in 0..10 {
if i % 2 == 0 {
continue
}
if i > 7 {
break
}
<span>{i}</span>
}
};

break and continue are also available directly as match arms, whether braced or unbraced:

use yew::prelude::*;

html! {
for i in 0..10 {
match i {
0 => continue,
8.. => break,
_ => <span>{i}</span>,
}
}
};

Loop labels are supported. break 'label and continue 'label target a labeled loop defined in the surrounding Rust code, letting you exit an enclosing loop from inside the macro:

use yew::prelude::*;

let mut rendered = Vec::new();
'outer: for section in sections {
rendered.push(html! {
for item in section.items {
if should_stop(&item) {
break 'outer;
}
<span>{item.name}</span>
}
});
}
Qualified paths

A bare qualified-path expression like <Type>::method(...) collides with the <Type> element open tag and is rejected. Two ways out:

  • Add a trailing ; and it becomes an expression statement in the preamble: <Type>::method(...);. The return value is discarded.
  • Wrap it in {...} to use the return value as a node: { <Type>::method(...) }.
Imperative loops in the preamble

Block-like Rust expressions (for, while, loop, {...}) auto-terminate as statements in Rust grammar - a trailing ; is not folded into the statement. A bare imperative loop in a preamble:

html! {
for item in items.iter() {
let mut by_han = BTreeMap::new();
for src in &item.sources { // imperative side-effect loop
by_han.entry(src.han_nom.clone()).or_default().push(src);
}
<div>{render(by_han)}</div>
}
}

is parsed as html-for (emitting children) instead of as a Rust statement, and its body's () value cannot be converted to a VNode. Bind it with let _ = ...; so the parser sees a Stmt::Local:

html! {
for item in items.iter() {
let mut by_han = BTreeMap::new();
let _ = for src in &item.sources {
by_han.entry(src.han_nom.clone()).or_default().push(src);
};
<div>{render(by_han)}</div>
}
}

The same applies to imperative while, loop, and bare-block {...} statements. match and if are not subject to this collision because their html-control-flow forms naturally accept the same body shapes as their imperative use.

Keyed lists

A keyed list is an optimized list that has keys on all children. key is a special prop provided by Yew that gives an HTML element or component a unique identifier that is used for optimization purposes inside Yew.

caution

Key has to be unique only in each list, in contrast to the global uniqueness of HTML ids. It must not depend on the order of the list.

It is always recommended to add keys to lists.

Keys can be added by passing a unique String, str or integer to the special key prop:

use yew::prelude::*;

let names = vec!["Sam","Bob","Ray"]

html! {
<div id="introductions">
{
names.into_iter().map(|name| {
html!{<div key={name}>{ format!("Hello, I'am {}!",name) }</div>}
}).collect::<Html>()
}
</div>
};

Performance increases

We have Keyed list example that lets you test the performance improvements, but here is a rough rundown:

  1. Go to Keyed list hosted demo
  2. Add 500 elements.
  3. Disable keys.
  4. Reverse the list.
  5. Look at "The last rendering took Xms" (At the time of writing this it was ~60ms)
  6. Enable keys.
  7. Reverse the list.
  8. Look at "The last rendering took Xms" (At the time of writing this it was ~30ms)

So just at the time of writing this, for 500 components it is a 2x increase of speed.

Detailed explanation

Usually, you just need a key on every list item when you iterate and the order of data can change. It's used to speed up the reconciliation process when re-rendering the list.

Without keys, assume you iterate through ["bob", "sam", "rob"], ending up with the HTML:

<div id="bob">My name is Bob</div>
<div id="sam">My name is Sam</div>
<div id="rob">My name is rob</div>

Then on the next render, if your list changed to ["bob", "rob"], yew could delete the element with id="rob" and update id="sam" to be id="rob"

If you had added a key to each element, the initial HTML would be the same, but after the render with the modified list, ["bob", "rob"], yew would just delete the second HTML element and leave the rest untouched since it can use the keys to associate them.

If you ever encounter a bug/"feature" where you switch from one component to another but both have a div as the highest rendered element. Yew reuses the rendered HTML div in those cases as an optimization. If you need that div to be recreated instead of reused, then you can add different keys and they will not be reused.

Further reading