Browse documentation
Documentation

Components

Edit this guide on GitHub

The #[component]``component attribute defines an async function that returns a view. Components accept typed parameters and return Result<impl View>.

use hearth::{
    Result,
    view::{View, class, component, view},
};

#[component]
async fn badge(label: &str, is_success: bool) -> Result<impl View> {
    Ok(view! {
        <span class=(class!("badge", "badge-success" if is_success))>
            (label)
        </span>
    })
}

Calling Components

Call components inside view! with named arguments:

Ok(view! {
    <header>
        badge(
            label: "New",
            is_success: true,
        )
    </header>
})

Child Content

If a component accepts a parameter named child with type Child, any extra view nodes in the call are collected and passed as that child view. Give it #[default] so the component can also be called without children.

use hearth::{
    Result,
    view::{Child, View, component, view},
};

#[component]
async fn panel(title: &str, #[default] child: Child<'_>) -> Result<impl View> {
    Ok(view! {
        <section class="panel">
            <h2>(title)</h2>
            <div class="panel-body">
                (child)
            </div>
        </section>
    })
}

Ok(view! {
    panel(
        title: "Profile",
        // Child nodes:
        <p>"Account details"</p>
        badge(
            label: "Active",
            is_success: true,
        )
    )
})

You can also pass a view explicitly as the child argument.

Parameter attributes

A parameter can have these attributes:

#[component]
async fn badge(#[into] label: String, #[default] tone: Tone, #[default(80)] max_length: usize) -> Result<impl View> {
    // ...
}

Generics

Components can be generic. Depending on usage, you may need to declare the type as Send or Sync:

#[component]
async fn count<T: Send + Sync>(items: Vec<T>) -> Result<impl View> {
    Ok(view! { <span>(items.len())</span> })
}

impl Trait parameters work too:

#[component]
async fn shout(label: impl Into<String> + Send) -> Result<impl View> {
    Ok(view! { <b>(label.into().to_uppercase())</b> })
}

Prefer #[into] when the component only needs the converted value.

Request Context

Components can ask for the current request context by declaring a cx parameter that borrows Cx:

use hearth::{
    Result,
    context::Cx,
    router::request::uri,
    view::{View, component, view},
};

#[component]
async fn current_path(cx: &Cx) -> Result<impl View> {
    Ok(view! {
        <span>(uri(cx).path())</span>
    })
}

Recursive Components

A recursive component needs to box its view with boxed. This gives the view a type with a known size:

use hearth::{
    Result,
    view::{View, ViewExt, component, view},
};

#[component]
async fn countdown(n: u32) -> Result<impl View> {
    Ok(view! {
        <li>(n)</li>
        if n > 0 {
            countdown(n: n - 1)
        }
    }
    .boxed())
}

For mutually recursive components, boxing one view in the cycle is enough.