diff --git a/README.md b/README.md index 6b4a3b27b..e90523c8a 100644 --- a/README.md +++ b/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 🎉")} } }) }; diff --git a/packages/core-macro/src/rsxt.rs b/packages/core-macro/src/rsxt.rs index f8ee16aca..0cb16f5f8 100644 --- a/packages/core-macro/src/rsxt.rs +++ b/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 { - let fork1 = stream.fork(); - let fork2 = stream.fork(); - let fork3 = stream.fork(); + // Supposedly this approach is discouraged due to inability to return proper errors + // TODO: Rework this to provide more informative errors - // 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::() { - stream.advance_to(&fork1); - Self::Text(text) - } else if let Ok(element) = fork2.parse::() { - stream.advance_to(&fork2); - Self::Element(element) - } else if let Ok(comp) = fork3.parse::() { - stream.advance_to(&fork3); - Self::Component(comp) - } else { - return Err(Error::new( - stream.span(), - "Failed to parse as a valid child", - )); - }; - - // consume comma if it exists - // we don't actually care if there *are* commas after elements/text - if stream.peek(Token![,]) { - let _ = stream.parse::(); + let fork = stream.fork(); + if let Ok(text) = fork.parse::() { + stream.advance_to(&fork); + return Ok(Self::Text(text)); } - Ok(ret) + + let fork = stream.fork(); + if let Ok(element) = fork.parse::() { + stream.advance_to(&fork); + return Ok(Self::Element(element)); + } + + let fork = stream.fork(); + if let Ok(comp) = fork.parse::() { + 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, - // attrs: Vec, children: Vec, } @@ -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 { - // 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::() { 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::(); + } } // todo: add support for children let children: Vec = 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 { let name = Ident::parse_any(input)?; - let _name_str = name.to_string(); input.parse::()?; 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::(); - } - 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, children: Vec, - // children: MaybeExpr>, } 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 = 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, ) -> 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 - // [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::(); + } // [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 { /// ======================================= /// 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) } // ======================================= // Parse just plain text // ======================================= -// - [ ] Perform formatting automatically -// -// struct TextNode(LitStr); -// struct TextNode(MaybeExpr); impl Parse for TextNode { fn parse(s: ParseStream) -> Result { @@ -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 { -// Literal(T), -// Expr(Expr), -// } - -// impl Parse for MaybeExpr { -// fn parse(s: ParseStream) -> Result { -// 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 { -// fn to_tokens(&self, tokens: &mut TokenStream2) { -// match &self { -// MaybeExpr::Literal(v) => v.to_tokens(tokens), -// MaybeExpr::Expr(expr) => expr.to_tokens(tokens), -// } -// } -// } diff --git a/packages/core/examples/borrowed.rs b/packages/core/examples/borrowed.rs index e076a23cf..d1fd7728d 100644 --- a/packages/core/examples/borrowed.rs +++ b/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 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 = dyn Fn(T); -// struct StateSetter(dyn Fn(T)); - -// impl PartialEq for StateSetter { -// 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, + 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 - {}, - } -} - -// 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::() - .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} - }, - } - }) -} - -*/ diff --git a/packages/core/examples/listener.rs b/packages/core/examples/listener.rs index fe87513a7..93f7416ec 100644 --- a/packages/core/examples/listener.rs +++ b/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! {

"Hello, {name}"

- - + +
}) }; diff --git a/packages/core/src/context.rs b/packages/core/src/context.rs index 3b89a4855..3a1d2d013 100644 --- a/packages/core/src/context.rs +++ b/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 FnOnce(&'b NodeCtx<'a>) -> VNode<'a> + 'a>( &self, - _fut: impl Future) -> VNode<'a>>, + _fut: impl Future>, ) -> VNode<'a> { todo!() } diff --git a/packages/core/src/debug_renderer.rs b/packages/core/src/debug_renderer.rs index 1d177ccdd..a398897d3 100644 --- a/packages/core/src/debug_renderer.rs +++ b/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)] diff --git a/packages/core/src/hooks.rs b/packages/core/src/hooks.rs index 7936c6a88..bc0f470f6 100644 --- a/packages/core/src/hooks.rs +++ b/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 { - new_val: Rc>>, + pub struct UseState { + modifier: Rc>>>, current_val: T, - caller: Box, + update: Box, } - 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, + // // pub setter: &'a dyn Fn(T), + // // pub modifier: &'a dyn Fn(&mut T), + // } - impl<'a, T> Deref for UseStateHandle<'a, T> { + impl<'a, T: 'static> UseState { + pub fn setter(&self) -> &dyn Fn(T) { + todo!() + } + pub fn set(&self, new_val: T) { + self.modify(|f| *f = new_val); + } + + // 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 { 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 { + 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 { 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 - 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 }, |_| {}, ) diff --git a/packages/core/src/nodebuilder.rs b/packages/core/src/nodebuilder.rs index d8db0f7fa..b938e4fe2 100644 --- a/packages/core/src/nodebuilder.rs +++ b/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 +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; diff --git a/packages/docsite/src/components/app.rs b/packages/docsite/src/components/app.rs index e69de29bb..0ba4ad556 100644 --- a/packages/docsite/src/components/app.rs +++ b/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(()) +} diff --git a/packages/docsite/src/main.rs b/packages/docsite/src/main.rs index 350bd5e2c..d80c00b24 100644 --- a/packages/docsite/src/main.rs +++ b/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 = |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" } + }} + } + )) + }; +} diff --git a/packages/web/examples/deep.rs b/packages/web/examples/deep.rs index 83d2c7084..b232cd30c 100644 --- a/packages/web/examples/deep.rs +++ b/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::*; diff --git a/packages/web/examples/infer.rs b/packages/web/examples/infer.rs index 06140f6ef..1b2a5be16 100644 --- a/packages/web/examples/infer.rs +++ b/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() } } }) }; diff --git a/packages/web/examples/jackjill.rs b/packages/web/examples/jackjill.rs index d9edba0fb..91b2da2ef 100644 --- a/packages/web/examples/jackjill.rs +++ b/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! { - //
- //

"Hello, {name}"

- // - // - //
@@ -40,15 +32,14 @@ static Example: FC<()> = |ctx, props| {
@@ -57,9 +48,3 @@ static Example: FC<()> = |ctx, props| {
}) }; - -//
-//

"Hello, {name}"

-// -// -//
diff --git a/packages/web/examples/list.rs b/packages/web/examples/list.rs new file mode 100644 index 000000000..1b36d362d --- /dev/null +++ b/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 = { + 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} + } + } + )) +}; diff --git a/packages/web/examples/rsxt.rs b/packages/web/examples/rsxt.rs index 8fae96fb3..2996c50c8 100644 --- a/packages/web/examples/rsxt.rs +++ b/packages/web/examples/rsxt.rs @@ -25,7 +25,7 @@ struct ExampleProps { } static Example: FC = |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 = |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() } } }) };