DEV Community

tengxgfyrz67s
tengxgfyrz67s

Posted on

List-Rendering-Patterns

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);
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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:

  1. Use keyed diffing — Ensure items have stable identifiers so euv can efficiently track changes.

  2. Avoid unnecessary signal reads — Reading a signal inside a for loop creates a subscription for each iteration. Consider extracting the value first:

let current_items = items.get();
for item in current_items {
    li { item }
}
Enter fullscreen mode Exit fullscreen mode
  1. Use batch for bulk updates — When making multiple changes to a list, wrap them in a batch call:
batch(|| {
    count.set(1);
});
Enter fullscreen mode Exit fullscreen mode
  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)