Browse Source

Merge pull request #1810 from DioxusLabs/jk/disambiguate-exprs-in-rsx

Disambiguate if expressions in rsx by requiring curlies, allow shorthand component/element initialization
Jonathan Kelley 1 year ago
parent
commit
f7bf156422
81 changed files with 866 additions and 647 deletions
  1. 5 1
      examples/all_events.rs
  2. 4 4
      examples/calculator.rs
  3. 2 2
      examples/compose.rs
  4. 2 2
      examples/control_focus.rs
  5. 2 2
      examples/crm.rs
  6. 2 2
      examples/drops.rs
  7. 3 5
      examples/dynamic_asset.rs
  8. 3 5
      examples/error_handle.rs
  9. 3 3
      examples/fermi.rs
  10. 14 17
      examples/file_explorer.rs
  11. 6 5
      examples/framework_benchmark.rs
  12. 6 8
      examples/generic_component.rs
  13. 2 2
      examples/hello_world.rs
  14. 3 3
      examples/inputs.rs
  15. 5 5
      examples/openid_connect_demo/src/views/header.rs
  16. 5 2
      examples/openid_connect_demo/src/views/not_found.rs
  17. 7 5
      examples/pattern_model.rs
  18. 1 1
      examples/pattern_reducer.rs
  19. 2 2
      examples/query_segments_demo/src/main.rs
  20. 8 8
      examples/rsx_compile_fail.rs
  21. 17 16
      examples/rsx_usage.rs
  22. 4 2
      examples/scroll_to_top.rs
  23. 13 16
      examples/shared_state.rs
  24. 45 0
      examples/shorthand.rs
  25. 3 3
      examples/signals.rs
  26. 5 5
      examples/simple_list.rs
  27. 1 1
      examples/svg.rs
  28. 33 35
      examples/todomvc.rs
  29. 4 0
      packages/autofmt/src/component.rs
  30. 3 0
      packages/autofmt/src/element.rs
  31. 46 4
      packages/autofmt/src/writer.rs
  32. 3 3
      packages/autofmt/tests/samples/commentshard.rsx
  33. 4 4
      packages/autofmt/tests/samples/complex.rsx
  34. 15 1
      packages/autofmt/tests/samples/ifchain_forloop.rsx
  35. 1 1
      packages/autofmt/tests/samples/immediate_expr.rsx
  36. 4 4
      packages/autofmt/tests/samples/long.rsx
  37. 1 1
      packages/autofmt/tests/samples/long_exprs.rsx
  38. 5 5
      packages/autofmt/tests/samples/multirsx.rsx
  39. 1 1
      packages/autofmt/tests/samples/trailing_expr.rsx
  40. 2 2
      packages/autofmt/tests/wrong/multiexpr-4sp.rsx
  41. 1 1
      packages/autofmt/tests/wrong/multiexpr-4sp.wrong.rsx
  42. 2 2
      packages/autofmt/tests/wrong/multiexpr-tab.rsx
  43. 1 1
      packages/autofmt/tests/wrong/multiexpr-tab.wrong.rsx
  44. 2 2
      packages/cli/src/cli/autoformat.rs
  45. 16 1
      packages/core/src/nodes.rs
  46. 4 4
      packages/core/src/scope_context.rs
  47. 1 1
      packages/core/src/virtual_dom.rs
  48. 4 4
      packages/core/tests/create_dom.rs
  49. 9 9
      packages/core/tests/create_fragments.rs
  50. 2 2
      packages/core/tests/create_lists.rs
  51. 2 2
      packages/core/tests/create_passthru.rs
  52. 1 1
      packages/core/tests/diff_component.rs
  53. 12 12
      packages/core/tests/diff_keyed_list.rs
  54. 15 13
      packages/core/tests/diff_unkeyed_list.rs
  55. 2 2
      packages/core/tests/event_propagation.rs
  56. 1 1
      packages/core/tests/lifecycle.rs
  57. 2 2
      packages/core/tests/miri_full_app.rs
  58. 2 2
      packages/core/tests/miri_stress.rs
  59. 1 1
      packages/core/tests/task.rs
  60. 1 1
      packages/desktop/headless_tests/rendering.rs
  61. 1 1
      packages/dioxus-tui/examples/all_terminal_events.rs
  62. 23 33
      packages/dioxus-tui/examples/buttons.rs
  63. 15 22
      packages/dioxus-tui/examples/color_test.rs
  64. 2 2
      packages/dioxus-tui/examples/list.rs
  65. 18 22
      packages/dioxus-tui/examples/many_small_edit_stress.rs
  66. 6 4
      packages/dioxus/examples/stress.rs
  67. 11 26
      packages/native-core/src/utils/persistant_iterator.rs
  68. 1 1
      packages/router/examples/simple_routes.rs
  69. 2 2
      packages/router/src/components/history_buttons.rs
  70. 2 2
      packages/router/src/components/link.rs
  71. 29 7
      packages/rsx/src/attribute.rs
  72. 170 144
      packages/rsx/src/component.rs
  73. 65 35
      packages/rsx/src/element.rs
  74. 7 7
      packages/rsx/src/lib.rs
  75. 117 69
      packages/rsx/src/node.rs
  76. 3 1
      packages/ssr/src/renderer.rs
  77. 3 7
      packages/ssr/tests/hydration.rs
  78. 7 5
      packages/ssr/tests/simple.rs
  79. 2 2
      packages/web/examples/hydrate.rs
  80. 5 2
      packages/web/examples/timeout_count.rs
  81. 1 1
      packages/web/tests/hydrate.rs

+ 5 - 1
examples/all_events.rs

@@ -76,7 +76,11 @@ fn app(cx: Scope) -> Element {
 
                 "Hover, click, type or scroll to see the info down below"
             }
-            div { events.read().iter().map(|event| rsx!( div { "{event:?}" } )) }
+            div {
+                for event in events.read().iter() {
+                    div { "{event:?}" }
+                }
+            }
         }
     ))
 }

+ 4 - 4
examples/calculator.rs

@@ -58,13 +58,13 @@ fn app(cx: Scope) -> Element {
     };
 
     cx.render(rsx!(
-        style { include_str!("./assets/calculator.css") }
+        style { {include_str!("./assets/calculator.css")} }
         div { id: "wrapper",
             div { class: "app",
                 div { class: "calculator",
                     tabindex: "0",
                     onkeydown: handle_key_down_event,
-                    div { class: "calculator-display", val.to_string() }
+                    div { class: "calculator-display", "{val}" }
                     div { class: "calculator-keypad",
                         div { class: "input-keys",
                             div { class: "function-keys",
@@ -103,14 +103,14 @@ fn app(cx: Scope) -> Element {
                             div { class: "digit-keys",
                                 button { class: "calculator-key key-0", onclick: move |_| input_digit(0), "0" }
                                 button { class: "calculator-key key-dot", onclick: move |_| val.make_mut().push('.'), "●" }
-                                (1..10).map(|k| rsx!{
+                                for k in 1..10 {
                                     button {
                                         class: "calculator-key {k}",
                                         name: "key-{k}",
                                         onclick: move |_| input_digit(k),
                                         "{k}"
                                     }
-                                }),
+                                }
                             }
                         }
                         div { class: "operator-keys",

+ 2 - 2
examples/compose.rs

@@ -32,12 +32,12 @@ fn app(cx: Scope) -> Element {
             }
 
             ul {
-                emails_sent.read().iter().map(|message| cx.render(rsx! {
+                for message in emails_sent.read().iter() {
                     li {
                         h3 { "email" }
                         span {"{message}"}
                     }
-                }))
+                }
             }
         }
     })

+ 2 - 2
examples/control_focus.rs

@@ -15,8 +15,8 @@ fn app(cx: Scope) -> Element {
         if *running.current() {
             loop {
                 tokio::time::sleep(std::time::Duration::from_millis(10)).await;
-                if let Some(element) = elements.read().get(focused) {
-                    element.set_focus(true);
+                if let Some(element) = elements.with(|f| f.get(focused).cloned()) {
+                    _ = element.set_focus(true).await;
                 } else {
                     focused = 0;
                 }

+ 2 - 2
examples/crm.rs

@@ -62,7 +62,7 @@ fn ClientList(cx: Scope) -> Element {
         Link { to: Route::ClientAdd {}, class: "pure-button pure-button-primary", "Add Client" }
         Link { to: Route::Settings {}, class: "pure-button", "Settings" }
 
-        clients.read().iter().map(|client| rsx! {
+        for client in clients.read().iter() {
             div {
                 class: "client",
                 style: "margin-bottom: 50px",
@@ -70,7 +70,7 @@ fn ClientList(cx: Scope) -> Element {
                 p { "Name: {client.first_name} {client.last_name}" }
                 p { "Description: {client.description}" }
             }
-        })
+        }
     })
 }
 

+ 2 - 2
examples/drops.rs

@@ -14,9 +14,9 @@ fn app(cx: Scope) -> Element {
     }
 
     render! {
-        (0..count).map(|_| rsx!{
+        for _ in 0..count {
             drop_child {}
-        })
+        }
     }
 }
 

+ 3 - 5
examples/dynamic_asset.rs

@@ -17,11 +17,9 @@ fn app(cx: Scope) -> Element {
         response.respond(Response::new(include_bytes!("./assets/logo.png").to_vec()));
     });
 
-    cx.render(rsx! {
+    render! {
         div {
-            img {
-                src: "/logos/logo.png"
-            }
+            img { src: "/logos/logo.png" }
         }
-    })
+    }
 }

+ 3 - 5
examples/error_handle.rs

@@ -22,9 +22,7 @@ fn DemoC(cx: Scope, x: i32) -> Element {
 
     result.throw()?;
 
-    cx.render(rsx! {
-        h1 {
-            "{x}"
-        }
-    })
+    render! {
+        h1 { "{x}" }
+    }
 }

+ 3 - 3
examples/fermi.rs

@@ -39,9 +39,9 @@ fn ChildWithRef(cx: Scope) -> Element {
     cx.render(rsx! {
         div {
             ul {
-                names.read().iter().map(|f| rsx!{
-                    li { "hello: {f}" }
-                })
+                for name in names.read().iter() {
+                    li { "hello: {name}" }
+                }
             }
             button {
                 onclick: move |_| {

+ 14 - 17
examples/file_explorer.rs

@@ -28,39 +28,36 @@ fn app(cx: Scope) -> Element {
             link { href:"https://fonts.googleapis.com/icon?family=Material+Icons", rel:"stylesheet", }
             header {
                 i { class: "material-icons icon-menu", "menu" }
-                h1 { "Files: ", files.read().current() }
+                h1 { "Files: ", {files.read().current()} }
                 span { }
                 i { class: "material-icons", onclick: move |_| files.write().go_up(), "logout" }
             }
             main {
-                files.read().path_names.iter().enumerate().map(|(dir_id, path)| {
+                {files.read().path_names.iter().enumerate().map(|(dir_id, path)| {
                     let path_end = path.split('/').last().unwrap_or(path.as_str());
-                    let icon_type = if path_end.contains('.') {
-                        "description"
-                    } else {
-                        "folder"
-                    };
                     rsx! (
                         div {
                             class: "folder",
                             key: "{path}",
                             i { class: "material-icons",
                                 onclick: move |_| files.write().enter_dir(dir_id),
-                                "{icon_type}"
+                                if path_end.contains('.') {
+                                    "description"
+                                } else {
+                                    "folder"
+                                }
                                 p { class: "cooltip", "0 folders / 0 files" }
                             }
                             h1 { "{path_end}" }
                         }
                     )
-                }),
-                files.read().err.as_ref().map(|err| {
-                    rsx! (
-                        div {
-                            code { "{err}" }
-                            button { onclick: move |_| files.write().clear_err(), "x" }
-                        }
-                    )
-                })
+                })},
+                if let Some(err) = files.read().err.as_ref() {
+                    div {
+                        code { "{err}" }
+                        button { onclick: move |_| files.write().clear_err(), "x" }
+                    }
+                }
             }
         }
     })

+ 6 - 5
examples/framework_benchmark.rs

@@ -66,9 +66,9 @@ fn app(cx: Scope) -> Element {
             }
             table {
                 tbody {
-                    items.read().iter().enumerate().map(|(id, item)| {
-                        let is_in_danger = if (*selected).map(|s| s == id).unwrap_or(false) {"danger"} else {""};
-                        rsx!(tr { class: "{is_in_danger}",
+                    for (id, item) in items.read().iter().enumerate() {
+                        tr {
+                            class: if (*selected).map(|s| s == id).unwrap_or(false) { "danger" },
                             td { class:"col-md-1" }
                             td { class:"col-md-1", "{item.key}" }
                             td { class:"col-md-1", onclick: move |_| selected.set(Some(id)),
@@ -80,8 +80,9 @@ fn app(cx: Scope) -> Element {
                                 }
                             }
                             td { class: "col-md-6" }
-                        })
-                    })
+                        }
+                    }
+
                 }
              }
             span { class: "preloadicon glyphicon glyphicon-remove", aria_hidden: "true" }

+ 6 - 8
examples/generic_component.rs

@@ -7,9 +7,9 @@ fn main() {
 }
 
 fn app(cx: Scope) -> Element {
-    cx.render(rsx! { generic_child {
-        data: 0i32
-    } })
+    render! {
+        generic_child { data: 0 }
+    }
 }
 
 #[derive(PartialEq, Props)]
@@ -18,9 +18,7 @@ struct GenericChildProps<T: Display + PartialEq> {
 }
 
 fn generic_child<T: Display + PartialEq>(cx: Scope<GenericChildProps<T>>) -> Element {
-    let data = &cx.props.data;
-
-    cx.render(rsx! { div {
-        "{data}"
-    } })
+    render! {
+        div { "{&cx.props.data}" }
+    }
 }

+ 2 - 2
examples/hello_world.rs

@@ -5,7 +5,7 @@ fn main() {
 }
 
 fn app(cx: Scope) -> Element {
-    cx.render(rsx! (
+    render! {
         div { "Hello, world!" }
-    ))
+    }
 }

+ 3 - 3
examples/inputs.rs

@@ -37,7 +37,7 @@ const FIELDS: &[(&str, &str)] = &[
 fn app(cx: Scope) -> Element {
     cx.render(rsx! {
         div { margin_left: "30px",
-            select_example(cx),
+            {select_example(cx)},
             div {
                 // handling inputs on divs will catch all input events below
                 // so the value of our input event will be either huey, dewey, louie, or true/false (because of the checkboxe)
@@ -114,7 +114,7 @@ fn app(cx: Scope) -> Element {
                 }
             }
 
-            FIELDS.iter().map(|(field, value)| rsx! {
+            for (field, value) in FIELDS.iter() {
                 div {
                     input {
                         id: "{field}",
@@ -131,7 +131,7 @@ fn app(cx: Scope) -> Element {
                     }
                     br {}
                 }
-            })
+            }
         }
     })
 }

+ 5 - 5
examples/openid_connect_demo/src/views/header.rs

@@ -41,7 +41,7 @@ pub fn LogOut(cx: Scope<ClientProps>) -> Element {
                         }
                         Err(error) => {
                             rsx! {
-                                div { format!{"Failed to load disconnection url: {:?}", error} }
+                                div { "Failed to load disconnection url: {error:?}" }
                             }
                         }
                     },
@@ -143,9 +143,9 @@ pub fn LoadClient(cx: Scope) -> Element {
                 }
             }
             Err(error) => {
+                log::info! {"Failed to load client: {:?}", error};
                 rsx! {
-                    div { format!{"Failed to load client: {:?}", error} }
-                    log::info!{"Failed to load client: {:?}", error},
+                    div { "Failed to load client: {error:?}" }
                     Outlet::<Route> {}
                 }
             }
@@ -184,7 +184,7 @@ pub fn AuthHeader(cx: Scope) -> Element {
                                 Ok(email) => {
                                     rsx! {
                                         div {
-                                            div { email }
+                                            div { {email} }
                                             LogOut { client_id: client_props.client_id, client: client_props.client }
                                             Outlet::<Route> {}
                                         }
@@ -207,7 +207,7 @@ pub fn AuthHeader(cx: Scope) -> Element {
                                         log::info!("Other issue with token");
                                         rsx! {
                                             div {
-                                                div { error.to_string() }
+                                                div { "{error}" }
                                                 Outlet::<Route> {}
                                             }
                                         }

+ 5 - 2
examples/openid_connect_demo/src/views/not_found.rs

@@ -2,6 +2,9 @@ use dioxus::prelude::*;
 
 #[component]
 pub fn NotFound(cx: Scope, route: Vec<String>) -> Element {
-    let routes = route.join("");
-    render! {rsx! {div{routes}}}
+    render! {
+        div{
+            {route.join("")}
+        }
+    }
 }

+ 7 - 5
examples/pattern_model.rs

@@ -35,15 +35,17 @@ fn main() {
     dioxus_desktop::launch_cfg(app, cfg);
 }
 
+const STYLE: &str = include_str!("./assets/calculator.css");
+
 fn app(cx: Scope) -> Element {
     let state = use_ref(cx, Calculator::new);
 
     cx.render(rsx! {
-        style { include_str!("./assets/calculator.css") }
+        style { {STYLE} }
         div { id: "wrapper",
             div { class: "app",
                 div { class: "calculator", onkeypress: move |evt| state.write().handle_keydown(evt),
-                    div { class: "calculator-display", state.read().formatted_display() }
+                    div { class: "calculator-display", {state.read().formatted_display()} }
                     div { class: "calculator-keypad",
                         div { class: "input-keys",
                             div { class: "function-keys",
@@ -74,14 +76,14 @@ fn app(cx: Scope) -> Element {
                                     onclick: move |_|  state.write().input_dot(),
                                     "●"
                                 }
-                                (1..10).map(move |k| rsx!{
+                                for k in 1..10 {
                                     CalculatorKey {
                                         key: "{k}",
                                         name: "key-{k}",
                                         onclick: move |_| state.write().input_digit(k),
                                         "{k}"
                                     }
-                                })
+                                }
                             }
                         }
                         div { class: "operator-keys",
@@ -130,7 +132,7 @@ fn CalculatorKey<'a>(cx: Scope<'a, CalculatorKeyProps<'a>>) -> Element {
         button {
             class: "calculator-key {cx.props.name}",
             onclick: move |e| cx.props.onclick.call(e),
-            &cx.props.children
+            {&cx.props.children}
         }
     })
 }

+ 1 - 1
examples/pattern_reducer.rs

@@ -20,7 +20,7 @@ fn app(cx: Scope) -> Element {
     cx.render(rsx!(
         div {
             h1 {"Select an option"}
-            h3 { "The radio is... ", state.is_playing(), "!" }
+            h3 { "The radio is... ", {state.is_playing()}, "!" }
             button { onclick: move |_| state.make_mut().reduce(PlayerAction::Pause),
                 "Pause"
             }

+ 2 - 2
examples/query_segments_demo/src/main.rs

@@ -66,7 +66,7 @@ impl FromQuery for ManualBlogQuerySegments {
 fn BlogPost(cx: Scope, query_params: ManualBlogQuerySegments) -> Element {
     render! {
         div{"This is your blogpost with a query segment:"}
-        div{format!("{:?}", query_params)}
+        div{ "{query_params:?}" }
     }
 }
 
@@ -74,7 +74,7 @@ fn BlogPost(cx: Scope, query_params: ManualBlogQuerySegments) -> Element {
 fn AutomaticBlogPost(cx: Scope, name: String, surname: String) -> Element {
     render! {
         div{"This is your blogpost with a query segment:"}
-        div{format!("name={}&surname={}", name, surname)}
+        div{ "name={name}&surname={surname}" }
     }
 }
 

+ 8 - 8
examples/rsx_compile_fail.rs

@@ -36,17 +36,17 @@ fn example(cx: Scope) -> Element {
             div { id: "asd",
                 "your neighborhood spiderman"
 
-                items.iter().cycle().take(5).map(|f| rsx!{
-                    div { "{f.a}" }
-                })
+                for item in items.iter().cycle().take(5) {
+                    div { "{item.a}" }
+                }
 
-                things_list.iter().map(|f| rsx!{
-                    div { "{f.a}" "{f.b}" }
-                })
+                for thing in things_list.iter() {
+                    div { "{thing.a}" "{thing.b}" }
+                }
 
-                mything_read.as_ref().map(|f| rsx! {
+                if let Some(f) = mything_read.as_ref() {
                     div { "{f}" }
-                })
+                }
             }
         }
     ))

+ 17 - 16
examples/rsx_usage.rs

@@ -61,7 +61,7 @@ fn App(cx: Scope) -> Element {
             h1 {"Some text"}
             h1 {"Some text with {formatting}"}
             h1 {"Formatting basic expressions {formatting_tuple.0} and {formatting_tuple.1}"}
-            h1 {"Formatting without interpolation " formatting_tuple.0 "and" formatting_tuple.1 }
+            h1 {"Formatting without interpolation " {formatting_tuple.0} "and" {formatting_tuple.1} }
             h2 {
                 "Multiple"
                 "Text"
@@ -94,10 +94,10 @@ fn App(cx: Scope) -> Element {
             }
 
             // Expressions can be used in element position too:
-            rsx!(p { "More templating!" }),
+            {rsx!(p { "More templating!" })},
 
             // Iterators
-            (0..10).map(|i| rsx!(li { "{i}" })),
+            {(0..10).map(|i| rsx!(li { "{i}" }))},
 
             // Iterators within expressions
             {
@@ -117,24 +117,25 @@ fn App(cx: Scope) -> Element {
             // Conditional rendering
             // Dioxus conditional rendering is based around None/Some. We have no special syntax for conditionals.
             // You can convert a bool condition to rsx! with .then and .or
-            true.then(|| rsx!(div {})),
+            {true.then(|| rsx!(div {}))},
 
             // Alternatively, you can use the "if" syntax - but both branches must be resolve to Element
             if false {
-                rsx!(h1 {"Top text"})
+                h1 {"Top text"}
             } else {
-                rsx!(h1 {"Bottom text"})
+                h1 {"Bottom text"}
             }
 
             // Using optionals for diverging branches
-            if true {
+            // Note that since this is wrapped in curlies, it's interpreted as an expression
+            {if true {
                 Some(rsx!(h1 {"Top text"}))
             } else {
                 None
-            }
+            }}
 
             // returning "None" without a diverging branch is a bit noisy... but rare in practice
-            None as Option<()>,
+            {None as Option<()>},
 
             // can also just use empty fragments
             Fragment {}
@@ -169,13 +170,13 @@ fn App(cx: Scope) -> Element {
 
             // Can pass in props directly as an expression
             {
-                let props = TallerProps {a: "hello", children: cx.render(rsx!(()))};
+                let props = TallerProps {a: "hello", children: None };
                 rsx!(Taller { ..props })
             }
 
             // Spreading can also be overridden manually
             Taller {
-                ..TallerProps { a: "ballin!", children: cx.render(rsx!(()) )},
+                ..TallerProps { a: "ballin!", children: None },
                 a: "not ballin!"
             }
 
@@ -204,16 +205,16 @@ fn App(cx: Scope) -> Element {
 
             // helper functions
             // Anything that implements IntoVnode can be dropped directly into Rsx
-            helper(cx, "hello world!")
+            {helper(cx, "hello world!")}
 
             // Strings can be supplied directly
-            String::from("Hello world!")
+            {String::from("Hello world!")}
 
             // So can format_args
-            format_args!("Hello {}!", "world")
+            {format_args!("Hello {}!", "world")}
 
             // Or we can shell out to a helper function
-            format_dollars(10, 50)
+            {format_dollars(10, 50)}
         }
     })
 }
@@ -269,7 +270,7 @@ pub struct TallerProps<'a> {
 #[component]
 pub fn Taller<'a>(cx: Scope<'a, TallerProps<'a>>) -> Element {
     cx.render(rsx! {
-        &cx.props.children
+        {&cx.props.children}
     })
 }
 

+ 4 - 2
examples/scroll_to_top.rs

@@ -22,8 +22,10 @@ fn app(cx: Scope) -> Element {
 
             button {
                 onclick: move |_| {
-                    if let Some(header) = header_element.read().as_ref() {
-                        header.scroll_to(ScrollBehavior::Smooth);
+                    if let Some(header) = header_element.read().as_ref().cloned() {
+                        cx.spawn(async move {
+                            let _ = header.scroll_to(ScrollBehavior::Smooth).await;
+                        });
                     }
                 },
                 "Scroll to top"

+ 13 - 16
examples/shared_state.rs

@@ -51,26 +51,23 @@ pub fn App(cx: Scope) -> Element {
 
 #[component]
 fn DataEditor(cx: Scope, id: usize) -> Element {
-    let cool_data = use_shared_state::<CoolData>(cx).unwrap().read();
+    let data = use_shared_state::<CoolData>(cx)?;
 
-    let my_data = &cool_data.view(id).unwrap();
-
-    render!(p {
-        "{my_data}"
-    })
+    render! {
+        p {
+            {data.read().view(id)?}
+        }
+    }
 }
 
 #[component]
 fn DataView(cx: Scope, id: usize) -> Element {
-    let cool_data = use_shared_state::<CoolData>(cx).unwrap();
-
-    let oninput = |e: FormEvent| cool_data.write().set(*id, e.value());
+    let data = use_shared_state::<CoolData>(cx)?;
 
-    let cool_data = cool_data.read();
-    let my_data = &cool_data.view(id).unwrap();
-
-    render!(input {
-        oninput: oninput,
-        value: "{my_data}"
-    })
+    render! {
+        input {
+            oninput: move |e: FormEvent| data.write().set(*id, e.value()),
+            value: data.read().view(id)?
+        }
+    }
 }

+ 45 - 0
examples/shorthand.rs

@@ -0,0 +1,45 @@
+use dioxus::prelude::*;
+
+fn main() {
+    dioxus_desktop::launch(app);
+}
+
+fn app(cx: Scope) -> Element {
+    let a = 123;
+    let b = 456;
+    let c = 789;
+    let class = "class";
+    let id = "id";
+
+    // todo: i'd like it for children on elements to be inferred as the children of the element
+    // also should shorthands understand references/dereferences?
+    // ie **a, *a, &a, &mut a, etc
+    let children = render! { "Child" };
+    let onclick = move |_| println!("Clicked!");
+
+    render! {
+        div { class, id, {&children} }
+        Component { a, b, c, children, onclick }
+        Component { a, ..ComponentProps { a: 1, b: 2, c: 3, children: None, onclick: Default::default() } }
+    }
+}
+
+#[component]
+fn Component<'a>(
+    cx: Scope<'a>,
+    a: i32,
+    b: i32,
+    c: i32,
+    children: Element<'a>,
+    onclick: EventHandler<'a, ()>,
+) -> Element {
+    render! {
+        div { "{a}" }
+        div { "{b}" }
+        div { "{c}" }
+        div { {children} }
+        div {
+            onclick: move |_| onclick.call(()),
+        }
+    }
+}

+ 3 - 3
examples/signals.rs

@@ -31,7 +31,7 @@ fn app(cx: Scope) -> Element {
 
         // We can do boolean operations on the current signal value
         if count.value() > 5 {
-            rsx!{ h2 { "High five!" } }
+            h2 { "High five!" }
         }
 
         // We can cleanly map signals with iterators
@@ -41,9 +41,9 @@ fn app(cx: Scope) -> Element {
 
         // We can also use the signal value as a slice
         if let [ref first, .., ref last] = saved_values.read().as_slice() {
-            rsx! { li { "First and last: {first}, {last}" } }
+            li { "First and last: {first}, {last}" }
         } else {
-            rsx! { "No saved values" }
+            "No saved values"
         }
     })
 }

+ 5 - 5
examples/simple_list.rs

@@ -8,17 +8,17 @@ fn app(cx: Scope) -> Element {
     cx.render(rsx!(
         div {
             // Use Map directly to lazily pull elements
-            (0..10).map(|f| rsx! { "{f}" }),
+            {(0..10).map(|f| rsx! { "{f}" })},
 
             // Collect into an intermediate collection if necessary, and call into_iter
-            ["a", "b", "c", "d", "e", "f"]
+            {["a", "b", "c", "d", "e", "f"]
                 .into_iter()
                 .map(|f| rsx! { "{f}" })
                 .collect::<Vec<_>>()
-                .into_iter(),
+                .into_iter()},
 
             // Use optionals
-            Some(rsx! { "Some" }),
+            {Some(rsx! { "Some" })},
 
             // use a for loop where the body itself is RSX
             for name in 0..10 {
@@ -27,7 +27,7 @@ fn app(cx: Scope) -> Element {
 
             // Or even use an unterminated conditional
             if true {
-                rsx!{ "hello world!" }
+                "hello world!"
             }
         }
     ))

+ 1 - 1
examples/svg.rs

@@ -97,7 +97,7 @@ pub fn Die<'a>(cx: Scope<'a, DieProps<'a>>) -> Element {
           fill: "{fill}",
         }
 
-        dots
+        {dots}
       }
     })
 }

+ 33 - 35
examples/todomvc.rs

@@ -52,30 +52,30 @@ pub fn app(cx: Scope<()>) -> Element {
             TodoHeader { todos: todos }
             section { class: "main",
                 if !todos.is_empty() {
-                    rsx! {
-                        input {
-                            id: "toggle-all",
-                            class: "toggle-all",
-                            r#type: "checkbox",
-                            onchange: move |_| {
-                                let check = active_todo_count != 0;
-                                for (_, item) in todos.make_mut().iter_mut() {
-                                    item.checked = check;
-                                }
-                            },
-                            checked: if active_todo_count == 0 { "true" } else { "false" },
-                        }
-                        label { r#for: "toggle-all" }
+                    input {
+                        id: "toggle-all",
+                        class: "toggle-all",
+                        r#type: "checkbox",
+                        onchange: move |_| {
+                            let check = active_todo_count != 0;
+                            for (_, item) in todos.make_mut().iter_mut() {
+                                item.checked = check;
+                            }
+                        },
+                        checked: if active_todo_count == 0 { "true" } else { "false" },
                     }
+                    label { r#for: "toggle-all" }
                 }
                 ul { class: "todo-list",
-                    filtered_todos.iter().map(|id| rsx!(TodoEntry {
-                        key: "{id}",
-                        id: *id,
-                        todos: todos,
-                    }))
+                    for id in filtered_todos.iter() {
+                        TodoEntry {
+                            key: "{id}",
+                            id: *id,
+                            todos: todos,
+                        }
+                    }
                 }
-                (!todos.is_empty()).then(|| rsx!(
+                if !todos.is_empty() {
                     ListFooter {
                         active_todo_count: active_todo_count,
                         active_todo_text: active_todo_text,
@@ -83,7 +83,7 @@ pub fn app(cx: Scope<()>) -> Element {
                         todos: todos,
                         filter: filter,
                     }
-                ))
+                }
             }
         }
         PageFooter {}
@@ -172,7 +172,7 @@ pub fn TodoEntry<'a>(cx: Scope<'a, TodoEntryProps<'a>>) -> Element {
                     prevent_default: "onclick"
                 }
             }
-            is_editing.then(|| rsx!{
+            if **is_editing {
                 input {
                     class: "edit",
                     value: "{todo.contents}",
@@ -186,7 +186,7 @@ pub fn TodoEntry<'a>(cx: Scope<'a, TodoEntryProps<'a>>) -> Element {
                         }
                     },
                 }
-            })
+            }
         }
     })
 }
@@ -220,29 +220,27 @@ pub fn ListFooter<'a>(cx: Scope<'a, ListFooterProps<'a>>) -> Element {
             }
             ul { class: "filters",
                 for (state , state_text , url) in [
-    (FilterState::All, "All", "#/"),
-    (FilterState::Active, "Active", "#/active"),
-    (FilterState::Completed, "Completed", "#/completed"),
-] {
+                    (FilterState::All, "All", "#/"),
+                    (FilterState::Active, "Active", "#/active"),
+                    (FilterState::Completed, "Completed", "#/completed"),
+                ] {
                     li {
                         a {
                             href: url,
                             class: selected(state),
                             onclick: move |_| cx.props.filter.set(state),
                             prevent_default: "onclick",
-                            state_text
+                            {state_text}
                         }
                     }
                 }
             }
             if cx.props.show_clear_completed {
-                cx.render(rsx! {
-                    button {
-                        class: "clear-completed",
-                        onclick: move |_| cx.props.todos.make_mut().retain(|_, todo| !todo.checked),
-                        "Clear completed"
-                    }
-                })
+                button {
+                    class: "clear-completed",
+                    onclick: move |_| cx.props.todos.make_mut().retain(|_, todo| !todo.checked),
+                    "Clear completed"
+                }
             }
         }
     })

+ 4 - 0
packages/autofmt/src/component.rs

@@ -182,6 +182,9 @@ impl Writer<'_> {
                         s.source.as_ref().unwrap().to_token_stream()
                     )?;
                 }
+                ContentField::Shorthand(e) => {
+                    write!(self.out, "{}", e.to_token_stream())?;
+                }
                 ContentField::OnHandlerRaw(exp) => {
                     let out = prettyplease::unparse_expr(exp);
                     let mut lines = out.split('\n').peekable();
@@ -223,6 +226,7 @@ impl Writer<'_> {
             .iter()
             .map(|field| match &field.content {
                 ContentField::Formatted(s) => ifmt_to_string(s).len() ,
+                ContentField::Shorthand(e) => e.to_token_stream().to_string().len(),
                 ContentField::OnHandlerRaw(exp) | ContentField::ManExpr(exp) => {
                     let formatted = prettyplease::unparse_expr(exp);
                     let len = if formatted.contains('\n') {

+ 3 - 0
packages/autofmt/src/element.rs

@@ -237,6 +237,9 @@ impl Writer<'_> {
             ElementAttrValue::AttrLiteral(value) => {
                 write!(self.out, "{value}", value = ifmt_to_string(value))?;
             }
+            ElementAttrValue::Shorthand(value) => {
+                write!(self.out, "{value}",)?;
+            }
             ElementAttrValue::AttrExpr(value) => {
                 let out = prettyplease::unparse_expr(value);
                 let mut lines = out.split('\n').peekable();

+ 46 - 4
packages/autofmt/src/writer.rs

@@ -1,11 +1,11 @@
-use dioxus_rsx::{AttributeType, BodyNode, ElementAttrValue, ForLoop};
+use dioxus_rsx::{AttributeType, BodyNode, ElementAttrValue, ForLoop, IfChain};
 use proc_macro2::{LineColumn, Span};
 use quote::ToTokens;
 use std::{
     collections::{HashMap, VecDeque},
     fmt::{Result, Write},
 };
-use syn::{spanned::Spanned, Expr, ExprIf};
+use syn::{spanned::Spanned, Expr};
 
 use crate::buffer::Buffer;
 use crate::ifmt_to_string;
@@ -142,6 +142,7 @@ impl<'a> Writer<'a> {
             }
             ElementAttrValue::AttrLiteral(lit) => ifmt_to_string(lit).len(),
             ElementAttrValue::AttrExpr(expr) => expr.span().line_length(),
+            ElementAttrValue::Shorthand(expr) => expr.span().line_length(),
             ElementAttrValue::EventTokens(tokens) => {
                 let location = Location::new(tokens.span().start());
 
@@ -231,8 +232,49 @@ impl<'a> Writer<'a> {
         Ok(())
     }
 
-    fn write_if_chain(&mut self, ifchain: &ExprIf) -> std::fmt::Result {
-        self.write_raw_expr(ifchain.span())
+    fn write_if_chain(&mut self, ifchain: &IfChain) -> std::fmt::Result {
+        // Recurse in place by setting the next chain
+        let mut branch = Some(ifchain);
+
+        while let Some(chain) = branch {
+            let IfChain {
+                if_token,
+                cond,
+                then_branch,
+                else_if_branch,
+                else_branch,
+            } = chain;
+
+            write!(
+                self.out,
+                "{} {} {{",
+                if_token.to_token_stream(),
+                prettyplease::unparse_expr(cond)
+            )?;
+
+            self.write_body_indented(then_branch)?;
+
+            if let Some(else_if_branch) = else_if_branch {
+                // write the closing bracket and else
+                self.out.tabbed_line()?;
+                write!(self.out, "}} else ")?;
+
+                branch = Some(else_if_branch);
+            } else if let Some(else_branch) = else_branch {
+                self.out.tabbed_line()?;
+                write!(self.out, "}} else {{")?;
+
+                self.write_body_indented(else_branch)?;
+                branch = None;
+            } else {
+                branch = None;
+            }
+        }
+
+        self.out.tabbed_line()?;
+        write!(self.out, "}}")?;
+
+        Ok(())
     }
 }
 

+ 3 - 3
packages/autofmt/tests/samples/commentshard.rsx

@@ -8,17 +8,17 @@ rsx! {
         "hello world"
 
         // Comments
-        expr1,
+        {expr1},
 
         // Comments
-        expr2,
+        {expr2},
 
         // Comments
         // Comments
         // Comments
         // Comments
         // Comments
-        expr3,
+        {expr3},
 
         div {
             // todo some work in here

+ 4 - 4
packages/autofmt/tests/samples/complex.rsx

@@ -18,7 +18,7 @@ rsx! {
             to: "{to}",
             span { class: "inline-block mr-3", icons::icon_0 {} }
             span { "{name}" }
-            children.is_some().then(|| rsx! {
+            {children.is_some().then(|| rsx! {
                 span {
                     class: "inline-block ml-auto hover:bg-gray-500",
                     onclick: move |evt| {
@@ -27,9 +27,9 @@ rsx! {
                     },
                     icons::icon_8 {}
                 }
-            })
+            })}
         }
-        div { class: "px-4", is_current.then(|| rsx!{ children }) }
+        div { class: "px-4", {is_current.then(|| rsx!{ children })} }
     }
 
     // No nesting
@@ -48,5 +48,5 @@ rsx! {
         }
     }
 
-    div { asdbascasdbasd, asbdasbdabsd, asbdabsdbasdbas }
+    div { "asdbascasdbasd", "asbdasbdabsd", {asbdabsdbasdbas} }
 }

+ 15 - 1
packages/autofmt/tests/samples/ifchain_forloop.rsx

@@ -8,6 +8,20 @@ rsx! {
     // Some ifchain
     if a > 10 {
         //
-        rsx! { div {} }
+        div {}
+    } else if a > 20 {
+        h1 {}
+    } else if a > 20 {
+        h1 {}
+    } else if a > 20 {
+        h1 {}
+    } else if a > 20 {
+        h1 {}
+    } else if a > 20 {
+        h1 {}
+    } else if a > 20 {
+        h1 {}
+    } else {
+        h3 {}
     }
 }

+ 1 - 1
packages/autofmt/tests/samples/immediate_expr.rsx

@@ -1,4 +1,4 @@
 fn it_works() {
-    cx.render(rsx!(()))
+    cx.render(rsx!({()}))
 }
 

+ 4 - 4
packages/autofmt/tests/samples/long.rsx

@@ -11,7 +11,7 @@ pub fn Explainer<'a>(
     // pt-5 sm:pt-24 lg:pt-24
 
     let mut right = rsx! {
-        div { class: "relative w-1/2", flasher }
+        div { class: "relative w-1/2", {flasher} }
     };
 
     let align = match invert {
@@ -24,7 +24,7 @@ pub fn Explainer<'a>(
             h2 { class: "mb-6 text-3xl leading-tight md:text-4xl md:leading-tight lg:text-3xl lg:leading-tight font-heading font-mono font-bold",
                 "{title}"
             }
-            content
+            {content}
         }
     };
 
@@ -34,8 +34,8 @@ pub fn Explainer<'a>(
 
     cx.render(rsx! {
         div { class: "flex flex-wrap items-center dark:text-white py-16 border-t font-light",
-            left,
-            right
+            {left},
+            {right}
         }
     })
 }

+ 1 - 1
packages/autofmt/tests/samples/long_exprs.rsx

@@ -6,7 +6,7 @@ rsx! {
                     section { class: "body-font overflow-hidden dark:bg-ideblack",
                         div { class: "container px-6 mx-auto",
                             div { class: "-my-8 divide-y-2 divide-gray-100",
-                                POSTS.iter().enumerate().map(|(id, post)| rsx! { BlogPostItem { post: post, id: id } })
+                                {POSTS.iter().enumerate().map(|(id, post)| rsx! { BlogPostItem { post: post, id: id } })}
                             }
                         }
                     }

+ 5 - 5
packages/autofmt/tests/samples/multirsx.rsx

@@ -4,20 +4,20 @@ rsx! {
     div {}
 
     // hi
-    div { abcd, ball, s }
+    div { "abcd", "ball", "s" }
 
     //
     //
     //
-    div { abcd, ball, s }
+    div { "abcd", "ball", "s" }
 
     //
     //
     //
     div {
-        abcd,
-        ball,
-        s,
+        "abcd"
+        "ball"
+        "s"
 
         //
         "asdasd"

+ 1 - 1
packages/autofmt/tests/samples/trailing_expr.rsx

@@ -1,7 +1,7 @@
 fn it_works() {
     cx.render(rsx! {
         div {
-            span { "Description: ", package.description.as_deref().unwrap_or("❌❌❌❌ missing") }
+            span { "Description: ", {package.description.as_deref().unwrap_or("❌❌❌❌ missing")} }
         }
     })
 }

+ 2 - 2
packages/autofmt/tests/wrong/multiexpr-4sp.rsx

@@ -1,8 +1,8 @@
 fn ItWroks() {
     cx.render(rsx! {
         div { class: "flex flex-wrap items-center dark:text-white py-16 border-t font-light",
-            left,
-            right
+            {left},
+            {right}
         }
     })
 }

+ 1 - 1
packages/autofmt/tests/wrong/multiexpr-4sp.wrong.rsx

@@ -1,5 +1,5 @@
 fn ItWroks() {
     cx.render(rsx! {
-        div { class: "flex flex-wrap items-center dark:text-white py-16 border-t font-light", left, right }
+        div { class: "flex flex-wrap items-center dark:text-white py-16 border-t font-light", {left}, {right} }
     })
 }

+ 2 - 2
packages/autofmt/tests/wrong/multiexpr-tab.rsx

@@ -1,8 +1,8 @@
 fn ItWroks() {
 	cx.render(rsx! {
 		div { class: "flex flex-wrap items-center dark:text-white py-16 border-t font-light",
-			left,
-			right
+			{left},
+			{right}
 		}
 	})
 }

+ 1 - 1
packages/autofmt/tests/wrong/multiexpr-tab.wrong.rsx

@@ -1,5 +1,5 @@
 fn ItWroks() {
 	cx.render(rsx! {
-		div { class: "flex flex-wrap items-center dark:text-white py-16 border-t font-light", left, right }
+		div { class: "flex flex-wrap items-center dark:text-white py-16 border-t font-light", {left}, {right} }
 	})
 }

+ 2 - 2
packages/cli/src/cli/autoformat.rs

@@ -267,10 +267,10 @@ async fn test_auto_fmt() {
     let test_rsx = r#"
                     //
 
-                    rsx! {
+
 
                         div {}
-                    }
+
 
                     //
                     //

+ 16 - 1
packages/core/src/nodes.rs

@@ -804,6 +804,15 @@ impl<'a, 'b> IntoDynNode<'b> for &'a str {
     }
 }
 
+impl IntoDynNode<'_> for &String {
+    fn into_dyn_node(self, cx: &ScopeState) -> DynamicNode {
+        DynamicNode::Text(VText {
+            value: cx.bump().alloc_str(self),
+            id: Default::default(),
+        })
+    }
+}
+
 impl IntoDynNode<'_> for String {
     fn into_dyn_node(self, cx: &ScopeState) -> DynamicNode {
         DynamicNode::Text(VText {
@@ -868,7 +877,7 @@ where
         nodes.extend(self.into_iter().map(|node| node.into_vnode(cx)));
 
         match nodes.into_bump_slice() {
-            children if children.is_empty() => DynamicNode::default(),
+            [] => DynamicNode::default(),
             children => DynamicNode::Fragment(children),
         }
     }
@@ -898,6 +907,12 @@ impl<'a> IntoAttributeValue<'a> for String {
     }
 }
 
+impl<'a> IntoAttributeValue<'a> for &String {
+    fn into_value(self, cx: &'a Bump) -> AttributeValue<'a> {
+        AttributeValue::Text(cx.alloc_str(self))
+    }
+}
+
 impl<'a> IntoAttributeValue<'a> for f64 {
     fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
         AttributeValue::Float(self)

+ 4 - 4
packages/core/src/scope_context.rs

@@ -117,7 +117,7 @@ impl ScopeContext {
         }
 
         let mut search_parent = self.parent_id;
-        match with_runtime(|runtime: &crate::runtime::Runtime| {
+        let cur_runtime = with_runtime(|runtime: &crate::runtime::Runtime| {
             while let Some(parent_id) = search_parent {
                 let parent = runtime.get_context(parent_id).unwrap();
                 tracing::trace!(
@@ -135,9 +135,9 @@ impl ScopeContext {
                 search_parent = parent.parent_id;
             }
             None
-        })
-        .flatten()
-        {
+        });
+
+        match cur_runtime.flatten() {
             Some(ctx) => Some(ctx),
             None => {
                 tracing::trace!(

+ 1 - 1
packages/core/src/virtual_dom.rs

@@ -78,7 +78,7 @@ use std::{
 /// #[component]
 /// fn Title<'a>(cx: Scope<'a>, children: Element<'a>) -> Element {
 ///     cx.render(rsx! {
-///         div { id: "title", children }
+///         div { id: "title", {children} }
 ///     })
 /// }
 /// ```

+ 4 - 4
packages/core/tests/create_dom.rs

@@ -84,7 +84,7 @@ fn create() {
 fn create_list() {
     let mut dom = VirtualDom::new(|cx| {
         cx.render(rsx! {
-            (0..3).map(|f| rsx!( div { "hello" } ))
+            {(0..3).map(|f| rsx!( div { "hello" } ))}
         })
     });
 
@@ -148,7 +148,7 @@ fn create_components() {
     fn Child<'a>(cx: Scope<'a, ChildProps<'a>>) -> Element {
         cx.render(rsx! {
             h1 {}
-            div { &cx.props.children }
+            div { {&cx.props.children} }
             p {}
         })
     }
@@ -163,10 +163,10 @@ fn anchors() {
     let mut dom = VirtualDom::new(|cx| {
         cx.render(rsx! {
             if true {
-                rsx!( div { "hello" } )
+                div { "hello" }
             }
             if false {
-                rsx!( div { "goodbye" } )
+                div { "goodbye" }
             }
         })
     });

+ 9 - 9
packages/core/tests/create_fragments.rs

@@ -7,7 +7,7 @@ use dioxus_core::ElementId;
 #[test]
 fn empty_fragment_creates_nothing() {
     fn app(cx: Scope) -> Element {
-        cx.render(rsx!(()))
+        cx.render(rsx!({}))
     }
 
     let mut vdom = VirtualDom::new(app);
@@ -43,18 +43,18 @@ fn fragments_nested() {
         cx.render(rsx!(
             div { "hello" }
             div { "goodbye" }
-            rsx! {
+            {rsx! {
                 div { "hello" }
                 div { "goodbye" }
-                rsx! {
+                {rsx! {
                     div { "hello" }
                     div { "goodbye" }
-                    rsx! {
+                    {rsx! {
                         div { "hello" }
                         div { "goodbye" }
-                    }
-                }
-            }
+                    }}
+                }}
+            }}
         ))
     });
 
@@ -79,7 +79,7 @@ fn fragments_across_components() {
         let world = "world";
         cx.render(rsx! {
             "hellO!"
-            world
+            {world}
         })
     }
 
@@ -94,7 +94,7 @@ fn list_fragments() {
     fn app(cx: Scope) -> Element {
         cx.render(rsx!(
             h1 {"hello"}
-            (0..6).map(|f| rsx!( span { "{f}" }))
+            {(0..6).map(|f| rsx!( span { "{f}" }))}
         ))
     }
     assert_eq!(

+ 2 - 2
packages/core/tests/create_lists.rs

@@ -11,12 +11,12 @@ use dioxus_core::ElementId;
 fn app(cx: Scope) -> Element {
     cx.render(rsx! {
         div {
-            (0..3).map(|i| rsx! {
+            for i in 0..3 {
                 div {
                     h1 { "hello world! "}
                     p { "{i}" }
                 }
-            })
+            }
         }
     })
 }

+ 2 - 2
packages/core/tests/create_passthru.rs

@@ -20,7 +20,7 @@ fn nested_passthru_creates() {
 
     #[component]
     fn PassThru<'a>(cx: Scope<'a>, children: Element<'a>) -> Element {
-        cx.render(rsx!(children))
+        cx.render(rsx!({ children }))
     }
 
     let mut dom = VirtualDom::new(App);
@@ -60,7 +60,7 @@ fn nested_passthru_creates_add() {
 
     #[component]
     fn ChildComp<'a>(cx: Scope, children: Element<'a>) -> Element {
-        cx.render(rsx! { children })
+        cx.render(rsx! { {children} })
     }
 
     let mut dom = VirtualDom::new(App);

+ 1 - 1
packages/core/tests/diff_component.rs

@@ -41,7 +41,7 @@ fn component_swap() {
         cx.render(rsx! {
             h1 {
                 "NavBar"
-                (0..3).map(|_| rsx!(nav_link {}))
+                {(0..3).map(|_| rsx!(nav_link {}))}
             }
         })
     }

+ 12 - 12
packages/core/tests/diff_keyed_list.rs

@@ -17,7 +17,7 @@ fn keyed_diffing_out_of_order() {
             _ => unreachable!(),
         };
 
-        cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" }))))
+        cx.render(rsx!({ order.iter().map(|i| rsx!(div { key: "{i}" })) }))
     });
 
     {
@@ -59,7 +59,7 @@ fn keyed_diffing_out_of_order_adds() {
             _ => unreachable!(),
         };
 
-        cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" }))))
+        cx.render(rsx!({ order.iter().map(|i| rsx!(div { key: "{i}" })) }))
     });
 
     _ = dom.rebuild();
@@ -85,7 +85,7 @@ fn keyed_diffing_out_of_order_adds_3() {
             _ => unreachable!(),
         };
 
-        cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" }))))
+        cx.render(rsx!({ order.iter().map(|i| rsx!(div { key: "{i}" })) }))
     });
 
     _ = dom.rebuild();
@@ -111,7 +111,7 @@ fn keyed_diffing_out_of_order_adds_4() {
             _ => unreachable!(),
         };
 
-        cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" }))))
+        cx.render(rsx!({ order.iter().map(|i| rsx!(div { key: "{i}" })) }))
     });
 
     _ = dom.rebuild();
@@ -137,7 +137,7 @@ fn keyed_diffing_out_of_order_adds_5() {
             _ => unreachable!(),
         };
 
-        cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" }))))
+        cx.render(rsx!({ order.iter().map(|i| rsx!(div { key: "{i}" })) }))
     });
 
     _ = dom.rebuild();
@@ -162,7 +162,7 @@ fn keyed_diffing_additions() {
             _ => unreachable!(),
         };
 
-        cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" }))))
+        cx.render(rsx!({ order.iter().map(|i| rsx!(div { key: "{i}" })) }))
     });
 
     _ = dom.rebuild();
@@ -187,7 +187,7 @@ fn keyed_diffing_additions_and_moves_on_ends() {
             _ => unreachable!(),
         };
 
-        cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" }))))
+        cx.render(rsx!({ order.iter().map(|i| rsx!(div { key: "{i}" })) }))
     });
 
     _ = dom.rebuild();
@@ -216,7 +216,7 @@ fn keyed_diffing_additions_and_moves_in_middle() {
             _ => unreachable!(),
         };
 
-        cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" }))))
+        cx.render(rsx!({ order.iter().map(|i| rsx!(div { key: "{i}" })) }))
     });
 
     _ = dom.rebuild();
@@ -250,7 +250,7 @@ fn controlled_keyed_diffing_out_of_order() {
             _ => unreachable!(),
         };
 
-        cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" }))))
+        cx.render(rsx!({ order.iter().map(|i| rsx!(div { key: "{i}" })) }))
     });
 
     _ = dom.rebuild();
@@ -284,7 +284,7 @@ fn controlled_keyed_diffing_out_of_order_max_test() {
             _ => unreachable!(),
         };
 
-        cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" }))))
+        cx.render(rsx!({ order.iter().map(|i| rsx!(div { key: "{i}" })) }))
     });
 
     _ = dom.rebuild();
@@ -313,7 +313,7 @@ fn remove_list() {
             _ => unreachable!(),
         };
 
-        cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" }))))
+        cx.render(rsx!({ order.iter().map(|i| rsx!(div { key: "{i}" })) }))
     });
 
     _ = dom.rebuild();
@@ -338,7 +338,7 @@ fn no_common_keys() {
             _ => unreachable!(),
         };
 
-        cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" }))))
+        cx.render(rsx!({ order.iter().map(|i| rsx!(div { key: "{i}" })) }))
     });
 
     _ = dom.rebuild();

+ 15 - 13
packages/core/tests/diff_unkeyed_list.rs

@@ -9,9 +9,9 @@ fn list_creates_one_by_one() {
 
         cx.render(rsx! {
             div {
-                (0..gen).map(|i| rsx! {
+                for i in 0..gen {
                     div { "{i}" }
-                })
+                }
             }
         })
     });
@@ -78,9 +78,9 @@ fn removes_one_by_one() {
 
         cx.render(rsx! {
             div {
-                (0..gen).map(|i| rsx! {
+                for i in 0..gen {
                     div { "{i}" }
-                })
+                }
             }
         })
     });
@@ -153,10 +153,10 @@ fn list_shrink_multiroot() {
     let mut dom = VirtualDom::new(|cx| {
         cx.render(rsx! {
             div {
-                (0..cx.generation()).map(|i| rsx! {
+                for i in 0..cx.generation() {
                     div { "{i}" }
                     div { "{i}" }
-                })
+                }
             }
         })
     });
@@ -214,10 +214,10 @@ fn removes_one_by_one_multiroot() {
 
         cx.render(rsx! {
             div {
-                (0..gen).map(|i| rsx! {
+                {(0..gen).map(|i| rsx! {
                     div { "{i}" }
                     div { "{i}" }
-                })
+                })}
             }
         })
     });
@@ -276,9 +276,9 @@ fn removes_one_by_one_multiroot() {
 fn two_equal_fragments_are_equal_static() {
     let mut dom = VirtualDom::new(|cx| {
         cx.render(rsx! {
-            (0..5).map(|_| rsx! {
+            for _ in 0..5 {
                 div { "hello" }
-            })
+            }
         })
     });
 
@@ -290,9 +290,9 @@ fn two_equal_fragments_are_equal_static() {
 fn two_equal_fragments_are_equal() {
     let mut dom = VirtualDom::new(|cx| {
         cx.render(rsx! {
-            (0..5).map(|i| rsx! {
+            for i in 0..5 {
                 div { "hello {i}" }
-            })
+            }
         })
     });
 
@@ -311,7 +311,9 @@ fn remove_many() {
         };
 
         cx.render(rsx! {
-            (0..num).map(|i| rsx! { div { "hello {i}" } })
+            for i in 0..num {
+                div { "hello {i}" }
+            }
         })
     });
 

+ 2 - 2
packages/core/tests/event_propagation.rs

@@ -58,11 +58,11 @@ fn app(cx: Scope) -> Element {
                 *CLICKS.lock().unwrap() += 1;
             },
 
-            vec![
+            {vec![
                 render! {
                     problematic_child {}
                 }
-            ].into_iter()
+            ].into_iter()}
         }
     }
 }

+ 1 - 1
packages/core/tests/lifecycle.rs

@@ -51,7 +51,7 @@ fn events_generate() {
                     "Click me!"
                 }
             }),
-            _ => cx.render(rsx!(())),
+            _ => None,
         }
     };
 

+ 2 - 2
packages/core/tests/miri_full_app.rs

@@ -39,9 +39,9 @@ fn App(cx: Scope) -> Element {
             }
             button { onclick: move |_| idx -= 1, "-" }
             ul {
-                (0..**idx).map(|i| rsx! {
+                {(0..**idx).map(|i| rsx! {
                     ChildExample { i: i, onhover: onhover }
-                })
+                })}
             }
         }
     })

+ 2 - 2
packages/core/tests/miri_stress.rs

@@ -15,7 +15,7 @@ fn test_memory_leak() {
         cx.spawn(async {});
 
         if val == 2 || val == 4 {
-            return cx.render(rsx!(()));
+            return render!({});
         }
 
         let name = cx.use_hook(|| String::from("numbers: "));
@@ -73,7 +73,7 @@ fn memo_works_properly() {
         let val = cx.generation();
 
         if val == 2 || val == 4 {
-            return cx.render(rsx!(()));
+            return None;
         }
 
         let name = cx.use_hook(|| String::from("asd"));

+ 1 - 1
packages/core/tests/task.rs

@@ -25,7 +25,7 @@ async fn it_works() {
             });
         });
 
-        cx.render(rsx!(()))
+        None
     }
 
     let mut dom = VirtualDom::new(app);

+ 1 - 1
packages/desktop/headless_tests/rendering.rs

@@ -89,7 +89,7 @@ fn check_html_renders(cx: Scope) -> Element {
                 h1 {
                     "text"
                 }
-                dyn_element
+                {dyn_element}
             }
         }
     }

+ 1 - 1
packages/dioxus-tui/examples/all_terminal_events.rs

@@ -71,7 +71,7 @@ fn app(cx: Scope) -> Element {
 
                 "Hover, click, type or scroll to see the info down below"
             }
-            div { width: "80%", height: "50%", flex_direction: "column", events_rendered }
+            div { width: "80%", height: "50%", flex_direction: "column", {events_rendered} }
         }
     })
 }

+ 23 - 33
packages/dioxus-tui/examples/buttons.rs

@@ -27,26 +27,25 @@ fn Button(cx: Scope<ButtonProps>) -> Element {
             height: "100%",
             background_color: "{color}",
             tabindex: "{cx.props.layer}",
-            onkeydown: |e| {
+            onkeydown: move |e| {
                 if let Code::Space = e.inner().code() {
                     toggle.modify(|f| !f);
                 }
             },
-            onclick: |_| {
+            onclick: move |_| {
                 toggle.modify(|f| !f);
             },
-            onmouseenter: |_|{
+            onmouseenter: move |_| {
                 hovered.set(true);
             },
-            onmouseleave: |_|{
+            onmouseleave: move |_|{
                 hovered.set(false);
             },
             justify_content: "center",
             align_items: "center",
             display: "flex",
             flex_direction: "column",
-
-            p{"tabindex: {cx.props.layer}"}
+            p{ "tabindex: {cx.props.layer}" }
         }
     })
 }
@@ -58,37 +57,28 @@ fn app(cx: Scope) -> Element {
             flex_direction: "column",
             width: "100%",
             height: "100%",
-
-            (1..8).map(|y|
-                rsx!{
-                    div{
-                        display: "flex",
-                        flex_direction: "row",
-                        width: "100%",
-                        height: "100%",
-                        (1..8).map(|x|{
-                            if (x + y) % 2 == 0{
-                                rsx!{
-                                    div{
-                                        width: "100%",
-                                        height: "100%",
-                                        background_color: "rgb(100, 100, 100)",
-                                    }
-                                }
+            for y in 1..8 {
+                div {
+                    display: "flex",
+                    flex_direction: "row",
+                    width: "100%",
+                    height: "100%",
+                    for x in 1..8 {
+                        if (x + y) % 2 == 0 {
+                            div {
+                                width: "100%",
+                                height: "100%",
+                                background_color: "rgb(100, 100, 100)",
                             }
-                            else{
-                                let layer = (x + y) % 3;
-                                rsx!{
-                                    Button{
-                                        color_offset: x * y,
-                                        layer: layer as u16,
-                                    }
-                                }
+                        } else {
+                            Button {
+                                color_offset: x * y,
+                                layer: ((x + y) % 3) as u16,
                             }
-                        })
+                        }
                     }
                 }
-            )
+            }
         }
     })
 }

+ 15 - 22
packages/dioxus-tui/examples/color_test.rs

@@ -14,32 +14,25 @@ fn app(cx: Scope) -> Element {
             width: "100%",
             height: "100%",
             flex_direction: "column",
-            (0..=steps).map(|x|
-                {
-                    let hue = x as f32*360.0/steps as f32;
-                    rsx! {
-                        div{
-                            width: "100%",
-                            height: "100%",
-                            flex_direction: "row",
-                            (0..=steps).map(|y|
-                                {
-                                    let alpha = y as f32*100.0/steps as f32;
-                                    rsx! {
-                                        div {
-                                            left: "{x}px",
-                                            top: "{y}px",
-                                            width: "10%",
-                                            height: "100%",
-                                            background_color: "hsl({hue}, 100%, 50%, {alpha}%)",
-                                        }
-                                    }
+            for x in 0..=steps {
+                div { width: "100%", height: "100%", flex_direction: "row",
+                    for y in 0..=steps {
+                        {
+                            let hue = x as f32*360.0/steps as f32;
+                            let alpha = y as f32*100.0/steps as f32;
+                            rsx! {
+                                div {
+                                    left: "{x}px",
+                                    top: "{y}px",
+                                    width: "10%",
+                                    height: "100%",
+                                    background_color: "hsl({hue}, 100%, 50%, {alpha}%)",
                                 }
-                            )
+                            }
                         }
                     }
                 }
-            )
+            }
         }
     })
 }

+ 2 - 2
packages/dioxus-tui/examples/list.rs

@@ -19,9 +19,9 @@ fn app(cx: Scope) -> Element {
             ul {
                 flex_direction: "column",
                 padding_left: "3px",
-                (0..10).map(|i| rsx!(
+                for i in 0..10 {
                     "> hello {i}"
-                ))
+                }
             }
         }
     })

+ 18 - 22
packages/dioxus-tui/examples/many_small_edit_stress.rs

@@ -71,32 +71,28 @@ fn Grid(cx: Scope<GridProps>) -> Element {
             width: "100%",
             height: "100%",
             flex_direction: "column",
-            (0..size).map(|x|
-                    {
-                    rsx! {
-                        div{
-                            width: "100%",
-                            height: "100%",
-                            flex_direction: "row",
-                            (0..size).map(|y|
-                                {
-                                    let alpha = y as f32*100.0/size as f32 + counts.read()[x*size + y] as f32;
-                                    let key = format!("{}-{}", x, y);
-                                    rsx! {
-                                        Box {
-                                            x: x,
-                                            y: y,
-                                            alpha: 100.0,
-                                            hue: alpha,
-                                            key: "{key}",
-                                        }
-                                    }
+            for x in 0..size {
+                div{
+                    width: "100%",
+                    height: "100%",
+                    flex_direction: "row",
+                    for y in 0..size {
+                        {
+                            let alpha = y as f32*100.0/size as f32 + counts.read()[x*size + y] as f32;
+                            let key = format!("{}-{}", x, y);
+                            rsx! {
+                                Box {
+                                    x: x,
+                                    y: y,
+                                    alpha: 100.0,
+                                    hue: alpha,
+                                    key: "{key}",
                                 }
-                            )
+                            }
                         }
                     }
                 }
-            )
+            }
         }
     }
 }

+ 6 - 4
packages/dioxus/examples/stress.rs

@@ -17,10 +17,12 @@ fn app(cx: Scope) -> Element {
     cx.render(rsx! (
         table {
             tbody {
-                (0..10_000_usize).map(|f| {
-                    let label = Label::new(&mut rng);
-                    rsx!( table_row { row_id: f, label: label } )
-                })
+                for f in 0..10_000_usize {
+                    table_row {
+                        row_id: f,
+                        label: Label::new(&mut rng)
+                    }
+                }
             }
         }
     ))

+ 11 - 26
packages/native-core/src/utils/persistant_iterator.rs

@@ -306,15 +306,10 @@ fn persist_removes() {
             _ => unreachable!(),
         };
         render!(
-            div{
-                (0..children).map(|i|{
-                    rsx!{
-                        p{
-                            key: "{i}",
-                            "{i}"
-                        }
-                    }
-                })
+            div {
+                for i in 0..children {
+                    p { key: "{i}", "{i}" }
+                }
             }
         )
     }
@@ -387,15 +382,10 @@ fn persist_instertions_before() {
             _ => unreachable!(),
         };
         render!(
-            div{
-                (0..children).map(|i|{
-                    rsx!{
-                        p{
-                            key: "{i}",
-                            "{i}"
-                        }
-                    }
-                })
+            div {
+                for i in 0..children {
+                    p { key: "{i}", "{i}" }
+                }
             }
         )
     }
@@ -446,14 +436,9 @@ fn persist_instertions_after() {
         };
         render!(
             div{
-                (0..children).map(|i|{
-                    rsx!{
-                        p{
-                            key: "{i}",
-                            "{i}"
-                        }
-                    }
-                })
+                for i in 0..children {
+                    p { key: "{i}", "{i}" }
+                }
             }
         )
     }

+ 1 - 1
packages/router/examples/simple_routes.rs

@@ -103,7 +103,7 @@ fn Route1(cx: Scope, user_id: usize, dynamic: usize, query: String, extra: Strin
 fn Route2(cx: Scope, user_id: usize) -> Element {
     render! {
         pre { "Route2{{\n\tuser_id:{user_id}\n}}" }
-        (0..*user_id).map(|i| rsx!{ p { "{i}" } }),
+        {(0..*user_id).map(|i| rsx!{ p { "{i}" } })},
         p { "Footer" }
         Link {
             to: Route::Route3 {

+ 2 - 2
packages/router/src/components/history_buttons.rs

@@ -77,7 +77,7 @@ pub fn GoBackButton<'a>(cx: Scope<'a, HistoryButtonProps<'a>>) -> Element {
             disabled: "{disabled}",
             prevent_default: "onclick",
             onclick: move |_| router.go_back(),
-            children
+            {children}
         }
     }
 }
@@ -149,7 +149,7 @@ pub fn GoForwardButton<'a>(cx: Scope<'a, HistoryButtonProps<'a>>) -> Element {
             disabled: "{disabled}",
             prevent_default: "onclick",
             onclick: move |_| router.go_forward(),
-            children
+            {children}
         }
     }
 }

+ 2 - 2
packages/router/src/components/link.rs

@@ -167,7 +167,7 @@ impl Debug for LinkProps<'_> {
 ///                 new_tab: true,
 ///                 rel: "link_rel",
 ///                 to: Route::Index {},
-///    
+///
 ///                 "A fully configured link"
 ///             }
 ///         }
@@ -250,7 +250,7 @@ pub fn Link<'a>(cx: Scope<'a, LinkProps<'a>>) -> Element {
             id: "{id}",
             rel: "{rel}",
             target: "{tag_target}",
-            children
+            {children}
         }
     }
 }

+ 29 - 7
packages/rsx/src/attribute.rs

@@ -20,6 +20,13 @@ impl AttributeType {
         }
     }
 
+    pub fn matches_attr_name(&self, other: &Self) -> bool {
+        match (self, other) {
+            (Self::Named(a), Self::Named(b)) => a.attr.name == b.attr.name,
+            _ => false,
+        }
+    }
+
     pub(crate) fn try_combine(&self, other: &Self) -> Option<Self> {
         match (self, other) {
             (Self::Named(a), Self::Named(b)) => a.try_combine(b).map(Self::Named),
@@ -102,16 +109,25 @@ impl ToTokens for ElementAttrNamed {
         };
 
         let attribute = {
+            let value = &self.attr.value;
+            let is_shorthand_event = match &attr.value {
+                ElementAttrValue::Shorthand(s) => s.to_string().starts_with("on"),
+                _ => false,
+            };
+
             match &attr.value {
                 ElementAttrValue::AttrLiteral(_)
                 | ElementAttrValue::AttrExpr(_)
-                | ElementAttrValue::AttrOptionalExpr { .. } => {
+                | ElementAttrValue::Shorthand(_)
+                | ElementAttrValue::AttrOptionalExpr { .. }
+                    if !is_shorthand_event =>
+                {
                     let name = &self.attr.name;
                     let ns = ns(name);
                     let volitile = volitile(name);
                     let attribute = attribute(name);
-                    let value = &self.attr.value;
                     let value = quote! { #value };
+
                     quote! {
                         __cx.attr(
                             #attribute,
@@ -123,12 +139,13 @@ impl ToTokens for ElementAttrNamed {
                 }
                 ElementAttrValue::EventTokens(tokens) => match &self.attr.name {
                     ElementAttrName::BuiltIn(name) => {
-                        quote! {
-                            dioxus_elements::events::#name(__cx, #tokens)
-                        }
+                        quote! { dioxus_elements::events::#name(__cx, #tokens) }
                     }
                     ElementAttrName::Custom(_) => todo!(),
                 },
+                _ => {
+                    quote! { dioxus_elements::events::#value(__cx, #value) }
+                }
             }
         };
 
@@ -144,6 +161,8 @@ pub struct ElementAttr {
 
 #[derive(PartialEq, Eq, Clone, Debug, Hash)]
 pub enum ElementAttrValue {
+    /// attribute,
+    Shorthand(Ident),
     /// attribute: "value"
     AttrLiteral(IfmtInput),
     /// attribute: if bool { "value" }
@@ -159,7 +178,7 @@ pub enum ElementAttrValue {
 
 impl Parse for ElementAttrValue {
     fn parse(input: ParseStream) -> syn::Result<Self> {
-        Ok(if input.peek(Token![if]) {
+        let element_attr_value = if input.peek(Token![if]) {
             let if_expr = input.parse::<ExprIf>()?;
             if is_if_chain_terminated(&if_expr) {
                 ElementAttrValue::AttrExpr(Expr::If(if_expr))
@@ -180,13 +199,16 @@ impl Parse for ElementAttrValue {
         } else {
             let value = input.parse::<Expr>()?;
             ElementAttrValue::AttrExpr(value)
-        })
+        };
+
+        Ok(element_attr_value)
     }
 }
 
 impl ToTokens for ElementAttrValue {
     fn to_tokens(&self, tokens: &mut TokenStream2) {
         match self {
+            ElementAttrValue::Shorthand(i) => tokens.append_all(quote! { #i }),
             ElementAttrValue::AttrLiteral(lit) => tokens.append_all(quote! { #lit }),
             ElementAttrValue::AttrOptionalExpr { condition, value } => {
                 tokens.append_all(quote! { if #condition { Some(#value) } else { None } })

+ 170 - 144
packages/rsx/src/component.rs

@@ -19,6 +19,7 @@ use syn::{
     ext::IdentExt,
     parse::{Parse, ParseBuffer, ParseStream},
     spanned::Spanned,
+    token::Brace,
     AngleBracketedGenericArguments, Error, Expr, Ident, LitStr, PathArguments, Result, Token,
 };
 
@@ -32,62 +33,15 @@ pub struct Component {
     pub brace: syn::token::Brace,
 }
 
-impl Component {
-    pub fn validate_component_path(path: &syn::Path) -> Result<()> {
-        // ensure path segments doesn't have PathArguments, only the last
-        // segment is allowed to have one.
-        if path
-            .segments
-            .iter()
-            .take(path.segments.len() - 1)
-            .any(|seg| seg.arguments != PathArguments::None)
-        {
-            component_path_cannot_have_arguments!(path.span());
-        }
-
-        // ensure last segment only have value of None or AngleBracketed
-        if !matches!(
-            path.segments.last().unwrap().arguments,
-            PathArguments::None | PathArguments::AngleBracketed(_)
-        ) {
-            invalid_component_path!(path.span());
-        }
-
-        Ok(())
-    }
-
-    pub fn key(&self) -> Option<&IfmtInput> {
-        match self
-            .fields
-            .iter()
-            .find(|f| f.name == "key")
-            .map(|f| &f.content)
-        {
-            Some(ContentField::Formatted(fmt)) => Some(fmt),
-            _ => None,
-        }
-    }
-}
-
 impl Parse for Component {
     fn parse(stream: ParseStream) -> Result<Self> {
         let mut name = stream.parse::<syn::Path>()?;
         Component::validate_component_path(&name)?;
 
         // extract the path arguments from the path into prop_gen_args
-        let prop_gen_args = name.segments.last_mut().and_then(|seg| {
-            if let PathArguments::AngleBracketed(args) = seg.arguments.clone() {
-                seg.arguments = PathArguments::None;
-                Some(args)
-            } else {
-                None
-            }
-        });
+        let prop_gen_args = normalize_path(&mut name);
 
         let content: ParseBuffer;
-
-        // if we see a `{` then we have a block
-        // else parse as a function-like call
         let brace = syn::braced!(content in stream);
 
         let mut fields = Vec::new();
@@ -98,11 +52,20 @@ impl Parse for Component {
             // if we splat into a component then we're merging properties
             if content.peek(Token![..]) {
                 content.parse::<Token![..]>()?;
-                manual_props = Some(content.parse::<Expr>()?);
-            } else if content.peek(Ident) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
-                fields.push(content.parse::<ComponentField>()?);
+                manual_props = Some(content.parse()?);
+            } else if
+            // Named fields
+            (content.peek(Ident) && content.peek2(Token![:]) && !content.peek3(Token![:]))
+            // shorthand struct initialization
+                // Not a div {}, mod::Component {}, or web-component {}
+                || (content.peek(Ident)
+                    && !content.peek2(Brace)
+                    && !content.peek2(Token![:])
+                    && !content.peek2(Token![-]))
+            {
+                fields.push(content.parse()?);
             } else {
-                children.push(content.parse::<BodyNode>()?);
+                children.push(content.parse()?);
             }
 
             if content.peek(Token![,]) {
@@ -123,74 +86,23 @@ impl Parse for Component {
 
 impl ToTokens for Component {
     fn to_tokens(&self, tokens: &mut TokenStream2) {
-        let name = &self.name;
-        let prop_gen_args = &self.prop_gen_args;
-
-        let builder = match &self.manual_props {
-            Some(manual_props) => {
-                let mut toks = quote! {
-                    let mut __manual_props = #manual_props;
-                };
-                for field in &self.fields {
-                    if field.name == "key" {
-                        // skip keys
-                    } else {
-                        let name = &field.name;
-                        let val = &field.content;
-                        toks.append_all(quote! {
-                            __manual_props.#name = #val;
-                        });
-                    }
-                }
-                toks.append_all(quote! {
-                    __manual_props
-                });
-                quote! {{
-                    #toks
-                }}
-            }
-            None => {
-                let mut toks = match prop_gen_args {
-                    Some(gen_args) => quote! { fc_to_builder(__cx, #name #gen_args) },
-                    None => quote! { fc_to_builder(__cx, #name) },
-                };
-                for field in &self.fields {
-                    match field.name.to_string().as_str() {
-                        "key" => {}
-                        _ => toks.append_all(quote! {#field}),
-                    }
-                }
-
-                if !self.children.is_empty() {
-                    let renderer: TemplateRenderer = TemplateRenderer {
-                        roots: &self.children,
-                        location: None,
-                    };
-
-                    toks.append_all(quote! {
-                        .children(
-                            Some({ #renderer })
-                        )
-                    });
-                }
-
-                toks.append_all(quote! {
-                    .build()
-                });
-                toks
-            }
-        };
+        let Self {
+            name,
+            prop_gen_args,
+            ..
+        } = self;
 
-        let fn_name = self.name.segments.last().unwrap().ident.to_string();
+        let builder = self
+            .manual_props
+            .as_ref()
+            .map(|props| self.collect_manual_props(props))
+            .unwrap_or_else(|| self.collect_props());
 
-        let gen_name = match &self.prop_gen_args {
-            Some(gen) => quote! { #name #gen },
-            None => quote! { #name },
-        };
+        let fn_name = self.fn_name();
 
         tokens.append_all(quote! {
             __cx.component(
-                #gen_name,
+                #name #prop_gen_args,
                 #builder,
                 #fn_name
             )
@@ -198,6 +110,91 @@ impl ToTokens for Component {
     }
 }
 
+impl Component {
+    fn validate_component_path(path: &syn::Path) -> Result<()> {
+        // ensure path segments doesn't have PathArguments, only the last
+        // segment is allowed to have one.
+        if path
+            .segments
+            .iter()
+            .take(path.segments.len() - 1)
+            .any(|seg| seg.arguments != PathArguments::None)
+        {
+            component_path_cannot_have_arguments!(path.span());
+        }
+
+        // ensure last segment only have value of None or AngleBracketed
+        if !matches!(
+            path.segments.last().unwrap().arguments,
+            PathArguments::None | PathArguments::AngleBracketed(_)
+        ) {
+            invalid_component_path!(path.span());
+        }
+
+        Ok(())
+    }
+
+    pub fn key(&self) -> Option<&IfmtInput> {
+        match self
+            .fields
+            .iter()
+            .find(|f| f.name == "key")
+            .map(|f| &f.content)
+        {
+            Some(ContentField::Formatted(fmt)) => Some(fmt),
+            _ => None,
+        }
+    }
+
+    fn collect_manual_props(&self, manual_props: &Expr) -> TokenStream2 {
+        let mut toks = quote! { let mut __manual_props = #manual_props; };
+        for field in &self.fields {
+            if field.name == "key" {
+                continue;
+            }
+            let ComponentField { name, content } = field;
+            toks.append_all(quote! { __manual_props.#name = #content; });
+        }
+        toks.append_all(quote! { __manual_props });
+        quote! {{ #toks }}
+    }
+
+    fn collect_props(&self) -> TokenStream2 {
+        let name = &self.name;
+
+        let mut toks = match &self.prop_gen_args {
+            Some(gen_args) => quote! { fc_to_builder(__cx, #name #gen_args) },
+            None => quote! { fc_to_builder(__cx, #name) },
+        };
+        for field in &self.fields {
+            match field.name.to_string().as_str() {
+                "key" => {}
+                _ => toks.append_all(quote! {#field}),
+            }
+        }
+        if !self.children.is_empty() {
+            let renderer: TemplateRenderer = TemplateRenderer {
+                roots: &self.children,
+                location: None,
+            };
+
+            toks.append_all(quote! {
+                .children(
+                    Some({ #renderer })
+                )
+            });
+        }
+        toks.append_all(quote! {
+            .build()
+        });
+        toks
+    }
+
+    fn fn_name(&self) -> String {
+        self.name.segments.last().unwrap().ident.to_string()
+    }
+}
+
 // the struct's fields info
 #[derive(PartialEq, Eq, Clone, Debug, Hash)]
 pub struct ComponentField {
@@ -207,21 +204,50 @@ pub struct ComponentField {
 
 #[derive(PartialEq, Eq, Clone, Debug, Hash)]
 pub enum ContentField {
+    Shorthand(Ident),
     ManExpr(Expr),
     Formatted(IfmtInput),
     OnHandlerRaw(Expr),
 }
 
+impl ContentField {
+    fn new_from_name(name: &Ident, input: ParseStream) -> Result<Self> {
+        if name.to_string().starts_with("on") {
+            return Ok(ContentField::OnHandlerRaw(input.parse()?));
+        }
+
+        if *name == "key" {
+            return Ok(ContentField::Formatted(input.parse()?));
+        }
+
+        if input.peek(LitStr) {
+            let forked = input.fork();
+            let t: LitStr = forked.parse()?;
+
+            // the string literal must either be the end of the input or a followed by a comma
+            let res =
+                match (forked.is_empty() || forked.peek(Token![,])) && is_literal_foramtted(&t) {
+                    true => ContentField::Formatted(input.parse()?),
+                    false => ContentField::ManExpr(input.parse()?),
+                };
+
+            return Ok(res);
+        }
+
+        Ok(ContentField::ManExpr(input.parse()?))
+    }
+}
+
 impl ToTokens for ContentField {
     fn to_tokens(&self, tokens: &mut TokenStream2) {
         match self {
+            ContentField::Shorthand(i) if i.to_string().starts_with("on") => {
+                tokens.append_all(quote! { __cx.event_handler(#i) })
+            }
+            ContentField::Shorthand(i) => tokens.append_all(quote! { #i }),
             ContentField::ManExpr(e) => e.to_tokens(tokens),
-            ContentField::Formatted(s) => tokens.append_all(quote! {
-                __cx.raw_text(#s)
-            }),
-            ContentField::OnHandlerRaw(e) => tokens.append_all(quote! {
-                __cx.event_handler(#e)
-            }),
+            ContentField::Formatted(s) => tokens.append_all(quote! { __cx.raw_text(#s) }),
+            ContentField::OnHandlerRaw(e) => tokens.append_all(quote! { __cx.event_handler(#e) }),
         }
     }
 }
@@ -229,30 +255,21 @@ impl ToTokens for ContentField {
 impl Parse for ComponentField {
     fn parse(input: ParseStream) -> Result<Self> {
         let name = Ident::parse_any(input)?;
-        input.parse::<Token![:]>()?;
-
-        let content = {
-            if name.to_string().starts_with("on") {
-                ContentField::OnHandlerRaw(input.parse()?)
-            } else if name == "key" {
-                let content = ContentField::Formatted(input.parse()?);
-                return Ok(Self { name, content });
-            } else if input.peek(LitStr) {
-                let forked = input.fork();
-                let t: LitStr = forked.parse()?;
-                // the string literal must either be the end of the input or a followed by a comma
-                if (forked.is_empty() || forked.peek(Token![,])) && is_literal_foramtted(&t) {
-                    ContentField::Formatted(input.parse()?)
-                } else {
-                    ContentField::ManExpr(input.parse()?)
-                }
-            } else {
-                ContentField::ManExpr(input.parse()?)
-            }
+
+        // if the next token is not a colon, then it's a shorthand field
+        if input.parse::<Token![:]>().is_err() {
+            return Ok(Self {
+                content: ContentField::Shorthand(name.clone()),
+                name,
+            });
         };
+
+        let content = ContentField::new_from_name(&name, input)?;
+
         if input.peek(LitStr) || input.peek(Ident) {
             missing_trailing_comma!(content.span());
         }
+
         Ok(Self { name, content })
     }
 }
@@ -260,9 +277,7 @@ impl Parse for ComponentField {
 impl ToTokens for ComponentField {
     fn to_tokens(&self, tokens: &mut TokenStream2) {
         let ComponentField { name, content, .. } = self;
-        tokens.append_all(quote! {
-            .#name(#content)
-        })
+        tokens.append_all(quote! { .#name(#content) })
     }
 }
 
@@ -281,3 +296,14 @@ fn is_literal_foramtted(lit: &LitStr) -> bool {
 
     false
 }
+
+fn normalize_path(name: &mut syn::Path) -> Option<AngleBracketedGenericArguments> {
+    let seg = name.segments.last_mut()?;
+    match seg.arguments.clone() {
+        PathArguments::AngleBracketed(args) => {
+            seg.arguments = PathArguments::None;
+            Some(args)
+        }
+        _ => None,
+    }
+}

+ 65 - 35
packages/rsx/src/element.rs

@@ -8,6 +8,7 @@ use syn::{
     parse::{Parse, ParseBuffer, ParseStream},
     punctuated::Punctuated,
     spanned::Spanned,
+    token::Brace,
     Expr, Ident, LitStr, Result, Token,
 };
 
@@ -59,6 +60,7 @@ impl Parse for Element {
             }
 
             // Parse the raw literal fields
+            // "def": 456,
             if content.peek(LitStr) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
                 let name = content.parse::<LitStr>()?;
                 let ident = name.clone();
@@ -84,6 +86,8 @@ impl Parse for Element {
                 continue;
             }
 
+            // Parse
+            // abc: 123,
             if content.peek(Ident) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
                 let name = content.parse::<Ident>()?;
 
@@ -102,22 +106,17 @@ impl Parse for Element {
                             value: ElementAttrValue::EventTokens(content.parse()?),
                         },
                     }));
+                } else if name_str == "key" {
+                    key = Some(content.parse()?);
                 } else {
-                    match name_str.as_str() {
-                        "key" => {
-                            key = Some(content.parse()?);
-                        }
-                        _ => {
-                            let value = content.parse::<ElementAttrValue>()?;
-                            attributes.push(attribute::AttributeType::Named(ElementAttrNamed {
-                                el_name: el_name.clone(),
-                                attr: ElementAttr {
-                                    name: ElementAttrName::BuiltIn(name),
-                                    value,
-                                },
-                            }));
-                        }
-                    }
+                    let value = content.parse::<ElementAttrValue>()?;
+                    attributes.push(attribute::AttributeType::Named(ElementAttrNamed {
+                        el_name: el_name.clone(),
+                        attr: ElementAttr {
+                            name: ElementAttrName::BuiltIn(name),
+                            value,
+                        },
+                    }));
                 }
 
                 if content.is_empty() {
@@ -130,6 +129,47 @@ impl Parse for Element {
                 continue;
             }
 
+            // Parse shorthand fields
+            if content.peek(Ident)
+                && !content.peek2(Brace)
+                && !content.peek2(Token![:])
+                && !content.peek2(Token![-])
+            {
+                let name = content.parse::<Ident>()?;
+                let name_ = name.clone();
+
+                // If the shorthand field is children, these are actually children!
+                if name == "children" {
+                    return Err(syn::Error::new(
+                        name.span(),
+                        r#"Shorthand element children are not supported.
+To pass children into elements, wrap them in curly braces.
+Like so:
+    div {{ {{children}} }}
+
+"#,
+                    ));
+                };
+
+                let value = ElementAttrValue::Shorthand(name.clone());
+                attributes.push(attribute::AttributeType::Named(ElementAttrNamed {
+                    el_name: el_name.clone(),
+                    attr: ElementAttr {
+                        name: ElementAttrName::BuiltIn(name),
+                        value,
+                    },
+                }));
+
+                if content.is_empty() {
+                    break;
+                }
+
+                if content.parse::<Token![,]>().is_err() {
+                    missing_trailing_comma!(name_.span());
+                }
+                continue;
+            }
+
             break;
         }
 
@@ -137,31 +177,21 @@ impl Parse for Element {
         // For example, if there are two `class` attributes, combine them into one
         let mut merged_attributes: Vec<AttributeType> = Vec::new();
         for attr in &attributes {
-            if let Some(old_attr_index) = merged_attributes.iter().position(|a| {
-                matches!((a, attr), (
-                                AttributeType::Named(ElementAttrNamed {
-                                    attr: ElementAttr {
-                                        name: ElementAttrName::BuiltIn(old_name),
-                                        ..
-                                    },
-                                    ..
-                                }),
-                                AttributeType::Named(ElementAttrNamed {
-                                    attr: ElementAttr {
-                                        name: ElementAttrName::BuiltIn(new_name),
-                                        ..
-                                    },
-                                    ..
-                                }),
-                            ) if old_name == new_name)
-            }) {
+            let attr_index = merged_attributes
+                .iter()
+                .position(|a| a.matches_attr_name(attr));
+
+            if let Some(old_attr_index) = attr_index {
                 let old_attr = &mut merged_attributes[old_attr_index];
+
                 if let Some(combined) = old_attr.try_combine(attr) {
                     *old_attr = combined;
                 }
-            } else {
-                merged_attributes.push(attr.clone());
+
+                continue;
             }
+
+            merged_attributes.push(attr.clone());
         }
 
         while !content.is_empty() {

+ 7 - 7
packages/rsx/src/lib.rs

@@ -576,7 +576,7 @@ fn create_template() {
             p {
                 "hello world"
             }
-            (0..10).map(|i| rsx!{"{i}"})
+            {(0..10).map(|i| rsx!{"{i}"})}
         }
     };
 
@@ -668,9 +668,9 @@ fn diff_template() {
             p {
                 "hello world"
             }
-            (0..10).map(|i| rsx!{"{i}"}),
-            (0..10).map(|i| rsx!{"{i}"}),
-            (0..11).map(|i| rsx!{"{i}"}),
+            {(0..10).map(|i| rsx!{"{i}"})},
+            {(0..10).map(|i| rsx!{"{i}"})},
+            {(0..11).map(|i| rsx!{"{i}"})},
             Comp{}
         }
     };
@@ -714,9 +714,9 @@ fn diff_template() {
             "height2": "100px",
             width: 100,
             Comp{}
-            (0..11).map(|i| rsx!{"{i}"}),
-            (0..10).map(|i| rsx!{"{i}"}),
-            (0..10).map(|i| rsx!{"{i}"}),
+            {(0..11).map(|i| rsx!{"{i}"})},
+            {(0..10).map(|i| rsx!{"{i}"})},
+            {(0..10).map(|i| rsx!{"{i}"})},
             p {
                 "hello world"
             }

+ 117 - 69
packages/rsx/src/node.rs

@@ -6,7 +6,8 @@ use syn::{
     braced,
     parse::{Parse, ParseStream},
     spanned::Spanned,
-    token, Expr, ExprIf, LitStr, Pat, Result,
+    token::{self, Brace},
+    Expr, ExprIf, LitStr, Pat, Result,
 };
 
 /*
@@ -15,14 +16,14 @@ Parse
 -> Component {}
 -> component()
 -> "text {with_args}"
--> (0..10).map(|f| rsx!("asd")),  // <--- notice the comma - must be a complete expr
+-> {(0..10).map(|f| rsx!("asd"))}  // <--- notice the curly braces
 */
 #[derive(PartialEq, Eq, Clone, Debug, Hash)]
 pub enum BodyNode {
     Element(Element),
     Component(Component),
     ForLoop(ForLoop),
-    IfChain(ExprIf),
+    IfChain(IfChain),
     Text(IfmtInput),
     RawExpr(Expr),
 }
@@ -97,7 +98,6 @@ impl Parse for BodyNode {
             // Input::<InputProps<'_, i32> {}
             // crate::Input::<InputProps<'_, i32> {}
             if body_stream.peek(token::Brace) {
-                Component::validate_component_path(&path)?;
                 return Ok(BodyNode::Component(stream.parse()?));
             }
         }
@@ -112,7 +112,27 @@ impl Parse for BodyNode {
             return Ok(BodyNode::IfChain(stream.parse()?));
         }
 
-        Ok(BodyNode::RawExpr(stream.parse::<Expr>()?))
+        // Match statements are special but have no special arm syntax
+        // we could allow arm syntax if we wanted
+        //
+        // ```
+        // match {
+        //  val => div {}
+        //  other_val => div {}
+        // }
+        // ```
+        if stream.peek(Token![match]) {
+            return Ok(BodyNode::RawExpr(stream.parse::<Expr>()?));
+        }
+
+        if stream.peek(token::Brace) {
+            return Ok(BodyNode::RawExpr(stream.parse::<Expr>()?));
+        }
+
+        Err(syn::Error::new(
+            stream.span(),
+            "Expected a valid body node.\nExpressions must be wrapped in curly braces.",
+        ))
     }
 }
 
@@ -151,71 +171,52 @@ impl ToTokens for BodyNode {
                 })
             }
             BodyNode::IfChain(chain) => {
-                if is_if_chain_terminated(chain) {
-                    tokens.append_all(quote! {
-                        {
-                            let ___nodes = (#chain).into_dyn_node(__cx);
-                            ___nodes
-                        }
-                    });
-                } else {
-                    let ExprIf {
+                let mut body = TokenStream2::new();
+                let mut terminated = false;
+
+                let mut elif = Some(chain);
+
+                while let Some(chain) = elif {
+                    let IfChain {
+                        if_token,
                         cond,
                         then_branch,
+                        else_if_branch,
                         else_branch,
-                        ..
                     } = chain;
 
-                    let mut body = TokenStream2::new();
-
-                    body.append_all(quote! {
-                        if #cond {
-                            Some(#then_branch)
-                        }
-                    });
-
-                    let mut elif = else_branch;
-
-                    while let Some((_, ref branch)) = elif {
-                        match branch.as_ref() {
-                            Expr::If(ref eelif) => {
-                                let ExprIf {
-                                    cond,
-                                    then_branch,
-                                    else_branch,
-                                    ..
-                                } = eelif;
-
-                                body.append_all(quote! {
-                                    else if #cond {
-                                        Some(#then_branch)
-                                    }
-                                });
-
-                                elif = else_branch;
-                            }
-                            _ => {
-                                body.append_all(quote! {
-                                    else {
-                                        #branch
-                                    }
-                                });
-                                break;
-                            }
-                        }
+                    let mut renderer: TemplateRenderer = TemplateRenderer {
+                        roots: then_branch,
+                        location: None,
+                    };
+
+                    body.append_all(quote! { #if_token #cond { Some({#renderer}) } });
+
+                    if let Some(next) = else_if_branch {
+                        body.append_all(quote! { else });
+                        elif = Some(next);
+                    } else if let Some(else_branch) = else_branch {
+                        renderer.roots = else_branch;
+                        body.append_all(quote! { else { Some({#renderer}) } });
+                        terminated = true;
+                        break;
+                    } else {
+                        elif = None;
                     }
+                }
 
+                if !terminated {
                     body.append_all(quote! {
                         else { None }
                     });
-
-                    tokens.append_all(quote! {
-                        {
-                            let ___nodes = (#body).into_dyn_node(__cx);
-                            ___nodes
-                        }
-                    });
                 }
+
+                tokens.append_all(quote! {
+                    {
+                        let ___nodes = (#body).into_dyn_node(__cx);
+                        ___nodes
+                    }
+                });
             }
         }
     }
@@ -240,26 +241,73 @@ impl Parse for ForLoop {
         let in_token: Token![in] = input.parse()?;
         let expr: Expr = input.call(Expr::parse_without_eager_brace)?;
 
-        let content;
-        let brace_token = braced!(content in input);
-
-        let mut children = vec![];
-
-        while !content.is_empty() {
-            children.push(content.parse()?);
-        }
+        let (brace_token, body) = parse_buffer_as_braced_children(input)?;
 
         Ok(Self {
             for_token,
             pat,
             in_token,
-            body: children,
-            expr: Box::new(expr),
+            body,
             brace_token,
+            expr: Box::new(expr),
+        })
+    }
+}
+
+#[derive(PartialEq, Eq, Clone, Debug, Hash)]
+pub struct IfChain {
+    pub if_token: Token![if],
+    pub cond: Box<Expr>,
+    pub then_branch: Vec<BodyNode>,
+    pub else_if_branch: Option<Box<IfChain>>,
+    pub else_branch: Option<Vec<BodyNode>>,
+}
+
+impl Parse for IfChain {
+    fn parse(input: ParseStream) -> Result<Self> {
+        let if_token: Token![if] = input.parse()?;
+
+        // stolen from ExprIf
+        let cond = Box::new(input.call(Expr::parse_without_eager_brace)?);
+
+        let (_, then_branch) = parse_buffer_as_braced_children(input)?;
+
+        let mut else_branch = None;
+        let mut else_if_branch = None;
+
+        // if the next token is `else`, set the else branch as the next if chain
+        if input.peek(Token![else]) {
+            input.parse::<Token![else]>()?;
+            if input.peek(Token![if]) {
+                else_if_branch = Some(Box::new(input.parse::<IfChain>()?));
+            } else {
+                let (_, else_branch_nodes) = parse_buffer_as_braced_children(input)?;
+                else_branch = Some(else_branch_nodes);
+            }
+        }
+
+        Ok(Self {
+            cond,
+            if_token,
+            then_branch,
+            else_if_branch,
+            else_branch,
         })
     }
 }
 
+fn parse_buffer_as_braced_children(
+    input: &syn::parse::ParseBuffer<'_>,
+) -> Result<(Brace, Vec<BodyNode>)> {
+    let content;
+    let brace_token = braced!(content in input);
+    let mut then_branch = vec![];
+    while !content.is_empty() {
+        then_branch.push(content.parse()?);
+    }
+    Ok((brace_token, then_branch))
+}
+
 pub(crate) fn is_if_chain_terminated(chain: &ExprIf) -> bool {
     let mut current = chain;
     loop {

+ 3 - 1
packages/ssr/src/renderer.rs

@@ -242,7 +242,9 @@ fn to_string_works() {
                 div {}
                 div { "nest 2" }
                 "{dyn2}"
-                (0..5).map(|i| rsx! { div { "finalize {i}" } })
+                for i in (0..5) {
+                    div { "finalize {i}" }
+                }
             }
         }
     }

+ 3 - 7
packages/ssr/tests/hydration.rs

@@ -70,7 +70,7 @@ fn text_nodes() {
     fn app(cx: Scope) -> Element {
         let dynamic_text = "hello";
         render! {
-            div { dynamic_text }
+            div { {dynamic_text} }
         }
     }
 
@@ -124,7 +124,7 @@ fn components_hydrate() {
     fn Child2(cx: Scope) -> Element {
         let dyn_text = "hello";
         render! {
-            div { dyn_text }
+            div { {dyn_text} }
         }
     }
 
@@ -159,11 +159,7 @@ fn components_hydrate() {
     fn Child4(cx: Scope) -> Element {
         render! {
             for _ in 0..2 {
-                render! {
-                    render! {
-                        "{1}"
-                    }
-                }
+                {render! { "{1}" }}
             }
         }
     }

+ 7 - 5
packages/ssr/tests/simple.rs

@@ -23,9 +23,9 @@ fn lists() {
     assert_eq!(
         dioxus_ssr::render_lazy(rsx! {
             ul {
-                (0..5).map(|i| rsx! {
+                for i in 0..5 {
                     li { "item {i}" }
-                })
+                }
             }
         }),
         "<ul><li>item 0</li><li>item 1</li><li>item 2</li><li>item 3</li><li>item 4</li></ul>"
@@ -53,9 +53,9 @@ fn components() {
     assert_eq!(
         dioxus_ssr::render_lazy(rsx! {
             div {
-                (0..5).map(|name| rsx! {
+                for name in 0..5 {
                     MyComponent { name: name }
-                })
+                }
             }
         }),
         "<div><div>component 0</div><div>component 1</div><div>component 2</div><div>component 3</div><div>component 4</div></div>"
@@ -67,7 +67,9 @@ fn fragments() {
     assert_eq!(
         dioxus_ssr::render_lazy(rsx! {
             div {
-                (0..5).map(|_| rsx! (()))
+                for _ in 0..5 {
+                    {}
+                }
             }
         }),
         "<div></div>"

+ 2 - 2
packages/web/examples/hydrate.rs

@@ -12,11 +12,11 @@ fn app(cx: Scope) -> Element {
             "asd"
             Bapp {}
         }
-        (0..10).map(|f| rsx!{
+        {(0..10).map(|f| rsx!{
             div {
                 "thing {f}"
             }
-        })
+        })}
     })
 }
 

+ 5 - 2
packages/web/examples/timeout_count.rs

@@ -24,8 +24,11 @@ fn app(cx: Scope) -> Element {
                 start();
                 *count.write() += 1;
             },
-            // format is needed as {count} does not seemed to work in `if` within content
-            if **started { format!("Current score: {}", count.write()) } else { "Start".to_string() }
+            if **started {
+                "Current score: {count.read()}"
+            } else {
+                "Start"
+            }
         }
     })
 }

+ 1 - 1
packages/web/tests/hydrate.rs

@@ -43,7 +43,7 @@ fn rehydrates() {
                     },
                     "listener test"
                 }
-                false.then(|| rsx!{ "hello" })
+                {false.then(|| rsx!{ "hello" })}
             }
         })
     }