Attribute Macro leptos::component

source ·
#[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 a snake_case name, then the generated component’s name will still be in PascalCase. 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 an impl 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 {
}
  1. You can access the children passed into the component with the children property, which takes an argument of the type Children. This is an alias for Box<dyn FnOnce() -> Fragment>. If you need children to be a Fn or FnMut, you can use the ChildrenFn or ChildrenFnMut 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 is Option<T>, values should be passed as name=T and will be received as Some(T).
  • #[prop(optional_no_strip)]: The same as optional, but requires values to be passed as None or Some(T) explicitly. This means that the optional property can be omitted (and be None), or explicitly specified as either None or Some(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`
      />
    }
}