瀏覽代碼

Feat: more ergonomics, more examples

Jonathan Kelley 4 年之前
父節點
當前提交
0bcff1f

+ 3 - 3
README.md

@@ -12,13 +12,13 @@ Dioxus is a portable, performant, and ergonomic framework for building cross-pla
 
 ```rust
 static Example: FC<()> = |ctx, props| {
-    let (selection, set_selection) = use_state(&ctx, || "...?");
+    let selection = use_state(&ctx, || "...?");
 
     ctx.render(rsx! {
         div {
             h1 { "Hello, {selection}" }
-            button { "?", onclick: move |_| set_selection("world!")}
-            button { "?", onclick: move |_| set_selection("Dioxus 🎉")}
+            button { "?", onclick: move |_| selection.set("world!")}
+            button { "?", onclick: move |_| selection.set("Dioxus 🎉")}
         }
     })
 };

+ 45 - 230
packages/core-macro/src/rsxt.rs

@@ -1,42 +1,3 @@
-/*
-
-
-https://github.com/chinedufn/percy/issues/37
-
-An example usage of rsx! would look like this:
-```ignore
-ctx.render(rsx!{
-    div {
-        h1 { "Example" },
-        p {
-            tag: "type",
-            abc: 123,
-            enabled: true,
-            class: "big small wide short",
-
-            a { "abcder" },
-            h2 { "whatsup", class: "abc-123" },
-            CustomComponent { a: 123, b: 456, key: "1" },
-            { 0..3.map(|i| rsx!{ h1 {"{:i}"} }) },
-            {expr}
-
-            // expr can take:
-            // - iterator
-            // - |bump| { }
-            // - value (gets formatted as &str)
-            // - ... more as we upgrade it
-        }
-    }
-})
-```
-
-optionally, include the allocator directly
-
-rsx!(ctx, div { "hello"} )
-
-each element is given by tag { [Attr] }
-
-*/
 use syn::parse::{discouraged::Speculative, ParseBuffer};
 
 use {
@@ -72,11 +33,6 @@ impl Parse for RsxRender {
             })
             .ok();
 
-        // cannot accept multiple elements
-        // can only accept one root per component
-        // fragments can be used as
-        // todo
-        // enable fragements by autocoerrcing into list
         let el: Element = input.parse()?;
         Ok(Self { el, custom_context })
     }
@@ -85,7 +41,6 @@ impl Parse for RsxRender {
 impl ToTokens for RsxRender {
     fn to_tokens(&self, out_tokens: &mut TokenStream2) {
         let new_toks = (&self.el).to_token_stream();
-        // let new_toks = ToToksCtx::new(&self.kind).to_token_stream();
 
         // create a lazy tree that accepts a bump allocator
         let final_tokens = match &self.custom_context {
@@ -130,42 +85,40 @@ impl ToTokens for &Node {
 
 impl Parse for Node {
     fn parse(stream: ParseStream) -> Result<Self> {
-        let fork1 = stream.fork();
-        let fork2 = stream.fork();
-        let fork3 = stream.fork();
-
-        // todo: map issues onto the second fork if any arise
-        // it's unlikely that textnodes or components would fail?
-
-        let ret = if let Ok(text) = fork1.parse::<TextNode>() {
-            stream.advance_to(&fork1);
-            Self::Text(text)
-        } else if let Ok(element) = fork2.parse::<Element>() {
-            stream.advance_to(&fork2);
-            Self::Element(element)
-        } else if let Ok(comp) = fork3.parse::<Component>() {
-            stream.advance_to(&fork3);
-            Self::Component(comp)
-        } else {
-            return Err(Error::new(
-                stream.span(),
-                "Failed to parse as a valid child",
-            ));
-        };
+        // Supposedly this approach is discouraged due to inability to return proper errors
+        // TODO: Rework this to provide more informative errors
 
-        // consume comma if it exists
-        // we don't actually care if there *are* commas after elements/text
-        if stream.peek(Token![,]) {
-            let _ = stream.parse::<Token![,]>();
+        let fork = stream.fork();
+        if let Ok(text) = fork.parse::<TextNode>() {
+            stream.advance_to(&fork);
+            return Ok(Self::Text(text));
         }
-        Ok(ret)
+
+        let fork = stream.fork();
+        if let Ok(element) = fork.parse::<Element>() {
+            stream.advance_to(&fork);
+            return Ok(Self::Element(element));
+        }
+
+        let fork = stream.fork();
+        if let Ok(comp) = fork.parse::<Component>() {
+            stream.advance_to(&fork);
+            return Ok(Self::Component(comp));
+        }
+
+        let fork = stream.fork();
+        if let Ok(tok) = try_parse_bracketed(&fork) {
+            stream.advance_to(&fork);
+            return Ok(Node::RawExpr(tok));
+        }
+
+        return Err(Error::new(stream.span(), "Failed to parse as a valid node"));
     }
 }
 
 struct Component {
     name: Ident,
     body: Vec<ComponentField>,
-    // attrs: Vec<Attr>,
     children: Vec<Node>,
 }
 
@@ -185,38 +138,6 @@ impl ToTokens for &Component {
             .build()
         });
 
-        // let mut toks = quote! {};
-
-        // for attr in self.inner.attrs.iter() {
-        //     self.recurse(attr).to_tokens(&mut toks);
-        // }
-
-        // panic!("tokens are {:#?}", toks);
-
-        // no children right now
-
-        // match &self.inner.children {
-        //     MaybeExpr::Expr(expr) => tokens.append_all(quote! {
-        //         .children(#expr)
-        //     }),
-        //     MaybeExpr::Literal(nodes) => {
-        //         let mut children = nodes.iter();
-        //         if let Some(child) = children.next() {
-        //             let mut inner_toks = TokenStream2::new();
-        //             self.recurse(child).to_tokens(&mut inner_toks);
-        //             while let Some(child) = children.next() {
-        //                 quote!(,).to_tokens(&mut inner_toks);
-        //                 self.recurse(child).to_tokens(&mut inner_toks);
-        //             }
-        //             tokens.append_all(quote! {
-        //                 .children([#inner_toks])
-        //             });
-        //         }
-        //     }
-        // }
-        // tokens.append_all(quote! {
-        //     .finish()
-        // });
         let _toks = tokens.append_all(quote! {
             dioxus::builder::virtual_child(ctx, #name, #builder)
         });
@@ -225,8 +146,6 @@ impl ToTokens for &Component {
 
 impl Parse for Component {
     fn parse(s: ParseStream) -> Result<Self> {
-        // TODO: reject anything weird/nonstandard
-        // we want names *only*
         let name = Ident::parse_any(s)?;
 
         if crate::util::is_valid_tag(&name.to_string()) {
@@ -252,11 +171,16 @@ impl Parse for Component {
             if let Ok(field) = content.parse::<ComponentField>() {
                 body.push(field);
             }
+
+            // consume comma if it exists
+            // we don't actually care if there *are* commas between attrs
+            if content.peek(Token![,]) {
+                let _ = content.parse::<Token![,]>();
+            }
         }
 
         // todo: add support for children
         let children: Vec<Node> = vec![];
-        // let children = MaybeExpr::Literal(children);
 
         Ok(Self {
             name,
@@ -275,25 +199,16 @@ pub struct ComponentField {
 impl Parse for ComponentField {
     fn parse(input: ParseStream) -> Result<Self> {
         let name = Ident::parse_any(input)?;
-        let _name_str = name.to_string();
         input.parse::<Token![:]>()?;
         let content = input.parse()?;
 
-        // consume comma if it exists
-        // we don't actually care if there *are* commas between attrs
-        if input.peek(Token![,]) {
-            let _ = input.parse::<Token![,]>();
-        }
-
         Ok(Self { name, content })
     }
 }
 
 impl ToTokens for &ComponentField {
-    // impl ToTokens for ToToksCtx<&ComponentField> {
     fn to_tokens(&self, tokens: &mut TokenStream2) {
-        let name = &self.name;
-        let content = &self.content;
+        let ComponentField { name, content, .. } = self;
         tokens.append_all(quote! {
             .#name(#content)
         })
@@ -307,7 +222,6 @@ struct Element {
     name: Ident,
     attrs: Vec<Attr>,
     children: Vec<Node>,
-    // children: MaybeExpr<Vec<Node>>,
 }
 
 impl ToTokens for &Element {
@@ -321,46 +235,15 @@ impl ToTokens for &Element {
         for attr in self.attrs.iter() {
             attr.to_tokens(tokens);
         }
-        // match &self.children {
-        //     // MaybeExpr::Expr(expr) => tokens.append_all(quote! {
-        //     //     .iter_child(#expr)
-        //     // }),
-        //     MaybeExpr::Literal(nodes) => {
-        // let mut children = nodes.iter();
+
         let mut children = self.children.iter();
         while let Some(child) = children.next() {
-            // if let Some(child) = children.next() {
-            // let mut inner_toks = TokenStream2::new();
-            // child.to_tokens(&mut inner_toks);
-            // while let Some(child) = children.next() {
-            match child {
-                // raw exprs need to be wrapped in a once type?
-                Node::RawExpr(_) => {
-                    let inner_toks = child.to_token_stream();
-                    tokens.append_all(quote! {
-                        .iter_child(std::iter::once(#inner_toks))
-                    })
-                }
-                _ => {
-                    let inner_toks = child.to_token_stream();
-                    tokens.append_all(quote! {
-                        .iter_child(#inner_toks)
-                    })
-                }
-            }
-            // quote!(,).to_tokens(&mut inner_toks);
-            // child.to_tokens(&mut inner_toks);
-            // }
-            // while let Some(child) = children.next() {
-            //     quote!(,).to_tokens(&mut inner_toks);
-            //     child.to_tokens(&mut inner_toks);
-            // }
-            // tokens.append_all(quote! {
-            //     .iter_child([#inner_toks])
-            // });
+            let inner_toks = child.to_token_stream();
+            tokens.append_all(quote! {
+                .iter_child(#inner_toks)
+            })
         }
-        // }
-        // }
+
         tokens.append_all(quote! {
             .finish()
         });
@@ -383,8 +266,6 @@ impl Parse for Element {
         let mut children: Vec<Node> = vec![];
         parse_element_content(content, &mut attrs, &mut children)?;
 
-        // let children = MaybeExpr::Literal(children);
-
         Ok(Self {
             name,
             attrs,
@@ -400,12 +281,11 @@ fn parse_element_content(
     children: &mut Vec<Node>,
 ) -> Result<()> {
     'parsing: loop {
-        // todo move this around into a more functional style
-        // [1] Break if empty
-        // [2] Try to consume an attr (with comma)
-        // [3] Try to consume a child node (with comma)
-        // [4] Try to consume brackets as anything thats Into<Node>
-        // [last] Fail if none worked
+        // consume comma if it exists
+        // we don't actually care if there *are* commas after elements/text
+        if stream.peek(Token![,]) {
+            let _ = stream.parse::<Token![,]>();
+        }
 
         // [1] Break if empty
         if stream.is_empty() {
@@ -430,33 +310,6 @@ fn parse_element_content(
             continue 'parsing;
         }
 
-        // [4] TODO: Parsing brackets
-
-        let fork = stream.fork();
-        if fork.peek(token::Brace) {
-            // todo!("Add support");
-            // this can fail (mismatched brackets)
-            // let content;
-            // syn::braced!(content in &stream);
-            match try_parse_bracketed(&fork) {
-                Ok(tok) => {
-                    children.push(Node::RawExpr(tok))
-                    // todo!("succeeded")
-                }
-                Err(e) => {
-                    todo!("failed {}", e)
-                }
-            }
-            stream.advance_to(&fork);
-            continue 'parsing;
-            // let fork = content.fork();
-            // stream.advance_to(fork)
-        }
-        // if let Ok(el) = fork.parse() {
-        //     children.push(el);
-        //     continue 'parsing;
-        // }
-
         // todo: pass a descriptive error onto the offending tokens
         panic!("Entry is not an attr or element\n {}", stream)
     }
@@ -471,9 +324,6 @@ fn try_parse_bracketed(stream: &ParseBuffer) -> Result<Expr> {
 /// =======================================
 /// Parse a VElement's Attributes
 /// =======================================
-/// - [ ] Allow expressions as attribute
-///
-///
 struct Attr {
     name: Ident,
     ty: AttrType,
@@ -540,7 +390,6 @@ impl Parse for Attr {
 }
 
 impl ToTokens for &Attr {
-    // impl ToTokens for ToToksCtx<&Attr> {
     fn to_tokens(&self, tokens: &mut TokenStream2) {
         let name = self.name.to_string();
         let nameident = &self.name;
@@ -555,13 +404,11 @@ impl ToTokens for &Attr {
                 tokens.append_all(quote! {
                     .add_listener(dioxus::events::on::#nameident(ctx, #event))
                 });
-                // .on(#name, #event)
             }
             AttrType::Tok(exp) => {
                 tokens.append_all(quote! {
                     .add_listener(dioxus::events::on::#nameident(ctx, #exp))
                 });
-                // .on(#name, #exp)
             }
         }
     }
@@ -571,17 +418,12 @@ enum AttrType {
     Value(LitStr),
     Event(ExprClosure),
     Tok(Expr),
-    // todo Bool(MaybeExpr<LitBool>)
 }
 
 // =======================================
 // Parse just plain text
 // =======================================
-// - [ ] Perform formatting automatically
-//
-//
 struct TextNode(LitStr);
-// struct TextNode(MaybeExpr<LitStr>);
 
 impl Parse for TextNode {
     fn parse(s: ParseStream) -> Result<Self> {
@@ -590,9 +432,8 @@ impl Parse for TextNode {
 }
 
 impl ToTokens for TextNode {
-    // impl ToTokens for ToToksCtx<&TextNode> {
-    // self.recurse(&self.inner.0).to_tokens(&mut token_stream);
     fn to_tokens(&self, tokens: &mut TokenStream2) {
+        // todo: use heuristics to see if we can promote to &static str
         let token_stream = &self.0.to_token_stream();
         tokens.append_all(quote! {
             {
@@ -604,29 +445,3 @@ impl ToTokens for TextNode {
         });
     }
 }
-
-// enum MaybeExpr<T> {
-//     Literal(T),
-//     Expr(Expr),
-// }
-
-// impl<T: Parse> Parse for MaybeExpr<T> {
-//     fn parse(s: ParseStream) -> Result<Self> {
-//         if s.peek(token::Brace) {
-//             let content;
-//             syn::braced!(content in s);
-//             Ok(MaybeExpr::Expr(content.parse()?))
-//         } else {
-//             Ok(MaybeExpr::Literal(s.parse()?))
-//         }
-//     }
-// }
-
-// impl<'a, T: 'a + ToTokens> ToTokens for &'a MaybeExpr<T> {
-//     fn to_tokens(&self, tokens: &mut TokenStream2) {
-//         match &self {
-//             MaybeExpr::Literal(v) => v.to_tokens(tokens),
-//             MaybeExpr::Expr(expr) => expr.to_tokens(tokens),
-//         }
-//     }
-// }

+ 14 - 148
packages/core/examples/borrowed.rs

@@ -7,6 +7,8 @@
 
 fn main() {}
 
+use std::borrow::Borrow;
+
 use dioxus_core::prelude::*;
 
 struct Props {
@@ -19,8 +21,8 @@ struct ListItem {
     age: u32,
 }
 
-fn app(ctx: Context, props: &Props) -> DomTree {
-    let (_f, setter) = use_state(&ctx, || 0);
+fn app<'a>(ctx: Context<'a>, props: &Props) -> DomTree {
+    let val = use_state(&ctx, || 0);
 
     ctx.render(dioxus::prelude::LazyNodes::new(move |c| {
         let mut root = builder::ElementBuilder::new(c, "div");
@@ -34,7 +36,7 @@ fn app(ctx: Context, props: &Props) -> DomTree {
                 // create the props with nothing but the fc<T>
                 fc_to_builder(ChildItem)
                     .item(child)
-                    .item_handler(setter)
+                    .item_handler(val.setter())
                     .build(),
             ));
         }
@@ -42,15 +44,6 @@ fn app(ctx: Context, props: &Props) -> DomTree {
     }))
 }
 
-type StateSetter<T> = dyn Fn(T);
-// struct StateSetter<T>(dyn Fn(T));
-
-// impl<T> PartialEq for StateSetter<T> {
-//     fn eq(&self, other: &Self) -> bool {
-//         self as *const _ == other as *const _
-//     }
-// }
-
 // props should derive a partialeq implementation automatically, but implement ptr compare for & fields
 #[derive(Props)]
 struct ChildProps<'a> {
@@ -58,153 +51,26 @@ struct ChildProps<'a> {
     item: &'a ListItem,
 
     // Even pass down handlers!
-    item_handler: &'a StateSetter<i32>,
+    item_handler: &'a dyn Fn(i32),
 }
 
 impl PartialEq for ChildProps<'_> {
     fn eq(&self, _other: &Self) -> bool {
-        // assume the dyn fn is never stable -
-        // wrap with use_callback if it's an issue for you
         false
     }
 }
 
-fn ChildItem(_ctx: Context, _props: &ChildProps) -> DomTree {
-    todo!()
-    //     ctx.render(rsx! {
-    //         div {
-    //             item: child,
-    //             handler: setter,
-    //             abc: 123,
-    //             onclick: props.item_handler,
-
-    //             h1 { "abcd123" }
-    //             h2 { "abcd123" }
-    //             div {
-    //                 "abcd123"
-    //                 h2 { }
-    //                 p { }
-    //             },
-    //         }
-    //     })
-}
-
-/*
-
-
-
-
-rsx! {
-    ChildItem {
-        // props
-        item: child, handler: setter,
-
-        // children
-        div { class:"abcd", abc: 123 },
-        div { class:"abcd", abc: 123 },
-
-        // Auto-text coercion
-        "eyo matie {abc}",
-
-        // Anything that accepts Into<VChild>
-        {},
-    }
-}
-
-// dreaming of this syntax
-#[derive(Properties)]
-struct ChildProps<'a> {
-    username:  &'a str,
-    item_handler: &'a dyn Fn(i32),
-}
-
-fn child_item(ctx: Context, props: &ChildProps) -> DomTree {
+fn ChildItem<'a>(ctx: Context<'a>, props: &ChildProps) -> DomTree {
     ctx.render(rsx! {
         div {
-            class: "abc123",
-            abc: 123,
-            onclick: props.item_handler,
-
-            h1 { "Hello, {props.username}!" },
-            h2 { "Welcome the RSX syntax" },
+            onclick: move |evt| (props.item_handler)(10)
+            h1 { "abcd123" }
+            h2 { "abcd123" }
             div {
-                h3 { "This is a subheader" }
-                button {
-                  onclick: props.handler,
-                  "This is a button"
-                  }
-                "This is child text"
-            },
+                "abcd123"
+                h2 { }
+                p { }
+            }
         }
     })
 }
-
-// This is also nice
-
-#[dioxus::component]
-static CHILD: FC = |ctx, username: &str, handler: &dyn Fn(i32)| {
-    ctx.render(rsx! {
-        div {
-            class: "abc123",
-            abc: 123,
-            onclick: handler,
-
-            h1 { "Hello, {username}!" },
-            h2 { "Welcome the RSX syntax" },
-            div {
-                h3 { "This is a subheader" }
-                button {
-                  onclick: props.handler,
-                  "This is a button"
-                  }
-                "This is child text"
-            },
-        }
-    })
-}
-Menlo, Monaco, 'Courier New', monospace
-
-
-
-struct Item {
-  name: String,
-  content: String,
-}
-
-#[dioxus::live_component]
-static CHILD: FC = |ctx, username: &str, handler: &dyn Fn(i32)| {
-      // return lazy nodes or
-      let ssr = ctx.suspend(async {
-        let data = fetch("https://google.com")
-                      .await?
-                      .json::<Item>()
-                      .await?;
-          rsx! {
-          div {
-            h1 { "Welcome: {data.name}" }
-            p { "Content: \n {data.content}" }
-          }
-          }
-      });
-
-    ctx.render(rsx! {
-        div {
-            class: "abc123",
-            abc: 123,
-            onclick: handler,
-
-            h1 { "Hello, {username}!" },
-            h2 { "Welcome the RSX syntax" },
-            div {
-                h3 { "This is a subheader" }
-                button {
-                  onclick: props.handler,
-                  "This is a button"
-                  }
-                {ssr}
-            },
-        }
-    })
-}
-
-*/

+ 3 - 3
packages/core/examples/listener.rs

@@ -11,13 +11,13 @@ fn main() {
 }
 
 static Example: FC<()> = |ctx, props| {
-    let (name, set_name) = use_state(&ctx, || "...?");
+    let name = use_state(&ctx, || "...?");
 
     ctx.render(html! {
         <div>
             <h1> "Hello, {name}" </h1>
-            <button onclick={move |_| set_name("jack")}> "jack!" </button>
-            <button onclick={move |_| set_name("jill")}> "jill!" </button>
+            <button onclick={move |_| name.set("jack")}> "jack!" </button>
+            <button onclick={move |_| name.set("jill")}> "jill!" </button>
         </div>
     })
 };

+ 2 - 2
packages/core/src/context.rs

@@ -61,9 +61,9 @@ impl<'a> Context<'a> {
     /// Create a suspended component from a future.
     ///
     /// When the future completes, the component will be renderered
-    pub fn suspend(
+    pub fn suspend<F: for<'b> FnOnce(&'b NodeCtx<'a>) -> VNode<'a> + 'a>(
         &self,
-        _fut: impl Future<Output = impl FnOnce(&'a NodeCtx<'a>) -> VNode<'a>>,
+        _fut: impl Future<Output = LazyNodes<'a, F>>,
     ) -> VNode<'a> {
         todo!()
     }

+ 27 - 0
packages/core/src/debug_renderer.rs

@@ -40,6 +40,33 @@ impl DebugRenderer {
     pub fn step(&mut self, machine: &mut DiffMachine) -> Result<()> {
         Ok(())
     }
+
+    // this does a "holy" compare - if something is missing in the rhs, it doesn't complain.
+    // it only complains if something shows up that's not in the lhs, *or* if a value is different.
+    // This lets you exclude various fields if you just want to drill in to a specific prop
+    // It leverages the internal diffing mechanism.
+    // If you have a list or "nth" child, you do need to list those children, but you don't need to
+    // fill in their children/attrs/etc
+    // Does not handle children or lifecycles and will always fail the test if they show up in the rhs
+    pub fn compare<'a, F>(&self, other: LazyNodes<'a, F>) -> Result<()>
+    where
+        F: for<'b> FnOnce(&'b NodeCtx<'a>) -> VNode<'a> + 'a,
+    {
+        Ok(())
+    }
+
+    // Do a full compare - everything must match
+    // Ignores listeners and children components
+    pub fn compare_full<'a, F>(&self, other: LazyNodes<'a, F>) -> Result<()>
+    where
+        F: for<'b> FnOnce(&'b NodeCtx<'a>) -> VNode<'a> + 'a,
+    {
+        Ok(())
+    }
+
+    pub fn trigger_listener(&mut self, id: usize) -> Result<()> {
+        Ok(())
+    }
 }
 
 #[cfg(test)]

+ 43 - 30
packages/core/src/hooks.rs

@@ -5,6 +5,7 @@
 //! - [ ] use_reducer
 //! - [ ] use_effect
 
+pub use use_reducer_def::use_reducer;
 pub use use_ref_def::use_ref;
 pub use use_state_def::use_state;
 
@@ -12,29 +13,52 @@ mod use_state_def {
     use crate::innerlude::*;
     use std::{
         cell::RefCell,
+        fmt::Display,
         ops::{Deref, DerefMut},
         rc::Rc,
     };
 
-    struct UseState<T: 'static> {
-        new_val: Rc<RefCell<Option<T>>>,
+    pub struct UseState<T: 'static> {
+        modifier: Rc<RefCell<Option<Box<dyn FnOnce(&mut T)>>>>,
         current_val: T,
-        caller: Box<dyn Fn(T) + 'static>,
+        update: Box<dyn Fn() + 'static>,
     }
 
-    struct UseStateHandle<'a, T> {
-        inner: &'a T,
-    }
-    impl<'a, T> UseStateHandle<'a, T> {
-        fn set(&self, new_val: T) {}
-        fn modify(&self, f: impl FnOnce(&mut T)) {}
-    }
+    // #[derive(Clone, Copy)]
+    // pub struct UseStateHandle<'a, T: 'static> {
+    //     inner: &'a UseState<T>,
+    //     // pub setter: &'a dyn Fn(T),
+    //     // pub modifier: &'a dyn Fn(&mut T),
+    // }
+
+    impl<'a, T: 'static> UseState<T> {
+        pub fn setter(&self) -> &dyn Fn(T) {
+            todo!()
+        }
+        pub fn set(&self, new_val: T) {
+            self.modify(|f| *f = new_val);
+        }
 
-    impl<'a, T> Deref for UseStateHandle<'a, T> {
+        // signal that we need to be updated
+        // save the modifier
+        pub fn modify(&self, f: impl FnOnce(&mut T) + 'static) {
+            let mut slot = self.modifier.as_ref().borrow_mut();
+            *slot = Some(Box::new(f));
+            (self.update)();
+        }
+    }
+    impl<'a, T: 'static> std::ops::Deref for UseState<T> {
         type Target = T;
 
         fn deref(&self) -> &Self::Target {
-            self.inner
+            &self.current_val
+        }
+    }
+
+    // enable displaty for the handle
+    impl<'a, T: 'static + Display> std::fmt::Display for UseState<T> {
+        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+            write!(f, "{}", self.current_val)
         }
     }
 
@@ -63,37 +87,26 @@ mod use_state_def {
     pub fn use_state<'a, 'c, T: 'static, F: FnOnce() -> T>(
         ctx: &'c Context<'a>,
         initial_state_fn: F,
-    ) -> (&'a T, &'a impl Fn(T)) {
+    ) -> &'a UseState<T> {
         ctx.use_hook(
             move || UseState {
-                new_val: Rc::new(RefCell::new(None)),
+                modifier: Rc::new(RefCell::new(None)),
                 current_val: initial_state_fn(),
-                caller: Box::new(|_| println!("setter called!")),
+                update: Box::new(|| {}),
             },
             move |hook| {
-                log::debug!("Use_state set called");
-                let inner = hook.new_val.clone();
                 let scheduled_update = ctx.schedule_update();
 
                 // get ownership of the new val and replace the current with the new
                 // -> as_ref -> borrow_mut -> deref_mut -> take
                 // -> rc     -> &RefCell   -> RefMut    -> &Option<T> -> T
-                if let Some(new_val) = hook.new_val.as_ref().borrow_mut().deref_mut().take() {
-                    hook.current_val = new_val;
+                if let Some(new_val) = hook.modifier.as_ref().borrow_mut().deref_mut().take() {
+                    (new_val)(&mut hook.current_val);
                 }
 
-                // todo: swap out the caller with a subscription call and an internal update
-                hook.caller = Box::new(move |new_val| {
-                    // update the setter with the new value
-                    let mut new_inner = inner.as_ref().borrow_mut();
-                    *new_inner = Some(new_val);
+                hook.update = Box::new(move || scheduled_update());
 
-                    // Ensure the component gets updated
-                    scheduled_update();
-                });
-
-                // box gets derefed into a ref which is then taken as ref with the hook
-                (&hook.current_val, &hook.caller)
+                &*hook
             },
             |_| {},
         )

+ 12 - 1
packages/core/src/nodebuilder.rs

@@ -496,6 +496,17 @@ pub trait IntoDomTree<'a> {
     fn into_vnode(self, ctx: &NodeCtx<'a>) -> VNode<'a>;
 }
 
+pub trait DomTreeBuilder<'a, G>: IntoIterator<Item = G>
+where
+    G: IntoDomTree<'a>,
+{
+}
+
+impl<'a, F> DomTreeBuilder<'a, LazyNodes<'a, F>> for LazyNodes<'a, F> where
+    F: for<'b> FnOnce(&'b NodeCtx<'a>) -> VNode<'a> + 'a
+{
+}
+
 // Cover the cases where nodes are pre-rendered.
 // Likely used by enums.
 // ----
@@ -547,7 +558,7 @@ where
 // Required because anything that enters brackets in the rsx! macro needs to implement IntoIterator
 impl<'a, G> IntoIterator for LazyNodes<'a, G>
 where
-    G: for<'b, 'c> FnOnce(&'b NodeCtx<'c>) -> VNode<'c> + 'a,
+    G: for<'b> FnOnce(&'b NodeCtx<'a>) -> VNode<'a> + 'a,
 {
     type Item = Self;
     type IntoIter = std::iter::Once<Self::Item>;

+ 152 - 0
packages/docsite/src/components/app.rs

@@ -0,0 +1,152 @@
+/*
+This example shows how to encapsulate sate in dioxus components with the reducer pattern.
+This pattern is very useful when a single component can handle many types of input that can
+be represented by an enum. This particular pattern is very powerful in rust where ADTs can simplify
+much of the traditional reducer boilerplate.
+*/
+#![allow(unused)]
+use std::future::Future;
+
+use dioxus::hooks::use_reducer;
+use dioxus_ssr::prelude::*;
+
+enum Actions {
+    Pause,
+    Play,
+}
+
+struct SomeState {
+    is_playing: bool,
+}
+
+impl SomeState {
+    fn new() -> Self {
+        Self { is_playing: false }
+    }
+    fn reduce(&mut self, action: Actions) {
+        match action {
+            Actions::Pause => self.is_playing = false,
+            Actions::Play => self.is_playing = true,
+        }
+    }
+    fn is_playing(&self) -> &'static str {
+        match self.is_playing {
+            true => "currently playing!",
+            false => "not currently playing",
+        }
+    }
+}
+
+pub static ExampleReducer: FC<()> = |ctx, props| {
+    let (state, reduce) = use_reducer(&ctx, SomeState::new, SomeState::reduce);
+
+    let is_playing = state.is_playing();
+
+    ctx.render(rsx! {
+        div {
+            h1 {"Select an option"}
+            h3 {"The radio is... {is_playing}!"}
+            button {
+                "Pause"
+                onclick: move |_| reduce(Actions::Pause)
+            }
+            button {
+                "Play"
+                onclick: move |_| reduce(Actions::Play)
+            }
+        }
+    })
+};
+
+/*
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+*/
+
+struct AppContext {
+    name: String,
+}
+
+enum KindaState {
+    Ready,
+    Complete,
+    Erred,
+}
+
+static EnumReducer: FC<()> = |ctx, props| {
+    let (state, reduce) = use_reducer(&ctx, || KindaState::Ready, |cur, new| *cur = new);
+
+    let contents = helper(&ctx);
+
+    let status = match state {
+        KindaState::Ready => "Ready",
+        KindaState::Complete => "Complete",
+        KindaState::Erred => "Erred",
+    };
+
+    ctx.render(rsx! {
+        div {
+            h1 {"{status}"}
+            {contents}
+            button {
+                "Set Ready"
+                onclick: move |_| reduce(KindaState::Ready)
+            }
+            button {
+                "Set Complete"
+                onclick: move |_| reduce(KindaState::Complete)
+            }
+            button {
+                "Set Erred"
+                onclick: move |_| reduce(KindaState::Erred)
+            }
+            ul {
+                {(0..10).map(|f| {
+
+                    rsx!{
+                        li {
+                            "hello there!"
+                        }
+                    }
+                })}
+            }
+        }
+    })
+};
+
+fn helper(ctx: &Context) -> DomTree {
+    ctx.render(rsx! {
+        div {}
+    })
+}
+
+/// Demonstrate how the DebugRenderer can be used to unit test components without needing a browser
+/// These tests can run locally.
+/// They use the "compare" method of the debug renderer to do partial tree compares for succint
+#[test]
+fn ensure_it_works_properly() -> dioxus::error::Result<()> {
+    let mut test = DebugRenderer::new(EnumReducer);
+    test.compare(rsx! { div { h1 {"Ready"} } })?;
+
+    test.trigger_listener(1)?;
+    test.compare(rsx! { div { h1 {"Ready"} } })?;
+
+    test.trigger_listener(2)?;
+    test.compare(rsx! { div { h1 {"Complete"} } })?;
+
+    test.trigger_listener(3)?;
+    test.compare(rsx! { div { h1 {"Erred"} } })?;
+    Ok(())
+}

+ 73 - 19
packages/docsite/src/main.rs

@@ -1,43 +1,52 @@
-use dioxus_ssr::{prelude::*, TextRenderer};
+use dioxus_ssr::{
+    prelude::{builder::IntoDomTree, dioxus::events::on::MouseEvent, *},
+    TextRenderer,
+};
+mod components {
+    mod app;
+}
 
 fn main() {
     TextRenderer::new(App);
 }
 
 #[derive(Debug, PartialEq)]
-enum Routes {
+enum Route {
     Homepage,
-    ExampleList,
+    Examples,
 }
 
 #[derive(Debug, PartialEq, Props)]
 struct AppProps {
-    route: Routes,
+    route: Route,
 }
 
-trait Blah {}
-impl<'a, G> Blah for LazyNodes<'a, G> where G: for<'b> FnOnce(&'b NodeCtx<'a>) -> VNode<'a> + 'a {}
-
 static App: FC<AppProps> = |ctx, props| {
-    //
-    let body = rsx! {
-        div {}
-    };
+    let body = match props.route {
+        Route::Homepage => ctx.render(rsx! {
+            div {
+                "Some Content"
+            }
+        }),
 
-    let top = rsx! {
-        div {}
+        Route::Examples => ctx.render(rsx! {
+            div {
+                "Other Content"
+            }
+        }),
     };
 
     ctx.render(rsx!(
         div {
             Header {}
             {body}
-            {top}
-            {rsx!{
-                div {
-                    "you ugl"
-                }
-            }}
+            ul {
+                {(0..10).map(|f| rsx!{
+                    li {
+                        "this is list item {f}"
+                    }
+                })}
+            }
         }
     ))
 };
@@ -49,3 +58,48 @@ static Header: FC<()> = |ctx, _| {
         }
     })
 };
+
+mod example {
+    use super::*;
+    static ExampleUsage: FC<()> = |ctx, props| {
+        // direct rsx!
+        let body = rsx! {
+            div {}
+        };
+
+        // rendered rsx!
+        let top = ctx.render(rsx! {
+            div {
+                "ack!"
+            }
+        });
+
+        // listy rsx
+        let list2 = (0..10).map(|f| {
+            rsx! {
+                div {}
+            }
+        });
+
+        // rendered list rsx
+        let list = (0..10).map(|f| {
+            ctx.render(rsx! {
+                div {}
+            })
+        });
+
+        ctx.render(rsx!(
+            div {
+                Header {}
+                {body}
+                {top}
+                {list}
+                {list2}
+                // inline rsx
+                {rsx!{
+                    div { "hello" }
+                }}
+            }
+        ))
+    };
+}

+ 5 - 5
packages/web/examples/deep.rs

@@ -10,27 +10,27 @@ fn main() {
 use components::CustomB;
 
 fn CustomA<'a>(ctx: Context<'a>, props: &'a ()) -> DomTree {
-    let (val, set_val) = use_state(&ctx, || "abcdef".to_string());
+    let val = use_state(&ctx, || "abcdef".to_string());
+
     ctx.render(rsx! {
         div {
             class: "m-8"
             "CustomA {val}"
             button {
                 "Upper"
-                onclick: move |_| set_val(val.to_ascii_uppercase())
+                onclick: move |_| val.set(val.to_ascii_uppercase())
             }
             button {
                 "Lower"
-                onclick: move |_| set_val(val.to_ascii_lowercase())
+                onclick: move |_| val.set(val.to_ascii_lowercase())
             }
             CustomB {
-                val: val
+                val: val.as_ref()
             }
         }
     })
 }
 
-
 mod components {
     use super::*;
 

+ 4 - 5
packages/web/examples/infer.rs

@@ -12,11 +12,10 @@ fn main() {
 
 // this is a component
 static Example: FC<()> = |ctx, _props| {
-    let (event, set_event) = use_state(&ctx, || None);
-    let event = format!("{:#?}", event);
+    let event = use_state(&ctx, || None);
 
     let handler = move |evt: MouseEvent| {
-        set_event(Some(evt));
+        event.set(Some(evt));
     };
 
     log::info!("hello world");
@@ -36,9 +35,9 @@ static Example: FC<()> = |ctx, _props| {
             pre {
                 onmousemove: {handler}
                 id: "json"
-                "{event}"
+                "Hello world"
             }
-            Example2 { name: event }
+            Example2 { name: "Blah".into() }
         }
     })
 };

+ 4 - 19
packages/web/examples/jackjill.rs

@@ -10,19 +10,11 @@ fn main() {
 }
 
 static Example: FC<()> = |ctx, props| {
-    let (name, set_name) = use_state(&ctx, || "...?");
+    let name = use_state(&ctx, || "...?");
 
     log::debug!("Running component....");
 
     ctx.render(html! {
-    // <div>
-    //     <h1> "Hello, {name}" </h1>
-    //     <button onclick={move |_| set_name("jack")}> "jack!" </button>
-    //     <button
-    //         onclick={move |_| set_name("jill")}
-    //         onclick={move |_| set_name("jill")}
-    //     > "jill!" </button>
-    // </div>
             <div>
                 <section class="py-12 px-4 text-center">
                     <div class="w-full max-w-2xl mx-auto">
@@ -40,15 +32,14 @@ static Example: FC<()> = |ctx, props| {
                         <div>
                             <button
                                 class="inline-block py-4 px-8 mr-6 leading-none text-white bg-indigo-600 hover:bg-indigo-900 font-semibold rounded shadow"
-                                // onclick={move |_| log::debug!("set jack")}>
-                                onclick={move |_| set_name("jack")}>
+                                onclick={move |_| name.set("jack")}>
                                 "Jack!"
                                 </button>
 
                                 <button
                                     class="inline-block py-4 px-8 mr-6 leading-none text-white bg-indigo-600 hover:bg-indigo-900 font-semibold rounded shadow"
-                                    onclick={move |_| set_name("jill")}
-                                    onclick={move |_| set_name("jill")}>
+                                    onclick={move |_| name.set("jill")}
+                                    onclick={move |_| name.set("jill")}>
                                     "Jill!"
                                 </button>
                         </div>
@@ -57,9 +48,3 @@ static Example: FC<()> = |ctx, props| {
             </div>
         })
 };
-
-// <div>
-//     <h1> "Hello, {name}" </h1>
-//     <button onclick={move |_| set_name("jack   .")}> "jack!" </button>
-//     <button onclick={move |_| set_name("jill   .")}> "jill!" </button>
-// </div>

+ 97 - 0
packages/web/examples/list.rs

@@ -0,0 +1,97 @@
+use std::collections::HashMap;
+
+use dioxus::prelude::*;
+use dioxus_core as dioxus;
+use dioxus_web::WebsysRenderer;
+
+fn main() {
+    wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
+    console_error_panic_hook::set_once();
+    wasm_bindgen_futures::spawn_local(async move {
+        WebsysRenderer::new_with_props(App, ())
+            .run()
+            .await
+            .expect("major crash");
+    });
+}
+
+use lazy_static::lazy_static;
+lazy_static! {
+    static ref DummyData: HashMap<String, String> = {
+        let vals = vec![
+            ("0 ", "abc123"),
+            ("1 ", "abc124"),
+            ("2 ", "abc125"),
+            ("3 ", "abc126"),
+            ("4 ", "abc127"),
+            ("5 ", "abc128"),
+            ("6 ", "abc129"),
+            ("7 ", "abc1210"),
+            ("8 ", "abc1211"),
+            ("9 ", "abc1212"),
+            ("10 ", "abc1213"),
+            ("11 ", "abc1214"),
+            ("12 ", "abc1215"),
+            ("13 ", "abc1216"),
+            ("14 ", "abc1217"),
+            ("15 ", "abc1218"),
+            ("16 ", "abc1219"),
+            ("17 ", "abc1220"),
+            ("18 ", "abc1221"),
+            ("19 ", "abc1222"),
+        ];
+        vals.into_iter()
+            .map(|(a, b)| (a.to_string(), b.to_string()))
+            .collect()
+    };
+}
+
+static App: FC<()> = |ctx, _| {
+    let items = use_state(&ctx, || DummyData.clone());
+
+    // handle new elements
+    let add_new = move |_| {
+        items.modify(|m| {
+            let k = m.len();
+            let v = match (k % 3, k % 5) {
+                (0, 0) => "FizzBuzz".to_string(),
+                (0, _) => "Fizz".to_string(),
+                (_, 0) => "Buzz".to_string(),
+                _ => k.to_string(),
+            };
+            m.insert(k.to_string(), v);
+        })
+    };
+
+    let elements = items.iter().map(|(k, v)| {
+        rsx! {
+            li {
+                span {"{k}: {v}"}
+                button {
+                    "Remove"
+                    onclick: move |_| {
+                        let key_to_remove = k.clone();
+                        items.modify(move |m| { m.remove(&key_to_remove); } )
+                    }
+                }
+            }
+        }
+    });
+
+    ctx.render(rsx!(
+        div {
+            h1 {"Some list"}
+
+            // button  to add new item
+            button {
+                "add new"
+                onclick: {add_new}
+            }
+
+            // list elements
+            ul {
+                {elements}
+            }
+        }
+    ))
+};

+ 4 - 4
packages/web/examples/rsxt.rs

@@ -25,7 +25,7 @@ struct ExampleProps {
 }
 
 static Example: FC<ExampleProps> = |ctx, props| {
-    let (name, set_name) = use_state(&ctx, move || props.initial_name.to_string());
+    let name = use_state(&ctx, move || props.initial_name.to_string());
 
     ctx.render(rsx! {
         div { 
@@ -39,9 +39,9 @@ static Example: FC<ExampleProps> = |ctx, props| {
                 "Hello, {name}"
             }
             
-            CustomButton { name: "Jack!", set_name: set_name }
-            CustomButton { name: "Jill!", set_name: set_name }
-            CustomButton { name: "Bob!", set_name: set_name }
+            CustomButton { name: "Jack!", set_name: name.setter() }
+            CustomButton { name: "Jill!", set_name: name.setter() }
+            CustomButton { name: "Bob!", set_name: name.setter() }
         }
     })
 };