#[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 a Scope
and 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(
cx: Scope,
/// 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(cx, 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! { cx,
<p>"Your name is " {name} " and you are " {age} " years old."</p>
}
}
#[component]
fn App(cx: Scope) -> impl IntoView {
view! { cx,
<main>
<HelloComponent name="Greg".to_string() age=32/>
</main>
}
}
The #[component]
macro creates a struct with a name like HelloComponentProps
. If you define
your component in one module and import it into another, make sure you import this ___Props
struct as well.
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. It’s important to be aware of this when using or importing the component.
// PascalCase: Generated component will be called MyComponent
#[component]
fn MyComponent(cx: Scope) -> impl IntoView {
todo!()
}
// snake_case: Generated component will be called MySnakeCaseComponent
#[component]
fn my_snake_case_component(cx: Scope) -> impl IntoView {
todo!()
}
- The macro generates a type
ComponentProps
for everyComponent
(so,HomePage
generatesHomePageProps
,Button
generatesButtonProps
, etc.) When you’re importing the component, you also need to explicitly import the prop type.
use component::{MyComponent, MyComponentProps};
mod component {
use leptos::*;
#[component]
pub fn MyComponent(cx: Scope) -> impl IntoView {
todo!()
}
}
use snake_case_component::{
MySnakeCaseComponent, MySnakeCaseComponentProps,
};
mod snake_case_component {
use leptos::*;
#[component]
pub fn my_snake_case_component(cx: Scope) -> impl IntoView {
todo!()
}
}
- You can pass generic arguments, but they should be defined in a
where
clause and not inline.
// ❌ This won't work.
# use leptos::*;
use leptos::html::Div;
#[component]
fn MyComponent<T: Fn() -> HtmlElement<Div>>(cx: Scope, render_prop: T) -> impl IntoView {
todo!()
}
// ✅ Do this instead
use leptos::html::Div;
#[component]
fn MyComponent<T>(cx: Scope, render_prop: T) -> impl IntoView
where
T: Fn() -> HtmlElement<Div>,
{
todo!()
}
- 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(Scope) -> Fragment>
. If you needchildren
to be aFn
orFnMut
, you can use theChildrenFn
orChildrenFnMut
type aliases.
#[component]
fn ComponentWithChildren(cx: Scope, children: Children) -> impl IntoView {
view! {
cx,
<ul>
{children(cx)
.nodes
.into_iter()
.map(|child| view! { cx, <li>{child}</li> })
.collect::<Vec<_>>()}
</ul>
}
}
#[component]
fn WrapSomeChildren(cx: Scope) -> impl IntoView {
view! { cx,
<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(
cx: Scope,
#[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(cx: Scope) -> impl IntoView {
view! { cx,
<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`
/>
}
}