Browse Source

docs: examples

Jonathan Kelley 4 years ago
parent
commit
d9e6d09

+ 1 - 0
Cargo.toml

@@ -30,6 +30,7 @@ split-debuginfo = "unpacked"
 
 
 [dev-dependencies]
 [dev-dependencies]
 futures = "0.3.15"
 futures = "0.3.15"
+log = "0.4.14"
 # For the tide ssr examples
 # For the tide ssr examples
 # async-std = { version="1.9.0", features=["attributes"] }
 # async-std = { version="1.9.0", features=["attributes"] }
 # tide = { version="0.16.0" }
 # tide = { version="0.16.0" }

+ 46 - 25
examples/README.md

@@ -1,29 +1,50 @@
 # Examples
 # Examples
 
 
-Most of these examples are run through webview so you don't need the dioxus cli installed to preview the functionality. Anything labeled `_web` will need to be built with the Dioxus CLI to preview features that only a native bundle can handle.
+Most of these examples are run through webview so you don't need the dioxus cli installed to preview the functionality.
 
 
-List of examples:
+These examples are fully-fledged micro apps. They can be ran with the `cargo run --example XYZ`
 
 
-| Example                                 | What it does |
-| --------------------------------------- | ------------ |
-| [The basics](./basics.rs)               | this does    |
-| [fine grained reactivity](./signals.rs) | this does    |
-| Global State Management                 | this does    |
-| Virtual Refs                            | this does    |
-| Inline Styles                           | this does    |
-| Conditional Rendering                   | this does    |
-| Maps/Iterators                          | this does    |
-| Render To string                        | this does    |
-| Component Children                      | this does    |
-| Function Driven children                | this does    |
-| Memoization                             | this does    |
-| Borrowed Data                           | this does    |
-| Fragments                               | this does    |
-| Null/None Components                    | this does    |
-| Spread Pattern for props                | this does    |
-| Controlled Inputs                       | this does    |
-| Custom Elements                         | this does    |
-| Testing And debugging                   | this does    |
-| Asynchronous Data                       | this does    |
-| Fiber/Scheduled Rendering               | this does    |
-| CSS Compiled Styles                     | this does    |
+| Example                                             | What it does                                | Status |
+| --------------------------------------------------- | ------------------------------------------- | ------ |
+| [The basics](./basics.rs)                           | A few basic examples to preview Dioxus      | 🛠      |
+| [fine grained reactivity](./signals.rs)             | Escape `diffing` by writing values directly | 🛠      |
+| [Global State Management](./statemanagement.rs)     | Share state between components              | 🛠      |
+| [Virtual Refs]()                                    | Cross-platform imperative elements          | 🛠      |
+| [Inline Styles](./inline-styles.rs)                 | Define styles for elements inline           | 🛠      |
+| [Conditional Rendering](./conditional-rendering.rs) | Hide/Show elements using conditionals       | ✅     |
+
+These examples are not necessarily meant to be run, but rather serve as a reference for the given functionality.
+
+| Example                                             | What it does                                    | Status |
+| --------------------------------------------------- | ----------------------------------------------- | ------ |
+| [The basics](./basics.rs)                           | A few basic examples to preview Dioxus          | 🛠      |
+| [fine grained reactivity](./signals.rs)             | Escape `diffing` by writing values directly     | 🛠      |
+| [Global State Management](./statemanagement.rs)     | Share state between components                  | 🛠      |
+| [Virtual Refs]()                                    | Cross-platform imperative elements              | 🛠      |
+| [Inline Styles](./inline-styles.rs)                 | Define styles for elements inline               | 🛠      |
+| [Conditional Rendering](./conditional-rendering.rs) | Hide/Show elements using conditionals           | ✅     |
+| [Maps/Iterators](./iterators.rs)                    | Use iterators in the rsx! macro                 | 🛠      |
+| [Render To string](./tostring.rs)                   | Render a mounted virtualdom to a string         | 🛠      |
+| [Component Children](./children.rs)                 | Pass children into child components             | 🛠      |
+| [Function Driven children]()                        | Pass functions to make VNodes                   | 🛠      |
+| [Memoization & Borrowed Data](./memo.rs)            | Suppress renders, borrow from parents           | ✅     |
+| [Fragments](./fragments.rs)                         | Support root-less element groups                | ✅     |
+| [Null/None Components](./empty.rs)                  | Return nothing!                                 | 🛠      |
+| [Spread Pattern for props](./spreadpattern.rs)      | Manually specify and override props             | ✅     |
+| [Controlled Inputs](./controlled-inputs.rs)         | this does                                       | 🛠      |
+| [Custom Elements]()                                 | Define custom elements                          | 🛠      |
+| [Web Components]()                                  | Custom elements to interface with WebComponents | 🛠      |
+| [Testing And debugging]()                           | this does                                       | 🛠      |
+| [Asynchronous Data]()                               | Using suspense to wait for data                 | 🛠      |
+| [Fiber/Scheduled Rendering]()                       | this does                                       | 🛠      |
+| [CSS Compiled Styles]()                             | this does                                       | 🛠      |
+| [Anti-patterns](./antipatterns.rs)                  | A collection of discouraged patterns            | ✅     |
+| [Complete rsx reference](./rsx_usage.rs)            | A complete reference for all rsx! usage         | ✅     |
+| [Event Listeners](./listener.rs)                    | Attach closures to events on elements           | ✅     |
+
+These web-specific examples must be run with `dioxus-cli` using `dioxus develop --example XYZ`
+
+| Example | What it does |
+| ------- | ------------ |
+| asd     | this does    |
+| asd     | this does    |

+ 0 - 24
examples/listener.rs

@@ -1,24 +0,0 @@
-#![allow(unused, non_upper_case_globals)]
-
-use dioxus_core::prelude::*;
-
-fn main() {}
-
-static Example: FC<()> = |cx| {
-    let (name, set_name) = use_state(&cx, || "...?");
-
-    cx.render(rsx!(
-        div {
-            h1 { "Hello, {name}" }
-            // look ma - we can rsx! and html! together
-            {["jack", "jill"].iter().map(|f| html!(<button onclick={move |_| set_name(f)}> "{f}" </button>))}
-        }
-    ))
-};
-
-pub fn render<'src, 'a, F: for<'b> FnOnce(&'b NodeFactory<'src>) -> VNode<'src> + 'src + 'a, P>(
-    cx: &'a Context<'src, P>,
-    lazy_nodes: LazyNodes<'src, F>,
-) -> VNode<'src> {
-    cx.render(lazy_nodes)
-}

+ 3 - 3
examples/antipatterns.rs → examples/reference/antipatterns.rs

@@ -21,10 +21,10 @@ fn main() {}
 /// Antipattern: Iterators without keys
 /// Antipattern: Iterators without keys
 /// -----------------------------------
 /// -----------------------------------
 ///
 ///
-/// This is considered an anti-pattern for performance reasons. Dioxus must diff your current and old layout and must
+/// This is considered an anti-pattern for performance reasons. Dioxus will diff your current and old layout and must
 /// take a slower path if it can't correlate old elements with new elements. Lists are particularly susceptible to the
 /// take a slower path if it can't correlate old elements with new elements. Lists are particularly susceptible to the
 /// "slow" path, so you're strongly encouraged to provide a unique ID stable between renders. Additionally, providing
 /// "slow" path, so you're strongly encouraged to provide a unique ID stable between renders. Additionally, providing
-/// the *wrong* keys is even worse. Keys should be:
+/// the *wrong* keys is even worse - props might be assigned to the wrong components! Keys should be:
 /// - Unique
 /// - Unique
 /// - Stable
 /// - Stable
 /// - Predictable
 /// - Predictable
@@ -39,7 +39,7 @@ static AntipatternNoKeys: FC<NoKeysProps> = |cx| {
     rsx!(in cx, ul {
     rsx!(in cx, ul {
         {cx.data.iter().map(|(k, v)| rsx!(li { "List item: {v}" }))}
         {cx.data.iter().map(|(k, v)| rsx!(li { "List item: {v}" }))}
     });
     });
-    // Like this:
+    // RIGHT: Like this:
     rsx!(in cx, ul {
     rsx!(in cx, ul {
         {cx.data.iter().map(|(k, v)| rsx!(li { key: "{k}", "List item: {v}" }))}
         {cx.data.iter().map(|(k, v)| rsx!(li { key: "{k}", "List item: {v}" }))}
     })
     })

+ 10 - 0
examples/reference/async.rs

@@ -0,0 +1,10 @@
+use dioxus::prelude::*;
+fn main() {}
+
+static Example: FC<()> = |cx| {
+    cx.render(rsx! {
+        div {
+
+        }
+    })
+};

+ 10 - 0
examples/reference/basics.rs

@@ -0,0 +1,10 @@
+use dioxus::prelude::*;
+fn main() {}
+
+static Example: FC<()> = |cx| {
+    cx.render(rsx! {
+        div {
+
+        }
+    })
+};

+ 45 - 0
examples/reference/children.rs

@@ -0,0 +1,45 @@
+//! Example: Children
+//! -----------------
+//!
+//! Dioxus supports passing children in from the parent. These children are allocated in the parent and just forced
+//! into the child. Components that pass in children may not be safely memoized, though in practice its rare for a
+//! change in a parent to not result in a different set of children.
+//!
+//! In Dioxus, children can *only be a list*. Unlike React, you cannot pass in functions or arbitrary data. This is
+//! partially a limitaiton in having static types, but is rather intentional to encourage the use of attributes where
+//! arbitrary child data might normally be used. Check out the `function driven children` example for how to adopt your
+//! React pattern for Dioxus' semantics.
+//!
+//! Dioxus will let you use the `children` method more than once - and it's semantically *okay* - but you'll likely
+//! ruin your page if you try to clone elements in this way. Under the hood, Dioxus shares a "mounted ID" for each node,
+//! and mounting the same VNode in two places will overwrite the first mounted ID. This will likely lead to dead elements.
+//!
+//! In the future, this might become a runtime error, so consider it an error today.
+
+use dioxus::prelude::*;
+fn main() {}
+
+static App: FC<()> = |cx| {
+    cx.render(rsx! {
+        div {
+            Banner {
+                p { "Some Content1" }
+            }
+            Banner {
+                p { "Some Content2" }
+            }
+        }
+    })
+};
+
+static Banner: FC<()> = |cx| {
+    cx.render(rsx! {
+        div {
+            h1 { "This is a great banner!" }
+            div { class: "content"
+                {cx.children()}
+            }
+            footer { "Wow, what a great footer" }
+        }
+    })
+};

+ 92 - 0
examples/reference/conditional_rendering.rs

@@ -0,0 +1,92 @@
+//! Example: Conditional Rendering
+//! ------------------------------
+//!
+//! This example shows how to hide or show elements using conditional rendering.
+//!
+//! Often times you might want to display a different UI given some sort of condition. This is called "conditonal rendering".
+//! In Dioxus, you can perform conditional rendering with optionals or matching.
+//!
+//! The rsx! and html! macro accepts anything that is `IntoIter<Item = impl IntoVnode>`. Options and Results both implement
+//! IntoIter for impl VNode, so you can use option/result for conditional rendering.
+
+use dioxus::prelude::*;
+
+fn main() {}
+
+// Convert a boolean conditional into a hide/show
+#[derive(PartialEq, Props)]
+struct MyProps {
+    should_show: bool,
+}
+static Example: FC<MyProps> = |cx| {
+    cx.render(rsx! {
+        div {
+            {cx.should_show.then(|| rsx!{
+                h1 { "showing the title!" }
+            })}
+        }
+    })
+};
+
+// Convert a boolean conditional into an either/or
+// Because rsx! is lazy (produces a closure), we cannot use it in two branch arms. To use it in matching/branching, we
+// must render it.
+//
+// Dioxus will let you render any `LazyNodes` into a `VNode` with `cx.render`. `rsx!` also supports the `in cx` syntax
+// which will do essentially the same thing as `cx.render`.
+//
+// In short:
+// `rsx!(in cx, ...)` is shorthand for `cx.render(rsx!(...))`
+#[derive(PartialEq, Props)]
+struct MyProps1 {
+    should_show: bool,
+}
+static Example1: FC<MyProps1> = |cx| {
+    cx.render(rsx! {
+        div {
+            // With matching
+            {match cx.should_show {
+                true => cx.render(rsx!(div {"it is true!"})),
+                false => rsx!(in cx, div {"it is false!"}),
+            }}
+
+            // or with just regular conditions
+            {if cx.should_show {
+                rsx!(in cx, div {"it is true!"})
+            } else {
+                rsx!(in cx, div {"it is false!"})
+            }}
+
+            // or with optional chaining
+            {
+                cx.should_show
+                .then(|| rsx!(in cx, div {"it is false!"}))
+                .unwrap_or_else(|| rsx!(in cx, div {"it is false!"}))
+            }
+        }
+    })
+};
+
+/// Matching can be expanded
+
+#[derive(PartialEq)]
+enum Color {
+    Green,
+    Yellow,
+    Red,
+}
+#[derive(PartialEq, Props)]
+struct MyProps2 {
+    color: Color,
+}
+static Example2: FC<MyProps2> = |cx| {
+    cx.render(rsx! {
+        div {
+            {match cx.color {
+                Color::Green => rsx!(in cx, div {"it is Green!"}),
+                Color::Yellow => rsx!(in cx, div {"it is Yellow!"}),
+                Color::Red => rsx!(in cx, div {"it is Red!"}),
+            }}
+        }
+    })
+};

+ 10 - 0
examples/reference/controlled_inputs.rs

@@ -0,0 +1,10 @@
+use dioxus::prelude::*;
+fn main() {}
+
+static Example: FC<()> = |cx| {
+    cx.render(rsx! {
+        div {
+
+        }
+    })
+};

+ 10 - 0
examples/reference/custom_elements.rs

@@ -0,0 +1,10 @@
+use dioxus::prelude::*;
+fn main() {}
+
+static Example: FC<()> = |cx| {
+    cx.render(rsx! {
+        div {
+
+        }
+    })
+};

+ 15 - 0
examples/reference/empty.rs

@@ -0,0 +1,15 @@
+//! Example: Null/None Children
+//! ---------------------------
+//!
+//! This is a simple pattern that allows you to return no elements!
+
+use dioxus::prelude::*;
+fn main() {}
+
+static Example: FC<()> = |cx| {
+    cx.render(rsx! {
+        div {
+
+        }
+    })
+};

+ 10 - 0
examples/reference/fiber.rs

@@ -0,0 +1,10 @@
+use dioxus::prelude::*;
+fn main() {}
+
+static Example: FC<()> = |cx| {
+    cx.render(rsx! {
+        div {
+
+        }
+    })
+};

+ 45 - 0
examples/reference/fragments.rs

@@ -0,0 +1,45 @@
+//! Example: Fragments
+//! ------------------
+//!
+//! Dioxus can return multiple elements without a container through the use of the VNode called a "Fragment". Fragments do not
+//! have a mounted root and are inserted inline with their siblings. There are three ways of creating fragments as outlined
+//! in the examples below:
+//! - By returning multiple elements in Rsx!
+//! - By using the `Fragment` component
+//! - By using the fragment() method on the node factory
+
+use dioxus::prelude::*;
+fn main() {}
+
+// Returning multiple elements with rsx! or html!
+static Example1: FC<()> = |cx| {
+    cx.render(rsx! {
+        h1 { }
+        h2 { }
+        h3 { }
+        // {}
+        // "hello world"
+    })
+};
+
+// Using the Fragment component
+static Example2: FC<()> = |cx| {
+    cx.render(rsx! {
+        Fragment {
+            div {}
+            div {}
+            "asd"
+        }
+    })
+};
+
+// Using the `fragment` method on the NodeFactory
+static Example3: FC<()> = |cx| {
+    cx.render(LazyNodes::new(move |fac: &NodeFactory| {
+        fac.fragment_builder(None, |list| {
+            list.add_child(fac.text(format_args!("A")))
+                .add_child(fac.text(format_args!("B")))
+                .finish()
+        })
+    }))
+};

+ 10 - 0
examples/reference/global_css.rs

@@ -0,0 +1,10 @@
+use dioxus::prelude::*;
+fn main() {}
+
+static Example: FC<()> = |cx| {
+    cx.render(rsx! {
+        div {
+
+        }
+    })
+};

+ 10 - 0
examples/reference/inline_styles.rs

@@ -0,0 +1,10 @@
+use dioxus::prelude::*;
+fn main() {}
+
+static Example: FC<()> = |cx| {
+    cx.render(rsx! {
+        div {
+
+        }
+    })
+};

+ 10 - 0
examples/reference/iterators.rs

@@ -0,0 +1,10 @@
+use dioxus::prelude::*;
+fn main() {}
+
+static Example: FC<()> = |cx| {
+    cx.render(rsx! {
+        div {
+
+        }
+    })
+};

+ 50 - 0
examples/reference/listener.rs

@@ -0,0 +1,50 @@
+//! Example: Listeners
+//! ------------------
+//!
+//! This example demonstrates the various ways listeners may be used in Dioxus.
+//! Listeners may be at most `FnMut` - but not `FnOnce`.
+//! Listeners may borrow the lifetime of cx (children and hooks), but not of local (stack) data.
+
+use dioxus::prelude::*;
+
+fn main() {}
+
+/// We can use `set_name` in multiple closures; the closures automatically *copy* the reference to set_name.
+static ButtonList: FC<()> = |cx| {
+    let (name, set_name) = use_state(&cx, || "...?");
+
+    let names = ["jack", "jill", "john", "jane"]
+        .iter()
+        .map(move |n| rsx!(button { onclick: move |_| set_name(n), "{n}" }));
+
+    cx.render(rsx!(
+        div {
+            h1 { "Hello, {name}" }
+            {names}
+        }
+    ))
+};
+
+/// This shows how listeners may be without a visible change in the display.
+/// Check the console.
+static NonUpdatingEvents: FC<()> = |cx| {
+    rsx!(in cx, div {
+        button {
+            onclick: move |_| log::info!("Did not cause any updates!")
+            "Click me to log!"
+        }
+    })
+};
+
+static DisablePropogation: FC<()> = |cx| {
+    rsx!(in cx,
+        div {
+            onclick: move |_| log::info!("event propogated to the div!")
+            button {
+                onclick: move |evt| {
+                    log::info!("Button will allow propogation");
+                }
+            }
+        }
+    )
+};

+ 85 - 0
examples/reference/memo.rs

@@ -0,0 +1,85 @@
+//! Example: Memoization
+//! --------------------
+//!
+//! This example showcases how memoization works in Dioxus.
+//!
+//! Memoization is the process in which Dioxus skips diffing child components if their props don't change.
+//! In React, components are never memoized unless wrapped in `memo` or configured with `shouldComponentUpdate`.
+//!
+//! Due to the safety guarantees of Rust, we can automatically memoize components in some circumstances. Whenever a
+//! component's properties are valid for the `'static` lifetime, Dioxus will automatically compare the props before
+//! diffing the component. If the props don't change (according to PartialEq), the component will not be re-rendered.
+//!
+//! However, if the props use some generics or borrow from their parent, then Dioxus can't safely supress updates,
+//! and is forced to render the child. If you think that this behavior is wrong for your usecase, you can implement
+//! the memo method yourself, but beware, doing so is UNSAFE and may cause issues if you do it wrong.
+//!
+//! If you want to gain that little bit extra performance, consider using global state management, signals, or
+//! memoized collections like im-rc which are designed for this use case.
+
+use dioxus::prelude::*;
+fn main() {}
+
+// By default, components with no props are always memoized.
+// A props of () is considered empty.
+static Example: FC<()> = |cx| {
+    cx.render(rsx! {
+        div { "100% memoized!" }
+    })
+};
+
+// These props do not borrow any content, and therefore can be safely memoized.
+// However, the parent *must* create a new string on every render.
+// Notice how these props implement PartialEq - this is required for 'static props
+#[derive(PartialEq, Props)]
+struct MyProps1 {
+    name: String,
+}
+
+static Example1: FC<MyProps1> = |cx| {
+    cx.render(rsx! {
+        div { "100% memoized! {cx.name}" }
+    })
+};
+
+// These props do not borrow any content, and therefore can be safely memoized.
+// In contrast with the `String` example, these props use `Rc<str>` which operates similar to strings in JavaScript.
+// These strings cannot be modified, but may be cheaply shared in many places without issue.
+#[derive(PartialEq, Props)]
+struct MyProps2 {
+    name: std::rc::Rc<str>,
+}
+
+static Example2: FC<MyProps2> = |cx| {
+    cx.render(rsx! {
+        div { "100% memoized! {cx.name}" }
+    })
+};
+
+// These props *do* borrow any content, and therefore cannot be safely memoized!.
+#[derive(PartialEq, Props)]
+struct MyProps3<'a> {
+    name: &'a str,
+}
+// We need to manually specify a lifetime that ensures props and scope (the component's state) share the same lifetime.
+// Using the `static Example: FC<()>` pattern _will_ specify a lifetime, but that lifetime will be static which might
+// not exactly be what you want
+fn Example3<'a>(cx: Context<'a, MyProps3<'a>>) -> VNode {
+    cx.render(rsx! {
+        div { "Not memoized! {cx.name}" }
+    })
+}
+
+// These props *do* borrow any content, and therefore cannot be safely memoized!.
+// However, they cannot be compared, so we don't need the PartialEq flag.
+#[derive(Props)]
+struct MyProps4<'a> {
+    onhandle: &'a dyn Fn(),
+}
+
+// We need to manually specify a lifetime that ensures props and scope (the component's state) share the same lifetime.
+fn Example4<'a>(cx: Context<'a, MyProps4<'a>>) -> VNode {
+    cx.render(rsx! {
+        div { "Not memoized!", onclick: move |_| (cx.onhandle)() }
+    })
+}

+ 2 - 0
examples/reference/mod.rs

@@ -0,0 +1,2 @@
+mod iterator;
+mod memo;

+ 10 - 0
examples/reference/noderefs.rs

@@ -0,0 +1,10 @@
+use dioxus::prelude::*;
+fn main() {}
+
+static Example: FC<()> = |cx| {
+    cx.render(rsx! {
+        div {
+
+        }
+    })
+};

+ 10 - 0
examples/reference/signals.rs

@@ -0,0 +1,10 @@
+use dioxus::prelude::*;
+fn main() {}
+
+static Example: FC<()> = |cx| {
+    cx.render(rsx! {
+        div {
+
+        }
+    })
+};

+ 40 - 0
examples/reference/spreadpattern.rs

@@ -0,0 +1,40 @@
+//! Example: Spread pattern for Components
+//! --------------------------------------
+//!
+//! Dioxus supports  the "spread" pattern for manually building a components properties. This is useful when props
+//! are passed down from a parent, or it's more ergonomic to construct props from outside the rsx! macro.
+//!
+//! To use the spread pattern, simply pass ".." followed by a Rust epxression. This pattern also supports overriding
+//! values, using the manual props as the base and then modifying fields specified with non-spread attributes.
+
+use dioxus::prelude::*;
+fn main() {}
+
+static App: FC<()> = |cx| {
+    let props = MyProps {
+        count: 0,
+        live: true,
+        name: "Dioxus",
+    };
+    cx.render(rsx! {
+        Example { ..props, count: 10, div {"child"} }
+    })
+};
+
+#[derive(PartialEq, Props)]
+struct MyProps {
+    count: u32,
+    live: bool,
+    name: &'static str,
+}
+
+static Example: FC<MyProps> = |cx| {
+    cx.render(rsx! {
+        div {
+            h1 { "Hello, {cx.name}"}
+            h3 {"Are we alive? {cx.live}"}
+            p {"Count is {cx.count}"}
+            { cx.children() }
+        }
+    })
+};

+ 10 - 0
examples/reference/statemanagement.rs

@@ -0,0 +1,10 @@
+use dioxus::prelude::*;
+fn main() {}
+
+static Example: FC<()> = |cx| {
+    cx.render(rsx! {
+        div {
+
+        }
+    })
+};

+ 0 - 0
examples/suspense.rs → examples/reference/suspense.rs


+ 10 - 0
examples/reference/testing.rs

@@ -0,0 +1,10 @@
+use dioxus::prelude::*;
+fn main() {}
+
+static Example: FC<()> = |cx| {
+    cx.render(rsx! {
+        div {
+
+        }
+    })
+};

+ 10 - 0
examples/reference/tostring.rs

@@ -0,0 +1,10 @@
+use dioxus::prelude::*;
+fn main() {}
+
+static Example: FC<()> = |cx| {
+    cx.render(rsx! {
+        div {
+
+        }
+    })
+};

+ 21 - 0
examples/reference/webcomponents.rs

@@ -0,0 +1,21 @@
+//! Example: Web Components
+//! -----------------------
+//!
+//! Web components are a flavor of html elements that can be user-defined.
+//! See: https://www.webcomponents.org for more information.
+//!
+//! Users who use webcomponents typically don't use Dioxus. However, if you would like to use webcomponents in Dioxus,
+//! you can easily create a new custom element with compile-time correct wrappers around the webcomponent.
+//!
+//! We don't support building new webcomponents with Dioxus, however.
+//!
+
+use dioxus::{builder::ElementBuilder, prelude::NodeFactory};
+
+fn main() {}
+
+// TODO
+struct MyEle<'a, 'b> {
+    el: ElementBuilder<'a, 'b>,
+    fac: &'b NodeFactory<'a>,
+}

+ 8 - 2
examples/rsx_usage.rs

@@ -120,7 +120,7 @@ static Example: FC<()> = |cx| {
             {if true {
             {if true {
                 rsx!(in cx, h1 {"Top text"})
                 rsx!(in cx, h1 {"Top text"})
             } else {
             } else {
-                cx.render(rsx!( h1 {"Bottom text"}))
+                rsx!(in cx, h1 {"Bottom text"})
             }}
             }}
 
 
             // returning "None" is a bit noisy... but rare in practice
             // returning "None" is a bit noisy... but rare in practice
@@ -168,7 +168,13 @@ static Example: FC<()> = |cx| {
                 rsx!(Taller { ..props })
                 rsx!(Taller { ..props })
             }}
             }}
 
 
-            // Can take children
+            // Spreading can also be overridden manually
+            Taller {
+                ..TallerProps { a: "ballin!" }
+                a: "not ballin!"
+            }
+
+            // Can take children too!
             Taller { a: "asd", div {"hello world!"} }
             Taller { a: "asd", div {"hello world!"} }
         }
         }
     })
     })

+ 28 - 23
examples/showcase.rs

@@ -1,26 +1,31 @@
-//! A Showcase of all the useful examples
+//! Example: Reference Showcase
+//! ---------------------------
 //!
 //!
-//!
-//!
-
-fn main() {
-    use_css_consumer(&cx, "mystyle");
+//! This example provides a cute interface for browsing the reference examples.
 
 
-    // at the global head of the app
-    use_css_provider(&cx, |cfg| {});
-    use_recoil_provider(&cx, |cfg| {});
-
-    let recoil = use_recoil_api(&cx, |_| {});
-    use_websocket_connection(&cx, move |cfg| {
-        cfg.on_receive(move |event| match event.data::<Msg>() {
-            Ok(msg) => match msg {
-                a => recoil.set(&ATOM, 10),
-                c => recoil.set(&ATOM, 20),
-                _ => {}
-            },
-            Err(e) => {}
-        });
-        cfg.on_close(move |event| {});
-        cfg.on_open(move |event| {});
-    });
+mod reference {
+    mod antipatterns;
+    mod basics;
+    mod children;
+    mod conditional_rendering;
+    mod controlled_inputs;
+    mod custom_elements;
+    mod empty;
+    mod fiber;
+    mod fragments;
+    mod global_css;
+    mod inline_styles;
+    mod iterators;
+    mod listener;
+    mod memo;
+    mod noderefs;
+    mod signals;
+    mod spreadpattern;
+    mod statemanagement;
+    mod suspense;
+    mod testing;
+    mod tostring;
+    mod webcomponents;
 }
 }
+
+fn main() {}

+ 36 - 23
packages/core-macro/src/rsx/component.rs

@@ -80,33 +80,46 @@ impl ToTokens for Component {
     fn to_tokens(&self, tokens: &mut TokenStream2) {
     fn to_tokens(&self, tokens: &mut TokenStream2) {
         let name = &self.name;
         let name = &self.name;
 
 
-        let using_manual_override = self.manual_props.is_some();
-
-        let mut builder = {
-            match &self.manual_props {
-                Some(manual_props) => quote! { #manual_props },
-                None => quote! { fc_to_builder(#name) },
-            }
-        };
-
         let mut has_key = None;
         let mut has_key = None;
 
 
-        for field in &self.body {
-            if field.name.to_string() == "key" {
-                has_key = Some(field);
-            } else {
-                match using_manual_override {
-                    true => panic!("Currently we don't support manual props and prop fields. Choose either manual props or prop fields"),
-                    false => builder.append_all(quote! {#field}),
+        let builder = match &self.manual_props {
+            Some(manual_props) => {
+                let mut toks = quote! {
+                    let mut __manual_props = #manual_props;
+                };
+                for field in &self.body {
+                    if field.name.to_string() == "key" {
+                        has_key = Some(field);
+                    } 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
+                }}
             }
             }
-        }
-
-        if !using_manual_override {
-            builder.append_all(quote! {
-                .build()
-            });
-        }
+            None => {
+                let mut toks = quote! { fc_to_builder(#name) };
+                for field in &self.body {
+                    if field.name.to_string() == "key" {
+                        has_key = Some(field);
+                    } else {
+                        toks.append_all(quote! {#field})
+                    }
+                }
+                toks.append_all(quote! {
+                    .build()
+                });
+                toks
+            }
+        };
 
 
         let key_token = match has_key {
         let key_token = match has_key {
             Some(field) => {
             Some(field) => {

+ 61 - 52
packages/core/src/events.rs

@@ -4,7 +4,7 @@
 //! 3rd party renderers are responsible for converting their native events into these virtual event types. Events might
 //! 3rd party renderers are responsible for converting their native events into these virtual event types. Events might
 //! be heavy or need to interact through FFI, so the events themselves are designed to be lazy.
 //! be heavy or need to interact through FFI, so the events themselves are designed to be lazy.
 
 
-use std::rc::Rc;
+use std::{ops::Deref, rc::Rc};
 
 
 use crate::{innerlude::ScopeIdx, virtual_dom::RealDomNode};
 use crate::{innerlude::ScopeIdx, virtual_dom::RealDomNode};
 
 
@@ -28,21 +28,21 @@ impl EventTrigger {
 #[derive(Debug)]
 #[derive(Debug)]
 pub enum VirtualEvent {
 pub enum VirtualEvent {
     // Real events
     // Real events
-    ClipboardEvent(Rc<dyn on::ClipboardEvent>),
-    CompositionEvent(Rc<dyn on::CompositionEvent>),
-    KeyboardEvent(Rc<dyn on::KeyboardEvent>),
-    FocusEvent(Rc<dyn on::FocusEvent>),
-    FormEvent(Rc<dyn on::FormEvent>),
-    SelectionEvent(Rc<dyn on::SelectionEvent>),
-    TouchEvent(Rc<dyn on::TouchEvent>),
-    UIEvent(Rc<dyn on::UIEvent>),
-    WheelEvent(Rc<dyn on::WheelEvent>),
-    MediaEvent(Rc<dyn on::MediaEvent>),
-    AnimationEvent(Rc<dyn on::AnimationEvent>),
-    TransitionEvent(Rc<dyn on::TransitionEvent>),
-    ToggleEvent(Rc<dyn on::ToggleEvent>),
-    MouseEvent(Rc<dyn on::MouseEvent>),
-    PointerEvent(Rc<dyn on::PointerEvent>),
+    ClipboardEvent(on::ClipboardEvent),
+    CompositionEvent(on::CompositionEvent),
+    KeyboardEvent(on::KeyboardEvent),
+    FocusEvent(on::FocusEvent),
+    FormEvent(on::FormEvent),
+    SelectionEvent(on::SelectionEvent),
+    TouchEvent(on::TouchEvent),
+    UIEvent(on::UIEvent),
+    WheelEvent(on::WheelEvent),
+    MediaEvent(on::MediaEvent),
+    AnimationEvent(on::AnimationEvent),
+    TransitionEvent(on::TransitionEvent),
+    ToggleEvent(on::ToggleEvent),
+    MouseEvent(on::MouseEvent),
+    PointerEvent(on::PointerEvent),
 
 
     // Whenever a task is ready (complete) Dioxus produces this "FiberEvent"
     // Whenever a task is ready (complete) Dioxus produces this "FiberEvent"
     FiberEvent { task_id: u16 },
     FiberEvent { task_id: u16 },
@@ -75,12 +75,21 @@ pub mod on {
     use super::VirtualEvent;
     use super::VirtualEvent;
 
 
     macro_rules! event_directory {
     macro_rules! event_directory {
-        ( $( $eventdata:ident: [ $( $name:ident )* ]; )* ) => {
+        ( $( $eventdata:ident($wrapper:ident): [ $( $name:ident )* ]; )* ) => {
             $(
             $(
+                #[derive(Debug)]
+                pub struct $wrapper(Rc<dyn $eventdata>);
+                impl Deref for $wrapper {
+                    type Target = Rc<dyn $eventdata>;
+                    fn deref(&self) -> &Self::Target {
+                        &self.0
+                    }
+                }
+
                 $(
                 $(
                     pub fn $name<'a>(
                     pub fn $name<'a>(
                         c: &'_ NodeFactory<'a>,
                         c: &'_ NodeFactory<'a>,
-                        callback: impl Fn(Rc<dyn $eventdata>) + 'a,
+                        callback: impl Fn($wrapper) + 'a,
                     ) -> Listener<'a> {
                     ) -> Listener<'a> {
                         let bump = &c.bump();
                         let bump = &c.bump();
                         Listener {
                         Listener {
@@ -88,7 +97,7 @@ pub mod on {
                             mounted_node: bump.alloc(Cell::new(RealDomNode::empty())),
                             mounted_node: bump.alloc(Cell::new(RealDomNode::empty())),
                             scope: c.scope_ref.arena_idx,
                             scope: c.scope_ref.arena_idx,
                             callback: bump.alloc(move |evt: VirtualEvent| match evt {
                             callback: bump.alloc(move |evt: VirtualEvent| match evt {
-                                VirtualEvent::$eventdata(event) => callback(event),
+                                VirtualEvent::$wrapper(event) => callback(event),
                                 _ => unreachable!("Downcasted VirtualEvent to wrong event type - this is an internal bug!")
                                 _ => unreachable!("Downcasted VirtualEvent to wrong event type - this is an internal bug!")
                             }),
                             }),
                         }
                         }
@@ -99,36 +108,36 @@ pub mod on {
     }
     }
 
 
     event_directory! {
     event_directory! {
-        ClipboardEvent: [copy cut paste];
-        CompositionEvent: [compositionend compositionstart compositionupdate];
-        KeyboardEvent: [keydown keypress keyup];
-        FocusEvent: [focus blur];
-        FormEvent: [change input invalid reset submit];
-        MouseEvent: [
+        ClipboardEventInner(ClipboardEvent): [copy cut paste];
+        CompositionEventInner(CompositionEvent): [compositionend compositionstart compositionupdate];
+        KeyboardEventInner(KeyboardEvent): [keydown keypress keyup];
+        FocusEventInner(FocusEvent): [focus blur];
+        FormEventInner(FormEvent): [change input invalid reset submit];
+        MouseEventInner(MouseEvent): [
             click contextmenu doubleclick drag dragend dragenter dragexit
             click contextmenu doubleclick drag dragend dragenter dragexit
             dragleave dragover dragstart drop mousedown mouseenter mouseleave
             dragleave dragover dragstart drop mousedown mouseenter mouseleave
             mousemove mouseout mouseover mouseup
             mousemove mouseout mouseover mouseup
         ];
         ];
-        PointerEvent: [
+        PointerEventInner(PointerEvent): [
             pointerdown pointermove pointerup pointercancel gotpointercapture
             pointerdown pointermove pointerup pointercancel gotpointercapture
             lostpointercapture pointerenter pointerleave pointerover pointerout
             lostpointercapture pointerenter pointerleave pointerover pointerout
         ];
         ];
-        SelectionEvent: [select];
-        TouchEvent: [touchcancel touchend touchmove touchstart];
-        UIEvent: [scroll];
-        WheelEvent: [wheel];
-        MediaEvent: [
+        SelectionEventInner(SelectionEvent): [select];
+        TouchEventInner(TouchEvent): [touchcancel touchend touchmove touchstart];
+        UIEventInner(UIEvent): [scroll];
+        WheelEventInner(WheelEvent): [wheel];
+        MediaEventInner(MediaEvent): [
             abort canplay canplaythrough durationchange emptied encrypted
             abort canplay canplaythrough durationchange emptied encrypted
             ended error loadeddata loadedmetadata loadstart pause play
             ended error loadeddata loadedmetadata loadstart pause play
             playing progress ratechange seeked seeking stalled suspend
             playing progress ratechange seeked seeking stalled suspend
             timeupdate volumechange waiting
             timeupdate volumechange waiting
         ];
         ];
-        AnimationEvent: [animationstart animationend animationiteration];
-        TransitionEvent: [transitionend];
-        ToggleEvent: [toggle];
+        AnimationEventInner(AnimationEvent): [animationstart animationend animationiteration];
+        TransitionEventInner(TransitionEvent): [transitionend];
+        ToggleEventInner(ToggleEvent): [toggle];
     }
     }
 
 
-    trait GenericEvent {
+    pub trait GenericEventInner {
         /// Returns whether or not a specific event is a bubbling event
         /// Returns whether or not a specific event is a bubbling event
         fn bubbles(&self) -> bool;
         fn bubbles(&self) -> bool;
         /// Sets or returns whether the event should propagate up the hierarchy or not
         /// Sets or returns whether the event should propagate up the hierarchy or not
@@ -159,15 +168,15 @@ pub mod on {
         fn time_stamp(&self) -> usize;
         fn time_stamp(&self) -> usize;
     }
     }
 
 
-    pub trait ClipboardEvent: Debug {
+    pub trait ClipboardEventInner: Debug + GenericEventInner {
         // DOMDataTransfer clipboardData
         // DOMDataTransfer clipboardData
     }
     }
 
 
-    pub trait CompositionEvent: Debug {
+    pub trait CompositionEventInner: Debug {
         fn data(&self) -> String;
         fn data(&self) -> String;
     }
     }
 
 
-    pub trait KeyboardEvent: Debug {
+    pub trait KeyboardEventInner: Debug {
         fn char_code(&self) -> usize;
         fn char_code(&self) -> usize;
         fn ctrl_key(&self) -> bool;
         fn ctrl_key(&self) -> bool;
         fn key(&self) -> String;
         fn key(&self) -> String;
@@ -181,15 +190,15 @@ pub mod on {
         fn get_modifier_state(&self, key_code: usize) -> bool;
         fn get_modifier_state(&self, key_code: usize) -> bool;
     }
     }
 
 
-    pub trait FocusEvent: Debug {
-        /* DOMEventTarget relatedTarget */
+    pub trait FocusEventInner: Debug {
+        /* DOMEventInnerTarget relatedTarget */
     }
     }
 
 
-    pub trait FormEvent: Debug {
+    pub trait FormEventInner: Debug {
         fn value(&self) -> String;
         fn value(&self) -> String;
     }
     }
 
 
-    pub trait MouseEvent: Debug {
+    pub trait MouseEventInner: Debug {
         fn alt_key(&self) -> bool;
         fn alt_key(&self) -> bool;
         fn button(&self) -> i16;
         fn button(&self) -> i16;
         fn buttons(&self) -> u16;
         fn buttons(&self) -> u16;
@@ -205,7 +214,7 @@ pub mod on {
         fn get_modifier_state(&self, key_code: &str) -> bool;
         fn get_modifier_state(&self, key_code: &str) -> bool;
     }
     }
 
 
-    pub trait PointerEvent: Debug {
+    pub trait PointerEventInner: Debug {
         // Mouse only
         // Mouse only
         fn alt_key(&self) -> bool;
         fn alt_key(&self) -> bool;
         fn button(&self) -> usize;
         fn button(&self) -> usize;
@@ -232,9 +241,9 @@ pub mod on {
         fn is_primary(&self) -> bool;
         fn is_primary(&self) -> bool;
     }
     }
 
 
-    pub trait SelectionEvent: Debug {}
+    pub trait SelectionEventInner: Debug {}
 
 
-    pub trait TouchEvent: Debug {
+    pub trait TouchEventInner: Debug {
         fn alt_key(&self) -> bool;
         fn alt_key(&self) -> bool;
         fn ctrl_key(&self) -> bool;
         fn ctrl_key(&self) -> bool;
         fn meta_key(&self) -> bool;
         fn meta_key(&self) -> bool;
@@ -245,37 +254,37 @@ pub mod on {
         // touches: DOMTouchList,
         // touches: DOMTouchList,
     }
     }
 
 
-    pub trait UIEvent: Debug {
+    pub trait UIEventInner: Debug {
         // DOMAbstractView view
         // DOMAbstractView view
         fn detail(&self) -> i32;
         fn detail(&self) -> i32;
     }
     }
 
 
-    pub trait WheelEvent: Debug {
+    pub trait WheelEventInner: Debug {
         fn delta_mode(&self) -> i32;
         fn delta_mode(&self) -> i32;
         fn delta_x(&self) -> i32;
         fn delta_x(&self) -> i32;
         fn delta_y(&self) -> i32;
         fn delta_y(&self) -> i32;
         fn delta_z(&self) -> i32;
         fn delta_z(&self) -> i32;
     }
     }
 
 
-    pub trait MediaEvent: Debug {}
+    pub trait MediaEventInner: Debug {}
 
 
-    pub trait ImageEvent: Debug {
+    pub trait ImageEventInner: Debug {
         //     load error
         //     load error
     }
     }
 
 
-    pub trait AnimationEvent: Debug {
+    pub trait AnimationEventInner: Debug {
         fn animation_name(&self) -> String;
         fn animation_name(&self) -> String;
         fn pseudo_element(&self) -> String;
         fn pseudo_element(&self) -> String;
         fn elapsed_time(&self) -> f32;
         fn elapsed_time(&self) -> f32;
     }
     }
 
 
-    pub trait TransitionEvent: Debug {
+    pub trait TransitionEventInner: Debug {
         fn property_name(&self) -> String;
         fn property_name(&self) -> String;
         fn pseudo_element(&self) -> String;
         fn pseudo_element(&self) -> String;
         fn elapsed_time(&self) -> f32;
         fn elapsed_time(&self) -> f32;
     }
     }
 
 
-    pub trait ToggleEvent: Debug {}
+    pub trait ToggleEventInner: Debug {}
 }
 }
 
 
 mod tests {
 mod tests {

+ 19 - 0
packages/core/src/nodebuilder.rs

@@ -768,6 +768,25 @@ impl<'a> NodeFactory<'a> {
     > {
     > {
         ElementBuilder::new(self, tag_name)
         ElementBuilder::new(self, tag_name)
     }
     }
+
+    pub fn child_list(&self) -> ChildrenList {
+        ChildrenList::new(&self)
+    }
+
+    pub fn fragment_builder<'b>(
+        &'b self,
+        key: Option<&'a str>,
+        builder: impl FnOnce(ChildrenList<'a, 'b>) -> &'a [VNode<'a>],
+    ) -> VNode<'a> {
+        self.fragment(builder(ChildrenList::new(&self)), key)
+    }
+
+    pub fn fragment(&self, children: &'a [VNode<'a>], key: Option<&'a str>) -> VNode<'a> {
+        VNode::Fragment(self.bump().alloc(VFragment {
+            children,
+            key: NodeKey::new_opt(key),
+        }))
+    }
 }
 }
 
 
 use std::fmt::Debug;
 use std::fmt::Debug;

+ 5 - 0
packages/core/src/nodes.rs

@@ -297,6 +297,11 @@ impl<'a> NodeKey<'a> {
     pub fn new(key: &'a str) -> Self {
     pub fn new(key: &'a str) -> Self {
         NodeKey(Some(key))
         NodeKey(Some(key))
     }
     }
+
+    #[inline]
+    pub fn new_opt(key: Option<&'a str>) -> Self {
+        NodeKey(key)
+    }
 }
 }
 
 
 // ==============================
 // ==============================