# RSX in Depth The RSX macro makes it very easy to assemble complex UIs with a very natural Rust syntax: ```rust rsx!( div { button { onclick: move |e| todos.write().new_todo(), "Add todo" } ul { class: "todo-list", todos.iter().map(|(key, todo)| rsx!( li { class: "beautiful-todo" key: "f" h3 { "{todo.title}" } p { "{todo.contents}"} } )) } } ) ``` In this section, we'll cover the `rsx!` macro in depth. If you prefer to learn through examples, the `code reference` guide has plenty of examples on how to use `rsx!` effectively. ### Element structure Attributes must come before child elements ```rust div { hidden: "false", "some text" child {} Component {} // uppercase component() // lowercase is treated like a function call (0..10).map(|f| rsx!{ "hi {f}" }) // arbitrary rust expressions } ``` Each element takes a comma-separated list of expressions to build the node. Roughly, here's how they work: - `name: value` sets a property on this element. - `"text"` adds a new text element - `tag {}` adds a new child element - `CustomTag {}` adds a new child component - `{expr}` pastes the `expr` tokens literally. They must be `IntoIterator where T: IntoVnode` to work properly Commas are entirely optional, but might be useful to delineate between elements and attributes. The `render` function provides an **extremely efficient** allocator for VNodes and text, so try not to use the `format!` macro in your components. Rust's default `ToString` methods pass through the global allocator, but all text in components is allocated inside a manually-managed Bump arena. To push you in the right direction, all text-based attributes take `std::fmt::Arguments` directly, so you'll want to reach for `format_args!` when the built-in `f-string` interpolation just doesn't cut it. ### Ignoring `cx.render` with `rsx!(cx, ...)` Sometimes, writing `cx.render` is a hassle. The `rsx! macro will accept any token followed by a comma as the target to call "render" on: ```rust cx.render(rsx!( div {} )) // becomes rsx!(cx, div {}) ``` ### Conditional Rendering Sometimes, you might not want to render an element given a condition. The rsx! macro will accept any tokens directly contained with curly braces, provided they resolve to a type that implements `IntoIterator`. This lets us write any Rust expression that resolves to a VNode: ```rust rsx!({ if enabled { rsx!(cx, div {"enabled"}) } else { rsx!(cx, li {"disabled"}) } }) ``` A convenient way of hiding/showing an element is returning an `Option`. When combined with `and_then`, we can succinctly control the display state given some boolean: ```rust rsx!({ a.and_then(rsx!(div {"enabled"})) }) ``` It's important to note that the expression `rsx!()` is typically lazy - this expression must be _rendered_ to produce a VNode. When using match statements, we must render every arm as to avoid the `no two closures are identical` rule that Rust imposes: ```rust // this will not compile! match case { true => rsx!(div {}), false => rsx!(div {}) } // the nodes must be rendered first match case { true => rsx!(cx, div {}), false => rsx!(cx, div {}) } ``` ### Lists Again, because anything that implements `IntoIterator` is valid, we can use lists directly in our `rsx!`: ```rust let items = vec!["a", "b", "c"]; cx.render(rsx!{ ul { {items.iter().map(|f| rsx!(li { "a" }))} } }) ``` Sometimes, it makes sense to render VNodes into a list: ```rust let mut items = vec![]; for _ in 0..5 { items.push(rsx!(cx, li {} )) } rsx!(cx, {items} ) ``` #### Lists and Keys When rendering the VirtualDom to the screen, Dioxus needs to know which elements have been added and which have been removed. These changes are determined through a process called "diffing" - an old set of elements is compared to a new set of elements. If an element is removed, then it won't show up in the new elements, and Dioxus knows to remove it. However, with lists, Dioxus does not exactly know how to determine which elements have been added or removed if the order changes or if an element is added or removed from the middle of the list. In these cases, it is vitally important to specify a "key" alongside the element. Keys should be persistent between renders. ```rust fn render_list(cx: Scope, items: HashMap) -> DomTree { rsx!(cx, ul { {items.iter().map(|key, item| { li { key: key, h2 { "{todo.title}" } p { "{todo.contents}" } } })} }) } ``` There have been many guides made for keys in React, so we recommend reading up to understand their importance: - [React guide on keys](https://reactjs.org/docs/lists-and-keys.html) - [Importance of keys (Medium)](https://kentcdodds.com/blog/understanding-reacts-key-prop) ### Complete Reference ```rust let text = "example"; cx.render(rsx!{ div { h1 { "Example" }, {title} // fstring interpolation "{text}" p { // Attributes tag: "type", // Anything that implements display can be an attribute abc: 123, enabled: true, // attributes also supports interpolation // `class` is not a restricted keyword unlike JS and ClassName class: "big small wide short {text}", class: format_args!("attributes take fmt::Arguments. {}", 99), tag: {"these tokens are placed directly"} // Children a { "abcder" }, // Children with attributes h2 { "hello", class: "abc-123" }, // Child components CustomComponent { a: 123, b: 456, key: "1" }, // Child components with paths crate::components::CustomComponent { a: 123, b: 456, key: "1" }, // Iterators { (0..3).map(|i| rsx!( h1 {"{:i}"} )) }, // More rsx!, or even html! { rsx! { div { } } }, { html! {
} }, // Matching // Requires rendering the nodes first. // rsx! is lazy, and the underlying closures cannot have the same type // Rendering produces the VNode type {match rand::gen_range::(1..3) { 1 => rsx!(cx, h1 { "big" }) 2 => rsx!(cx, h2 { "medium" }) _ => rsx!(cx, h3 { "small" }) }} // Optionals {true.and_then(|f| rsx!( h1 {"Conditional Rendering"} ))} // Child nodes {cx.props.children} // Any expression that is `IntoVNode` {expr} } } }) ```