Macro leptos::view

source ·
view!() { /* proc-macro */ }
Expand description

The view macro uses RSX (like JSX, but Rust!) It follows most of the same rules as HTML, with the following differences:

  1. Text content should be provided as a Rust string, i.e., double-quoted:
view! { <p>"Here’s some text"</p> };
  1. Self-closing tags need an explicit / as in XML/XHTML
// ❌ not like this
view! { <input type="text" name="name"> }
// ✅ add that slash
view! { <input type="text" name="name" /> }
  1. Components (functions annotated with #[component]) can be inserted as camel-cased tags. (Generics on components are specified as <Component<T>/>, not the turbofish <Component::<T>/>.)
view! { <div><Counter initial_value=3 /></div> }
  1. Dynamic content can be wrapped in curly braces ({ }) to insert text nodes, elements, or set attributes. If you insert a signal here, Leptos will create an effect to update the DOM whenever the value changes. (“Signal” here means Fn() -> T where T is the appropriate type for that node: a String in case of text nodes, a bool for class: attributes, etc.)

    Attributes can take a wide variety of primitive types that can be converted to strings. They can also take an Option, in which case Some sets the attribute and None removes the attribute.

let (count, set_count) = create_signal(0);

view! {
  // ❌ not like this: `count.get()` returns an `i32`, not a function
  // ✅ this is good: Leptos sees the function and knows it's a dynamic value
  <p>{move || count.get()}</p>
  // 🔥 with the `nightly` feature, `count` is a function, so `count` itself can be passed directly into the view
  1. Event handlers can be added with on: attributes. In most cases, the events are given the correct type based on the event name.
view! {
  <button on:click=|ev| {
    log::debug!("click event: {ev:#?}");
    "Click me"
  1. DOM properties can be set with prop: attributes, which take any primitive type or JsValue (or a signal that returns a primitive or JsValue). They can also take an Option, in which case Some sets the property and None deletes the property.
let (name, set_name) = create_signal("Alice".to_string());

view! {
    value={move || name.get()} // this only sets the default value!
    prop:value={move || name.get()} // here's how you update values. Sorry, I didn’t invent the DOM.
    on:click=move |ev| set_name.set(event_target_value(&ev)) // `event_target_value` is a useful little Leptos helper
  1. Classes can be toggled with class: attributes, which take a bool (or a signal that returns a bool).
let (count, set_count) = create_signal(2);
view! { <div class:hidden-div={move || count.get() < 3}>"Now you see me, now you don’t."</div> }

Class names can include dashes, and since v0.5.0 can include a dash-separated segment of only numbers.

let (count, set_count) = create_signal(2);
view! { <div class:hidden-div-25={move || count.get() < 3}>"Now you see me, now you don’t."</div> }

Class names cannot include special symbols.

let (count, set_count) = create_signal(2);
// class:hidden-[div]-25 is invalid attribute name
view! { <div class:hidden-[div]-25={move || count.get() < 3}>"Now you see me, now you don’t."</div> }

However, you can pass arbitrary class names using the syntax class=("name", value).

let (count, set_count) = create_signal(2);
// this allows you to use CSS frameworks that include complex class names
view! {
    class=("is-[this_-_really]-necessary-42", move || count.get() < 3)
    "Now you see me, now you don’t."
  1. Individual styles can also be set with style: or style=("property-name", value) syntax.
let (x, set_x) = create_signal(0);
let (y, set_y) = create_signal(0);
view! {
    style="position: absolute"
    style:left=move || format!("{}px", x.get())
    style:top=move || format!("{}px", y.get())
    style=("background-color", move || format!("rgb({}, {}, 100)", x.get(), y.get()))
    "Moves when coordinates change"
  1. You can use the node_ref or _ref attribute to store a reference to its DOM element in a NodeRef to use later.
use leptos::html::Input;

let (value, set_value) = create_signal(0);
let my_input = create_node_ref::<Input>();
view! { <input type="text" _ref=my_input/> }
// `my_input` now contains an `Element` that we can use anywhere
  1. You can add the same class to every element in the view by passing in a special class = {/* ... */}, argument after ``. This is useful for injecting a class provided by a scoped styling library.
let class = "mycustomclass";
view! { class = class,
  <div> // will have class="mycustomclass"
    <p>"Some text"</p> // will also have class "mycustomclass"
  1. You can set any HTML element’s innerHTML with the inner_html attribute on an element. Be careful: this HTML will not be escaped, so you should ensure that it only contains trusted input.
let html = "<p>This HTML will be injected.</p>";
view! {
  <div inner_html=html/>

Here’s a simple example that shows off several of these features, put together

pub fn SimpleCounter() -> impl IntoView {
    // create a reactive signal with the initial value
    let (value, set_value) = create_signal(0);

    // create event handlers for our buttons
    // note that `value` and `set_value` are `Copy`, so it's super easy to move them into closures
    let clear = move |_ev| set_value.set(0);
    let decrement = move |_ev| set_value.update(|value| *value -= 1);
    let increment = move |_ev| set_value.update(|value| *value += 1);

    view! {
            <button on:click=clear>"Clear"</button>
            <button on:click=decrement>"-1"</button>
            <span>"Value: " {move || value.get().to_string()} "!"</span>
            <button on:click=increment>"+1"</button>