List Rendering Patterns
Project Code:https://github.com/euv-dev/euv
Rendering lists is one of the most common tasks in UI development. euv provides powerful patterns for rendering dynamic lists, from simple iterations to complex keyed lists with reactive updates. This article explores the for loop in the html! macro, keyed diffing, and various list manipulation patterns.
Basic List Rendering with for
The html! macro supports for loops to iterate over collections and render a node for each item:
use euv::*;
fn app() -> VirtualNode {
let items = vec!["Apple", "Banana", "Cherry"];
html! {
ul {
for item in items {
li { item }
}
}
}
}
mount("#app", app);
This iterates over the items vector and renders an <li> element for each string. The for loop in the html! macro works with any iterable expression.
Rendering Lists from Signals
In practice, lists usually come from reactive signals. Use .get() to access the current value of a signal containing a list:
use euv::*;
fn app() -> VirtualNode {
let items: Signal<Vec<String>> = use_signal(|| {
vec![
String::from("Apple"),
String::from("Banana"),
String::from("Cherry"),
]
});
html! {
ul {
for item in items.get() {
li { item }
}
}
}
}
mount("#app", app);
When the items signal changes (e.g., when items are added or removed), euv re-renders the list automatically.
Transforming Lists
You can transform list data before rendering:
use euv::*;
fn app() -> VirtualNode {
let numbers: Signal<Vec<i32>> = use_signal(|| {
vec![1, 2, 3, 4, 5]
});
html! {
ul {
for n in numbers.get() {
li { "Square: " {n * n} }
}
}
}
}
mount("#app", app);
Lists with Complex Items
Lists can contain complex structures, not just strings:
use euv::*;
fn app() -> VirtualNode {
let todos: Signal<Vec<(String, bool)>> = use_signal(|| {
vec![
(String::from("Learn euv"), true),
(String::from("Build an app"), false),
(String::from("Deploy to production"), false),
]
});
html! {
ul {
for (text, done) in todos.get() {
li {
class: class!(
"todo-item",
if done { "completed" } else { "pending" }
),
text
}
}
}
}
}
mount("#app", app);
Dynamic Lists with Add/Remove
Here's a complete example of a dynamic list where items can be added and removed:
use euv::*;
fn app() -> VirtualNode {
let items: Signal<Vec<String>> = use_signal(|| {
vec![String::from("Item 1"), String::from("Item 2")]
});
html! {
div {
h1 { "Dynamic List" }
ul {
for item in items.get() {
li {
span { item }
button {
onclick: move || {
items.set(vec![
String::from("Item 1"),
String::from("Item 2"),
String::from("Item 3"),
]);
}
"Add Item"
}
}
}
}
p { "Total items: " {items.get().len()} }
}
}
}
mount("#app", app);
Using Keyed Diffing
euv's renderer employs Keyed Diffing to efficiently update lists. When items in a list have unique identifiers, euv can track individual items across renders, minimizing unnecessary DOM operations. This is particularly important when:
- Items are added or removed from the list
- Items are reordered
- The list is large
To take advantage of keyed diffing, ensure that each item in your list has a stable, unique identifier. The html! macro's for loop automatically works with the diffing algorithm to produce efficient updates.
Lists with Conditional Rendering
You can combine for loops with if expressions for conditional rendering within lists:
use euv::*;
fn app() -> VirtualNode {
let items: Signal<Vec<(String, bool)>> = use_signal(|| {
vec![
(String::from("Public item"), true),
(String::from("Private item"), false),
(String::from("Public item 2"), true),
]
});
html! {
ul {
for (name, is_public) in items.get() {
if is_public {
li { name }
}
}
}
}
}
mount("#app", app);
This renders only the items where is_public is true, filtering out the rest.
Empty List States
Always consider what to display when a list is empty:
use euv::*;
fn app() -> VirtualNode {
let items: Signal<Vec<String>> = use_signal(|| vec![]);
html! {
div {
if items.get().is_empty() {
p { "No items yet. Add one to get started!" }
} else {
ul {
for item in items.get() {
li { item }
}
}
}
}
}
}
mount("#app", app);
Nested Lists
Lists can be nested inside other lists for hierarchical data:
use euv::*;
fn app() -> VirtualNode {
let categories: Signal<Vec<(String, Vec<String>)>> = use_signal(|| {
vec![
(String::from("Fruits"), vec![
String::from("Apple"),
String::from("Banana"),
]),
(String::from("Vegetables"), vec![
String::from("Carrot"),
String::from("Broccoli"),
]),
]
});
html! {
div {
for (category, items) in categories.get() {
div {
h2 { category }
ul {
for item in items {
li { item }
}
}
}
}
}
}
}
mount("#app", app);
Lists with Interactive Items
Each item in a list can have its own interactive elements:
use euv::*;
fn app() -> VirtualNode {
let count: Signal<i32> = use_signal(|| 0);
html! {
div {
h1 { "Shopping List" }
ul {
for item in vec!["Milk", "Eggs", "Bread"] {
li {
span { item }
button {
onclick: move || {
count.set(count.get() + 1);
}
"Add to cart"
}
}
}
}
p { "Items in cart: " {count} }
}
}
}
mount("#app", app);
Combining Lists with Computed Values
Use computed! to derive filtered or sorted versions of lists:
use euv::*;
fn app() -> VirtualNode {
let all_items: Signal<Vec<i32>> = use_signal(|| {
vec![5, 3, 8, 1, 9, 2, 7, 4, 6]
});
let sorted_items = computed!(all_items, |items| {
let mut sorted = items.clone();
sorted.sort();
sorted
});
html! {
div {
h1 { "Sorted Numbers" }
ul {
for item in sorted_items.get() {
li { item }
}
}
}
}
}
mount("#app", app);
The computed! macro automatically re-sorts whenever all_items changes, keeping the sorted view in sync.
Performance Considerations
When working with large lists, keep these tips in mind:
Use keyed diffing — Ensure items have stable identifiers so euv can efficiently track changes.
Avoid unnecessary signal reads — Reading a signal inside a
forloop creates a subscription for each iteration. Consider extracting the value first:
let current_items = items.get();
for item in current_items {
li { item }
}
-
Use
batchfor bulk updates — When making multiple changes to a list, wrap them in abatchcall:
batch(|| {
count.set(1);
});
- Keep list items simple — Complex list items with many nested elements increase the diffing workload. Keep the virtual DOM tree shallow where possible.
Summary
euv provides flexible and efficient patterns for rendering lists. The for loop in the html! macro iterates over any collection, while reactive signals keep the UI in sync with data changes. Keyed diffing ensures efficient updates even for large or frequently changing lists. By combining these features with computed!, if expressions, and interactive elements, you can build sophisticated list-based UIs with minimal code.
Project Code:https://github.com/euv-dev/euv
Top comments (0)