#[component]
Expand description
Annotates a function so that it can be used with your template as a Leptos <Component/>
.
The #[component]
macro allows you to annotate plain Rust functions as components
and use them within your Leptos view as if they were custom HTML elements. The
component function takes any number of other arguments. When you use the component somewhere else,
the names of its arguments are the names of the properties you use in the view macro.
Every component function should have the return type -> impl IntoView
.
You can add Rust doc comments to component function arguments and the macro will use them to generate documentation for the component.
Here’s how you would define and use a simple Leptos component which can accept custom properties for a name and age:
use std::time::Duration;
#[component]
fn HelloComponent(
/// The user's name.
name: String,
/// The user's age.
age: u8,
) -> impl IntoView {
// create the signals (reactive values) that will update the UI
let (age, set_age) = create_signal(age);
// increase `age` by 1 every second
set_interval(
move || set_age.update(|age| *age += 1),
Duration::from_secs(1),
);
// return the user interface, which will be automatically updated
// when signal values change
view! {
<p>"Your name is " {name} " and you are " {move || age.get()} " years old."</p>
}
}
#[component]
fn App() -> impl IntoView {
view! {
<main>
<HelloComponent name="Greg".to_string() age=32/>
</main>
}
}
Here are some important details about how Leptos components work within the framework:
-
The component function only runs once. Your component function is not a “render” function that re-runs whenever changes happen in the state. It’s a “setup” function that runs once to create the user interface, and sets up a reactive system to update it. This means it’s okay to do relatively expensive work within the component function, as it will only happen once, not on every state change.
-
Component names are usually in
PascalCase
. If you use asnake_case
name, then the generated component’s name will still be inPascalCase
. This is how the framework recognizes that a particular tag is a component, not an HTML element.
// PascalCase: Generated component will be called MyComponent
#[component]
fn MyComponent() -> impl IntoView {}
// snake_case: Generated component will be called MySnakeCaseComponent
#[component]
fn my_snake_case_component() -> impl IntoView {}
- You can pass generic arguments, and they can either be defined in a
where
clause or inline in the generic block, but not in animpl
in function argument position.
// ❌ This won't work.
# use leptos::*;
use leptos::html::Div;
#[component]
fn MyComponent(render_prop: impl Fn() -> HtmlElement<Div>) -> impl IntoView {
}
// ✅ Do this instead
use leptos::html::Div;
#[component]
fn MyComponent<T>(render_prop: T) -> impl IntoView
where
T: Fn() -> HtmlElement<Div>,
{
}
// or
#[component]
fn MyComponent2<T: Fn() -> HtmlElement<Div>>(
render_prop: T,
) -> impl IntoView {
}
- You can access the children passed into the component with the
children
property, which takes an argument of the typeChildren
. This is an alias forBox<dyn FnOnce() -> Fragment>
. If you needchildren
to be aFn
orFnMut
, you can use theChildrenFn
orChildrenFnMut
type aliases.
#[component]
fn ComponentWithChildren(children: Children) -> impl IntoView {
view! {
<ul>
{children()
.nodes
.into_iter()
.map(|child| view! { <li>{child}</li> })
.collect::<Vec<_>>()}
</ul>
}
}
#[component]
fn WrapSomeChildren() -> impl IntoView {
view! {
<ComponentWithChildren>
"Ooh, look at us!"
<span>"We're being projected!"</span>
</ComponentWithChildren>
}
}
§Customizing Properties
You can use the #[prop]
attribute on individual component properties (function arguments) to
customize the types that component property can receive. You can use the following attributes:
#[prop(into)]
: This will call.into()
on any value passed into the component prop. (For example, you could apply#[prop(into)]
to a prop that takes Signal, which would allow users to pass a ReadSignal or RwSignal and automatically convert it.)#[prop(optional)]
: If the user does not specify this property when they use the component, it will be set to its default value. If the property type isOption<T>
, values should be passed asname=T
and will be received asSome(T)
.#[prop(optional_no_strip)]
: The same asoptional
, but requires values to be passed asNone
orSome(T)
explicitly. This means that the optional property can be omitted (and beNone
), or explicitly specified as eitherNone
orSome(T)
.
#[component]
pub fn MyComponent(
#[prop(into)] name: String,
#[prop(optional)] optional_value: Option<i32>,
#[prop(optional_no_strip)] optional_no_strip: Option<i32>,
) -> impl IntoView {
// whatever UI you need
}
#[component]
pub fn App() -> impl IntoView {
view! {
<MyComponent
name="Greg" // automatically converted to String with `.into()`
optional_value=42 // received as `Some(42)`
optional_no_strip=Some(42) // received as `Some(42)`
/>
<MyComponent
name="Bob" // automatically converted to String with `.into()`
// optional values can both be omitted, and received as `None`
/>
}
}