mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-10 14:44:12 +00:00
Feat: more ergonomics, more examples
This commit is contained in:
parent
44aad2746c
commit
0bcff1f88e
15 changed files with 488 additions and 469 deletions
|
@ -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 🎉")}
|
||||
}
|
||||
})
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
// 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::<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",
|
||||
));
|
||||
};
|
||||
|
||||
// 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),
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
|
|
@ -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}
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
*/
|
||||
|
|
|
@ -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>
|
||||
})
|
||||
};
|
||||
|
|
|
@ -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!()
|
||||
}
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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> Deref for UseStateHandle<'a, 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);
|
||||
}
|
||||
|
||||
// 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
|
||||
},
|
||||
|_| {},
|
||||
)
|
||||
|
|
|
@ -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>;
|
||||
|
|
|
@ -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(())
|
||||
}
|
|
@ -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" }
|
||||
}}
|
||||
}
|
||||
))
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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::*;
|
||||
|
||||
|
|
|
@ -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() }
|
||||
}
|
||||
})
|
||||
};
|
||||
|
|
|
@ -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
packages/web/examples/list.rs
Normal file
97
packages/web/examples/list.rs
Normal file
|
@ -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}
|
||||
}
|
||||
}
|
||||
))
|
||||
};
|
|
@ -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() }
|
||||
}
|
||||
})
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue