DEV Community

tengxgfyrz67s
tengxgfyrz67s

Posted on

Component-Nesting-and-Composition

Component Nesting and Composition in euv

Project Code:https://github.com/euv-dev/euv

euv is a Rust + WASM frontend UI framework that enables developers to build rich, interactive web applications using the power of Rust's type system and the flexibility of a virtual DOM. One of the most important architectural patterns in euv is component nesting and composition — the practice of building complex UIs by combining small, focused components into larger, more capable structures. This article explores how euv supports nested component hierarchies, composable prop passing, and the patterns that make component trees both expressive and maintainable.

Why Composition Matters

Modern user interfaces are inherently complex. A typical web application might contain navigation bars, sidebars, content cards, modals, forms, and dozens of other interactive elements. Trying to build all of this in a single monolithic component would be unmaintainable. Composition solves this by letting you break the UI into self-contained pieces, each responsible for a single concern, and then assemble them into a tree.

euv embraces this philosophy fully. Every component in euv is a Rust function annotated with the #[component] attribute. These components can be composed together using the html! macro, creating a declarative tree structure that mirrors the final DOM.

Defining a Composable Component

Let's start with a concrete example. Suppose we want to build a card component that displays a title and wraps some child content. In euv, we define a props struct and a component function:

#[derive(Clone, Default)]
struct MyCardProps {
    title: &'static str,
    onclick: Option<Rc<dyn Fn(Event)>>,
    disabled: Signal<bool>,
}

#[component]
pub fn my_card(node: VirtualNode<MyCardProps>) -> VirtualNode {
    let MyCardProps { title, .. } = node.try_get_props().unwrap_or_default();
    let children: VirtualNode = node.try_get_child_node();
    html! {
        div {
            h3 { title }
            children
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

There are several important things happening here:

  1. MyCardProps is a struct that defines the card's configuration. It derives Clone and Default, which euv requires for prop types.
  2. try_get_props() extracts the typed props from the incoming VirtualNode. The unwrap_or_default() call ensures that if no props are provided, the component falls back to sensible defaults.
  3. try_get_child_node() retrieves any child content that was passed into this component from its parent. This is the key mechanism that enables nesting — a component can accept and render arbitrary child nodes.
  4. The html! macro builds the virtual DOM tree. Notice how children is injected directly into the markup, allowing whatever the parent passes in to be rendered inside the card.

Nesting Components with the html! Macro

With our my_card component defined, we can nest it inside other components using the html! macro's declarative syntax. The macro supports a natural, indentation-based structure that reads like HTML but provides the full power of Rust:

html! {
    my_card {
        title: "Nested Components"
        primary_button {
            my_badge {
                color: "#7c3aed"
                text: "Badge"
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

This example demonstrates three levels of nesting:

  1. my_card is the outermost component, receiving a title prop.
  2. primary_button is nested inside my_card as its child content.
  3. my_badge is nested inside primary_button, receiving color and text props.

The html! macro translates this declarative structure into a tree of VirtualNode instances. Each component node receives its props (like title, color, and text) and its child nodes (the content indented within its block). This makes it straightforward to reason about the UI hierarchy at a glance.

The Children Mechanism

The ability to pass child content into a component is central to composition in euv. When you write:

html! {
    my_card {
        title: "Example"
        // Everything below is "children"
        p { "This paragraph is a child of my_card." }
        button { "Click me" }
    }
}
Enter fullscreen mode Exit fullscreen mode

The try_get_child_node() call inside my_card retrieves all the indented content as a VirtualNode. This means a component can act as a wrapper or layout container, providing structure and styling while delegating the actual content to whatever the parent chooses to pass in.

This pattern is incredibly powerful for building reusable layout components like cards, modals, tabs, sidebars, and more. The wrapper component handles the chrome (headers, borders, close buttons), while the content remains fully customizable by the consumer.

Props Down / Callback Up

Component composition in euv follows the classic "props down, callback up" pattern. Parent components pass data and callbacks down to children via props, and children communicate back up by invoking those callbacks.

Consider a scenario where a child component needs to notify its parent about an event:

#[derive(Clone, Default)]
struct MyCardProps {
    title: &'static str,
    onclick: Option<Rc<dyn Fn(Event)>>,
    disabled: Signal<bool>,
}
Enter fullscreen mode Exit fullscreen mode

Here, onclick is an optional callback. The parent provides it as a prop, and the child component can invoke it when the user interacts with the card. This unidirectional data flow makes component hierarchies predictable and easy to debug.

Composition Patterns

Layout Composition

The most basic form of composition is layout — wrapping content inside structural components:

html! {
    my_card {
        title: "User Profile"
        // Child content goes here
        p { "Name: Alice" }
        p { "Email: alice@example.com" }
    }
}
Enter fullscreen mode Exit fullscreen mode

Multi-Level Nesting

Components can be nested many levels deep. Each level passes its own props and forwards children as needed:

html! {
    my_card {
        title: "Deep Nesting Example"
        primary_button {
            my_badge {
                color: "#7c3aed"
                text: "Pro"
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Composing with Reactive Props

When props include reactive signals, the composition becomes dynamic. A parent can pass a Signal<bool> as a prop, and the child will automatically re-render when the signal changes:

#[derive(Clone, Default)]
struct MyCardProps {
    title: &'static str,
    onclick: Option<Rc<dyn Fn(Event)>>,
    disabled: Signal<bool>,
}
Enter fullscreen mode Exit fullscreen mode

The disabled: Signal<bool> prop means the card's appearance and behavior can change reactively based on application state, all without the child needing to know about the parent's state management strategy.

Best Practices for Component Composition

  1. Keep components focused. Each component should do one thing well. A card component should handle card layout, not form validation or data fetching.

  2. Use try_get_child_node() for wrapper components. If your component is a layout container, always extract and render the child node. This makes your component truly reusable.

  3. Provide default values with unwrap_or_default(). This ensures your component works even when the parent doesn't pass all props, reducing boilerplate for common use cases.

  4. Leverage the type system. Rust's type system catches prop mismatches at compile time. Use strongly-typed props structs to make your component interfaces explicit and self-documenting.

  5. Compose, don't inherit. euv doesn't have class inheritance. Instead, compose small components into larger ones. This leads to more flexible and testable code.

Conclusion

Component nesting and composition are at the heart of building scalable applications in euv. By defining small, focused components with clear prop interfaces and using the html! macro to compose them into larger structures, you can build complex UIs that remain maintainable and type-safe. The combination of Rust's type system, reactive signals, and the declarative html! macro makes euv a powerful tool for frontend development. In the next articles, we'll explore how these composed components interact with the virtual DOM, handle events, and manage state across the component tree.


Project Code:https://github.com/euv-dev/euv

Top comments (0)