Attribute Macro leptos_macro::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 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:

  1. 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.

  2. 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. 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!()
}
  1. The macro generates a type ComponentProps for every Component (so, HomePage generates HomePageProps, Button generates ButtonProps, 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!()
    }
}
  1. 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!()
}
  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(Scope) -> Fragment>. If you need children to be a Fn or FnMut, you can use the ChildrenFn or ChildrenFnMut 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 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(
    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`
      />
    }
}