Components
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:
#[default]usesDefault::default()when the caller omits the argument.#[default(expr)]uses a custom fallback instead. The fallback runs only when the argument is omitted and does not require the type to implementDefault.#[into]accepts any value that converts to the parameter's type throughInto. The component receives the converted value.
#[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.