From cafb7df736e9366c2acd99b5571cd4b7894ea595 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sun, 2 Jan 2022 18:35:38 -0500 Subject: [PATCH] docs: remove all usages of static closure syntax and update readme --- README.md | 28 +- .../src/elements/conditional_rendering.md | 5 - docs/guide/src/hello_world.md | 6 - examples/README.md | 2 +- examples/async.rs | 41 +- examples/borrowed.rs | 4 +- examples/calculator.rs | 2 +- examples/core/alternative.rs | 33 -- examples/core/html.rs | 108 ----- examples/core/jsframework.rs | 118 ------ examples/core_reference/antipatterns.rs | 40 +- examples/core_reference/basics.rs | 19 +- examples/core_reference/children.rs | 17 +- .../core_reference/conditional_rendering.rs | 40 +- examples/core_reference/controlled_inputs.rs | 12 +- examples/core_reference/custom_elements.rs | 4 +- examples/core_reference/empty.rs | 4 +- examples/core_reference/errorhandling.rs | 36 +- examples/core_reference/fragments.rs | 28 +- examples/core_reference/global_css.rs | 4 +- examples/core_reference/inline_styles.rs | 8 +- examples/core_reference/iterators.rs | 27 +- examples/core_reference/listener.rs | 43 +- examples/core_reference/memo.rs | 14 +- examples/core_reference/noderefs.rs | 12 - examples/core_reference/signals.rs | 10 - examples/core_reference/spreadpattern.rs | 16 +- examples/core_reference/statemanagement.rs | 10 - examples/core_reference/suspense.rs | 4 +- examples/core_reference/task.rs | 6 +- examples/core_reference/testing.rs | 9 - examples/core_reference/tostring.rs | 25 -- examples/coroutine.rs | 6 +- examples/crm.rs | 3 +- examples/desktop/core.rs | 5 - examples/desktop/crm.rs | 109 ----- examples/desktop/demo.rs | 18 - examples/desktop/todomvc.css | 379 ------------------ examples/desktop/todomvc.rs | 170 -------- examples/framework_benchmark.rs | 24 +- examples/hydration.rs | 9 +- examples/inputs.rs | 6 +- examples/manual_edits.rs | 2 +- examples/pattern_model.rs | 51 +-- examples/pattern_reducer.rs | 6 +- examples/readme.rs | 6 +- examples/router.rs | 4 +- examples/rsx_compile_fail.rs | 3 + examples/rsx_usage.rs | 14 +- examples/ssr.rs | 6 +- examples/ssr/basic.rs | 9 +- examples/ssr/hydration.rs | 10 - examples/ssr/template.html | 298 -------------- examples/ssr/tide.rs | 61 --- examples/ssr/tofile.rs | 26 +- examples/tailwind.rs | 24 +- examples/tasks.rs | 13 +- examples/todomvc.rs | 94 ++--- examples/weather_app.rs | 26 +- examples/web_tick.rs | 6 +- examples/xss_safety.rs | 18 +- packages/core-macro/src/inlineprops.rs | 2 +- packages/hooks/src/usestate.rs | 277 ------------- packages/hooks/src/usestate/handle.rs | 211 ++++++++++ packages/hooks/src/usestate/mod.rs | 78 ++++ packages/hooks/src/usestate/owned.rs | 102 +++++ 66 files changed, 792 insertions(+), 2019 deletions(-) delete mode 100644 examples/core/alternative.rs delete mode 100644 examples/core/html.rs delete mode 100644 examples/core/jsframework.rs delete mode 100644 examples/core_reference/noderefs.rs delete mode 100644 examples/core_reference/signals.rs delete mode 100644 examples/core_reference/statemanagement.rs delete mode 100644 examples/core_reference/testing.rs delete mode 100644 examples/core_reference/tostring.rs delete mode 100644 examples/desktop/core.rs delete mode 100644 examples/desktop/crm.rs delete mode 100644 examples/desktop/demo.rs delete mode 100644 examples/desktop/todomvc.css delete mode 100644 examples/desktop/todomvc.rs delete mode 100644 examples/ssr/hydration.rs delete mode 100644 examples/ssr/template.html delete mode 100644 examples/ssr/tide.rs delete mode 100644 packages/hooks/src/usestate.rs create mode 100644 packages/hooks/src/usestate/handle.rs create mode 100644 packages/hooks/src/usestate/mod.rs create mode 100644 packages/hooks/src/usestate/owned.rs diff --git a/README.md b/README.md index 7c171a686..49d2c5d39 100644 --- a/README.md +++ b/README.md @@ -22,19 +22,24 @@ alt="docs.rs docs" /> - + + + + Awesome Page

- Guide - | Website | - Examples + Guide + | + Examples

@@ -66,6 +71,15 @@ If you know React, then you already know Dioxus. - Multi-channel asynchronous scheduler for first-class async support. - And more! Read the [full release post here](https://dioxuslabs.com/). + +### Examples + +All examples in this repo are desktop apps. To run an example, simply clone this repo and use cargo with the `desktop` feature enabled. For SSR examples, you might need to enable SSR instead. + +``` +cargo run --features desktop --example EXAMPLE +``` + ## Get Started with... @@ -80,8 +94,7 @@ If you know React, then you already know Dioxus.
- -## Examples Projects: +## Example Projects: | File Navigator (Desktop) | WiFi scanner (Desktop) | TodoMVC (All platforms) | E-commerce w/ Tailwind (Liveview) | | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -90,9 +103,6 @@ If you know React, then you already know Dioxus. See the awesome-dioxus page for a curated list of content in the Dioxus Ecosystem. -## Running examples locally - -All local examples are built for the desktop renderer. This means you can simply clone this repo and call `cargo run --example EXAMPLE_NAME`. To run non-desktop examples, checkout the example projects shown above. ## Why Dioxus and why Rust? diff --git a/docs/guide/src/elements/conditional_rendering.md b/docs/guide/src/elements/conditional_rendering.md index b23df6000..c23e62e47 100644 --- a/docs/guide/src/elements/conditional_rendering.md +++ b/docs/guide/src/elements/conditional_rendering.md @@ -81,11 +81,6 @@ fn App(cx: Scope)-> Element { } ``` -This syntax even enables us to write a one-line component: -```rust -static App: Component = |cx| rsx!(cx, "hello world!"); -``` - Alternatively, for match statements, we can just return the builder itself and pass it into a final, single call to `cx.render`: ```rust diff --git a/docs/guide/src/hello_world.md b/docs/guide/src/hello_world.md index fbe6153f5..e9eb92081 100644 --- a/docs/guide/src/hello_world.md +++ b/docs/guide/src/hello_world.md @@ -132,12 +132,6 @@ fn App(cx: Scope) -> Element { } ``` -Writing `fn App(cx: Scope) -> Element {` might become tedious. Rust will also let you write functions as static closures, but these types of Components cannot have props that borrow data. - -```rust -static App: Component = |cx| cx.render(rsx!(div { "Hello, world!" })); -``` - ### What is this `Scope` object? Coming from React, the `Scope` object might be confusing. In React, you'll want to store data between renders with hooks. However, hooks rely on global variables which make them difficult to integrate in multi-tenant systems like server-rendering. diff --git a/examples/README.md b/examples/README.md index 2e4959e7f..e108c56cd 100644 --- a/examples/README.md +++ b/examples/README.md @@ -115,7 +115,7 @@ fn App(cx: Scope) -> Element { Tiny components: ```rust -static App: Component = |cx, _| rsx!(cx, div {"hello world!"}); +static App: Component = |cx| rsx!(cx, div {"hello world!"}); ``` Borrowed prop contents: diff --git a/examples/async.rs b/examples/async.rs index 4d292779e..dcd3b2757 100644 --- a/examples/async.rs +++ b/examples/async.rs @@ -17,27 +17,32 @@ fn app(cx: Scope) -> Element { let (async_count, dir) = (count.for_async(), *direction); - let task = use_coroutine(&cx, move || async move { - loop { - TimeoutFuture::new(250).await; - *async_count.modify() += dir; + let task = use_coroutine(&cx, move || { + // + async move { + loop { + TimeoutFuture::new(250).await; + // *async_count.modify() += dir; + } } }); - rsx!(cx, div { - h1 {"count is {count}"} - button { onclick: move |_| task.stop(), - "Stop counting" - } - button { onclick: move |_| task.resume(), - "Start counting" - } - button { - onclick: move |_| { - *direction.modify() *= -1; - task.restart(); - }, - "Switch counting direcion" + cx.render(rsx! { + div { + h1 {"count is {count}"} + button { onclick: move |_| task.stop(), + "Stop counting" + } + button { onclick: move |_| task.resume(), + "Start counting" + } + button { + onclick: move |_| { + *direction.modify() *= -1; + task.restart(); + }, + "Switch counting direcion" + } } }) } diff --git a/examples/borrowed.rs b/examples/borrowed.rs index 82920029b..63191bc97 100644 --- a/examples/borrowed.rs +++ b/examples/borrowed.rs @@ -17,10 +17,10 @@ and is proven to be safe with MIRI. use dioxus::prelude::*; fn main() { - dioxus::desktop::launch(App); + dioxus::desktop::launch(app); } -fn App(cx: Scope) -> Element { +fn app(cx: Scope) -> Element { let text = cx.use_hook(|_| vec![String::from("abc=def")]); let first = text.get_mut(0).unwrap(); diff --git a/examples/calculator.rs b/examples/calculator.rs index c09f1b2aa..e43323a21 100644 --- a/examples/calculator.rs +++ b/examples/calculator.rs @@ -12,7 +12,7 @@ use dioxus::prelude::*; use separator::Separatable; fn main() { - // dioxus::desktop::launch(app); + dioxus::desktop::launch(app); } fn app(cx: Scope) -> Element { diff --git a/examples/core/alternative.rs b/examples/core/alternative.rs deleted file mode 100644 index 219c603ee..000000000 --- a/examples/core/alternative.rs +++ /dev/null @@ -1,33 +0,0 @@ -use dioxus_core::prelude::*; -use dioxus_core_macro::format_args_f; -use dioxus_core_macro::rsx; -use dioxus_html as dioxus_elements; - -fn main() { - let mut dom = VirtualDom::new(EXAMPLE); - dom.rebuild(); - println!("{}", dom); -} - -pub static EXAMPLE: Component = |cx| { - let list = (0..10).map(|_f| { - rsx! { - "{_f}" - } - }); - - cx.render(Some(LazyNodes::new(move |cx| { - cx.raw_element( - "div", - None, - [], - [], - [ - cx.text(format_args!("hello")), - cx.text(format_args!("hello")), - cx.fragment_from_iter(list), - ], - None, - ) - }))) -}; diff --git a/examples/core/html.rs b/examples/core/html.rs deleted file mode 100644 index 96ce94986..000000000 --- a/examples/core/html.rs +++ /dev/null @@ -1,108 +0,0 @@ -use bumpalo::Bump; - -fn main() {} - -fn build(factory: Factory) { - factory.text(); - div::new(factory) - .r#class() - .r#tag() - .r#type() - .add_children() - .iter_children() - .finish(); -} - -/// # The `div` element -/// -/// -/// The
HTML element is the generic container for flow content. It has no effect on the content or layout until -/// styled in some way using CSS (e.g. styling is directly applied to it, or some kind of layout model like Flexbox is -/// applied to its parent element). -/// -/// As a "pure" container, the
element does not inherently represent anything. Instead, it's used to group content -/// so it can be easily styled using the class or id attributes, marking a section of a document as being written in a -/// different language (using the lang attribute), and so on. -/// -/// ## Usage -/// ``` -/// rsx!{ -/// div { class: "tall", id: "unique id" -/// h1 {} -/// p {} -/// } -/// } -/// -/// ``` -/// -/// ## Specifications -/// - Content categories: Flow content, palpable content. -/// - Permitted content: Flow content. -/// - Permitted parents: Any element that accepts flow content. -#[allow(non_camel_case_types)] -struct div {} - -struct h1 {} - -struct h2 {} - -trait BasicElement: Sized { - const TagName: &'static str; - fn get_bump(&self) -> &Bump; - fn new(factory: Factory) -> Self; - fn add_children(self) -> Self { - self - } - fn iter_children(self) -> Self { - self - } - fn finish(self) -> Self { - self - } -} - -impl BasicElement for div { - const TagName: &'static str = "div"; - - fn get_bump(&self) -> &Bump { - todo!() - } - - fn new(_factory: Factory) -> Self { - todo!() - } -} - -impl div { - fn class(self) -> Self { - self - } - fn tag(self) -> Self { - self - } - fn r#type(self) -> Self { - self - } -} - -#[derive(Clone, Copy)] -struct Factory<'a> { - bump: &'a bumpalo::Bump, -} - -impl<'a> Factory<'a> { - fn text(&self) -> &str { - todo!() - } - fn new_el(&'a self, tag: &'static str) -> ElementBuilder<'a> { - ElementBuilder { - bump: self.bump, - tag, - } - } -} - -struct ElementBuilder<'a> { - tag: &'static str, - bump: &'a bumpalo::Bump, -} diff --git a/examples/core/jsframework.rs b/examples/core/jsframework.rs deleted file mode 100644 index f9fa9c431..000000000 --- a/examples/core/jsframework.rs +++ /dev/null @@ -1,118 +0,0 @@ -#![allow(non_snake_case)] - -use dioxus::component::Scope; -use dioxus::events::on::MouseEvent; -use dioxus_core as dioxus; -use dioxus_core::prelude::*; -use dioxus_core_macro::*; -use dioxus_html as dioxus_elements; -use rand::prelude::*; -use std::fmt::Display; - -fn main() { - let mut dom = VirtualDom::new(App); - let g = dom.rebuild(); - assert!(g.edits.len() > 1); -} - -fn App((cx, props): Scope) -> Element { - let mut rng = SmallRng::from_entropy(); - let rows = (0..10_000_usize).map(|f| { - let label = Label::new(&mut rng); - rsx! { - Row { - row_id: f, - label: label - } - } - }); - cx.render(rsx! { - table { - tbody { - {rows} - } - } - }) -} - -#[derive(PartialEq, Props)] -struct RowProps { - row_id: usize, - label: Label, -} - -fn Row<'a>((cx, props): Scope<'a, RowProps>) -> Element<'a> { - let handler = move |evt: MouseEvent| { - let g = evt.button; - }; - cx.render(rsx! { - tr { - // td { class:"col-md-1", "{cx.props.row_id}" } - // td { class:"col-md-1", onclick: move |_| { /* run onselect */ } - // a { class: "lbl", "{cx.props.label}" } - // } - // td { class: "col-md-1" - // a { class: "remove", onclick: {handler} - // span { class: "glyphicon glyphicon-remove remove" aria_hidden: "true" } - // } - // } - // td { class: "col-md-6" } - } - }) -} - -#[derive(PartialEq)] -struct Label([&'static str; 3]); - -impl Label { - fn new(rng: &mut SmallRng) -> Self { - Label([ - ADJECTIVES.choose(rng).unwrap(), - COLOURS.choose(rng).unwrap(), - NOUNS.choose(rng).unwrap(), - ]) - } -} -impl Display for Label { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{} {} {}", self.0[0], self.0[1], self.0[2]) - } -} - -static ADJECTIVES: &[&str] = &[ - "pretty", - "large", - "big", - "small", - "tall", - "short", - "long", - "handsome", - "plain", - "quaint", - "clean", - "elegant", - "easy", - "angry", - "crazy", - "helpful", - "mushy", - "odd", - "unsightly", - "adorable", - "important", - "inexpensive", - "cheap", - "expensive", - "fancy", -]; - -static COLOURS: &[&str] = &[ - "red", "yellow", "blue", "green", "pink", "brown", "purple", "brown", "white", "black", - "orange", -]; - -static NOUNS: &[&str] = &[ - "table", "chair", "house", "bbq", "desk", "car", "pony", "cookie", "sandwich", "burger", - "pizza", "mouse", "keyboard", -]; diff --git a/examples/core_reference/antipatterns.rs b/examples/core_reference/antipatterns.rs index c031428bd..5814e7096 100644 --- a/examples/core_reference/antipatterns.rs +++ b/examples/core_reference/antipatterns.rs @@ -32,16 +32,20 @@ use dioxus::prelude::*; struct NoKeysProps { data: std::collections::HashMap, } -static AntipatternNoKeys: Component = |cx| { +fn AntipatternNoKeys(cx: Scope) -> Element { // WRONG: Make sure to add keys! - rsx!(cx, ul { - {cx.props.data.iter().map(|(k, v)| rsx!(li { "List item: {v}" }))} + cx.render(rsx! { + ul { + cx.props.data.iter().map(|(k, v)| rsx!(li { "List item: {v}" })) + } }); // RIGHT: Like this: - rsx!(cx, ul { - {cx.props.data.iter().map(|(k, v)| rsx!(li { key: "{k}", "List item: {v}" }))} + cx.render(rsx! { + ul { + cx.props.data.iter().map(|(k, v)| rsx!(li { key: "{k}", "List item: {v}" })) + } }) -}; +} /// Antipattern: Deeply nested fragments /// ------------------------------------ @@ -54,9 +58,9 @@ static AntipatternNoKeys: Component = |cx| { /// /// Only Component and Fragment nodes are susceptible to this issue. Dioxus mitigates this with components by providing /// an API for registering shared state without the ContextProvider pattern. -static AntipatternNestedFragments: Component = |cx| { +fn AntipatternNestedFragments(cx: Scope<()>) -> Element { // Try to avoid heavily nesting fragments - rsx!(cx, + cx.render(rsx!( Fragment { Fragment { Fragment { @@ -68,8 +72,8 @@ static AntipatternNestedFragments: Component = |cx| { } } } - ) -}; + )) +} /// Antipattern: Using state after it's been updated /// ----------------------------------------------- @@ -82,13 +86,13 @@ static AntipatternNestedFragments: Component = |cx| { /// However, calling set_state will *not* update the current version of state in the component. This should be easy to /// recognize from the function signature, but Dioxus will not update the "live" version of state. Calling `set_state` /// merely places a new value in the queue and schedules the component for a future update. -static AntipatternRelyingOnSetState: Component = |cx| { +fn AntipatternRelyingOnSetState(cx: Scope) -> Element { let (state, set_state) = use_state(&cx, || "Hello world").classic(); set_state("New state"); // This will return false! `state` will *still* be "Hello world" assert!(state == &"New state"); todo!() -}; +} /// Antipattern: Capitalization /// --------------------------- @@ -120,16 +124,16 @@ static antipattern_component: Component = |cx| todo!(); struct MisuedHooksProps { should_render_state: bool, } -static AntipatternMisusedHooks: Component = |cx| { +fn AntipatternMisusedHooks(cx: Scope) -> Element { if props.should_render_state { // do not place a hook in the conditional! // prefer to move it out of the conditional let (state, set_state) = use_state(&cx, || "hello world").classic(); - rsx!(cx, div { "{state}" }) + cx.render(rsx!(div { "{state}" })) } else { - rsx!(cx, div { "Not rendering state" }) + cx.render(rsx!(div { "Not rendering state" })) } -}; +} /// Antipattern: Downcasting refs and panicking /// ------------------------------------------ @@ -173,9 +177,9 @@ static _example: Component = |cx| todo!(); /// libraries. static __example: Component = |cx| todo!(); -pub static Example: Component = |cx| { +fn Example(cx: Scope) -> Element { cx.render(rsx! { AntipatternNoKeys { data: std::collections::HashMap::new() } AntipatternNestedFragments {} }) -}; +} diff --git a/examples/core_reference/basics.rs b/examples/core_reference/basics.rs index 3ba64e01a..4c076a3a2 100644 --- a/examples/core_reference/basics.rs +++ b/examples/core_reference/basics.rs @@ -9,7 +9,7 @@ use dioxus::prelude::*; -pub static Example: Component = |cx| { +pub fn Example(cx: Scope) -> Element { cx.render(rsx! { div { Greeting { @@ -18,20 +18,21 @@ pub static Example: Component = |cx| { } } }) -}; - -#[derive(PartialEq, Props)] -struct GreetingProps { - name: &'static str, } -static Greeting: Component = |cx| { +#[derive(PartialEq, Props)] +struct GreetingProps<'a> { + name: &'static str, + children: Element<'a>, +} + +pub fn Greeting<'a>(cx: Scope<'a, GreetingProps<'a>>) -> Element { cx.render(rsx! { div { h1 { "Hello, {cx.props.name}!" } p { "Welcome to the Dioxus framework" } br {} - {cx.children()} + &cx.props.children } }) -}; +} diff --git a/examples/core_reference/children.rs b/examples/core_reference/children.rs index 8190f53d4..1b922b322 100644 --- a/examples/core_reference/children.rs +++ b/examples/core_reference/children.rs @@ -18,7 +18,7 @@ use dioxus::prelude::*; -pub static Example: Component = |cx| { +fn Example(cx: Scope) -> Element { cx.render(rsx! { div { Banner { @@ -29,16 +29,19 @@ pub static Example: Component = |cx| { } } }) -}; +} -pub static Banner: Component = |cx| { +#[derive(Props)] +struct BannerProps { + children: Element<'a>, +} + +fn Banner(cx: Scope) -> Element { cx.render(rsx! { div { h1 { "This is a great banner!" } - div { class: "content" - {cx.children()} - } + div { class: "content", &cx.props.children } footer { "Wow, what a great footer" } } }) -}; +} diff --git a/examples/core_reference/conditional_rendering.rs b/examples/core_reference/conditional_rendering.rs index 9d15a735a..8b7f4baaa 100644 --- a/examples/core_reference/conditional_rendering.rs +++ b/examples/core_reference/conditional_rendering.rs @@ -16,15 +16,15 @@ use dioxus::prelude::*; pub struct MyProps { should_show: bool, } -pub static Example0: Component = |cx| { +pub fn Example0(cx: Scope) -> Element { cx.render(rsx! { div { - {cx.props.should_show.then(|| rsx!{ + cx.props.should_show.then(|| rsx!{ h1 { "showing the title!" } - })} + }) } }) -}; +} // Convert a boolean conditional into an either/or // Because rsx! is lazy (produces a closure), we cannot use it in two branch arms. To use it in matching/branching, we @@ -39,31 +39,29 @@ pub static Example0: Component = |cx| { pub struct MyProps1 { should_show: bool, } -pub static Example1: Component = |cx| { +pub fn Example1(cx: Scope) -> Element { cx.render(rsx! { div { // With matching - {match props.should_show { + match props.should_show { true => cx.render(rsx!(div {"it is true!"})), false => rsx!(cx, div {"it is false!"}), - }} + }, // or with just regular conditions - {if props.should_show { + if props.should_show { rsx!(cx, div {"it is true!"}) } else { rsx!(cx, div {"it is false!"}) - }} + }, // or with optional chaining - { - props.should_show + props.should_show .then(|| rsx!(cx, div {"it is true!"})) .unwrap_or_else(|| rsx!(cx, div {"it is false!"})) - } } }) -}; +} /// Matching can be expanded @@ -77,19 +75,19 @@ pub enum Color { pub struct MyProps2 { color: Color, } -pub static Example2: Component = |cx| { +pub fn Example2(cx: Scope) -> Element { cx.render(rsx! { div { - {match props.color { + match props.color { Color::Green => rsx!(cx, div {"it is Green!"}), Color::Yellow => rsx!(cx, div {"it is Yellow!"}), Color::Red => rsx!(cx, div {"it is Red!"}), - }} + } } }) -}; +} -pub static Example: Component = |cx| { +pub fn Example(cx: Scope<()>) -> Element { let should_show = use_state(&cx, || false); let mut color_index = use_state(&cx, || 0); let color = match *color_index % 2 { @@ -101,11 +99,11 @@ pub static Example: Component = |cx| { cx.render(rsx! { div { button { - onclick: move |_| should_show.set(!*should_show) + onclick: move |_| should_show.set(!*should_show), "click to toggle showing the examples" } button { - onclick: move |_| color_index += 1 + onclick: move |_| color_index += 1, "click to for the enxt color" } Example0 { should_show: *should_show } @@ -113,4 +111,4 @@ pub static Example: Component = |cx| { Example2 { color: color } } }) -}; +} diff --git a/examples/core_reference/controlled_inputs.rs b/examples/core_reference/controlled_inputs.rs index eccf46064..8f3292e22 100644 --- a/examples/core_reference/controlled_inputs.rs +++ b/examples/core_reference/controlled_inputs.rs @@ -1,16 +1,16 @@ use dioxus::prelude::*; fn main() {} -pub static Example: Component = |cx| { +pub fn Example(cx: Scope) -> Element { cx.render(rsx! { div { } }) -}; +} // A controlled component: -static ControlledSelect: Component = |cx| { +pub fn ControlledSelect(cx: Scope) -> Element { let value = use_state(&cx, || String::from("Grapefruit")); cx.render(rsx! { select { value: "{value}", onchange: move |evt| value.set(evt.value()), @@ -20,10 +20,10 @@ static ControlledSelect: Component = |cx| { option { value: "Mango", "Mango"} } }) -}; +} // TODO - how do uncontrolled things work? -static UncontrolledSelect: Component = |cx| { +pub fn UncontrolledSelect(cx: Scope) -> Element { let value = use_state(&cx, || String::new()); cx.render(rsx! { @@ -34,4 +34,4 @@ static UncontrolledSelect: Component = |cx| { option { value: "Mango", "Mango"} } }) -}; +} diff --git a/examples/core_reference/custom_elements.rs b/examples/core_reference/custom_elements.rs index 0830ed01a..6c7367d20 100644 --- a/examples/core_reference/custom_elements.rs +++ b/examples/core_reference/custom_elements.rs @@ -11,7 +11,7 @@ use dioxus::prelude::*; -pub static Example: Component = |cx| { +pub fn Example(cx: Scope) -> Element { cx.render(rsx! { div { custom_element { @@ -19,7 +19,7 @@ pub static Example: Component = |cx| { } } }) -}; +} mod dioxus_elements { use std::fmt::Arguments; diff --git a/examples/core_reference/empty.rs b/examples/core_reference/empty.rs index 78adce251..e773901e9 100644 --- a/examples/core_reference/empty.rs +++ b/examples/core_reference/empty.rs @@ -5,4 +5,6 @@ use dioxus::prelude::*; -pub static Example: Component = |cx| cx.render(rsx! { Fragment {} }); +pub fn Example(cx: Scope) -> Element { + None +} diff --git a/examples/core_reference/errorhandling.rs b/examples/core_reference/errorhandling.rs index 3a2875bf6..72b7d5468 100644 --- a/examples/core_reference/errorhandling.rs +++ b/examples/core_reference/errorhandling.rs @@ -23,20 +23,20 @@ fn main() {} /// This is one way to go about error handling (just toss things away with unwrap). /// However, if you get it wrong, the whole app will crash. /// This is pretty flimsy. -static App: Component = |cx| { +pub fn App(cx: Scope) -> Element { let data = get_data().unwrap(); cx.render(rsx!( div { "{data}" } )) -}; +} /// This is a pretty verbose way of error handling /// However, it's still pretty good since we don't panic, just fail to render anything -static App1: Component = |cx| { +pub fn App1(cx: Scope) -> Element { let data = match get_data() { Some(data) => data, None => return None, }; cx.render(rsx!( div { "{data}" } )) -}; +} /// This is an even better form of error handling. /// However, it _does_ make the component go blank, which might not be desirable. @@ -46,33 +46,37 @@ static App1: Component = |cx| { /// a user is logged in. /// /// Dioxus will throw an error in the console if the None-path is ever taken. -static App2: Component = |cx| { +pub fn App2(cx: Scope) -> Element { let data = get_data()?; cx.render(rsx!( div { "{data}" } )) -}; +} /// This is top-tier error handling since it displays a failure state. /// /// However, the error is lacking in context. -static App3: Component = |cx| match get_data() { - Some(data) => cx.render(rsx!( div { "{data}" } )), - None => cx.render(rsx!( div { "Failed to load data :(" } )), -}; +pub fn App3(cx: Scope) -> Element { + match get_data() { + Some(data) => cx.render(rsx!( div { "{data}" } )), + None => cx.render(rsx!( div { "Failed to load data :(" } )), + } +} /// For errors that return results, it's possible to short-circuit the match-based error handling with `.ok()` which converts /// a Result into an Option and lets you abort rendering by early-returning `None` -static App4: Component = |cx| { +pub fn App4(cx: Scope) -> Element { let data = get_data_err().ok()?; cx.render(rsx!( div { "{data}" } )) -}; +} /// This is great error handling since it displays a failure state... with context! /// /// Hopefully you'll never need to display a screen like this. It's rather bad taste -static App5: Component = |cx| match get_data_err() { - Ok(data) => cx.render(rsx!( div { "{data}" } )), - Err(c) => cx.render(rsx!( div { "Failed to load data: {c}" } )), -}; +pub fn App5(cx: Scope) -> Element { + match get_data_err() { + Ok(data) => cx.render(rsx!( div { "{data}" } )), + Err(c) => cx.render(rsx!( div { "Failed to load data: {c}" } )), + } +} // this fetching function produces "nothing" fn get_data() -> Option { diff --git a/examples/core_reference/fragments.rs b/examples/core_reference/fragments.rs index 2a5b87373..85523de31 100644 --- a/examples/core_reference/fragments.rs +++ b/examples/core_reference/fragments.rs @@ -10,17 +10,25 @@ use dioxus::prelude::*; +pub fn Example(cx: Scope) -> Element { + cx.render(rsx! { + App1 {} + App2 {} + App3 {} + }) +} + // Returning multiple elements with rsx! or html! -static App1: Component = |cx| { +pub fn App1(cx: Scope) -> Element { cx.render(rsx! { h1 { } h2 { } h3 { } }) -}; +} // Using the Fragment component -static App2: Component = |cx| { +pub fn App2(cx: Scope) -> Element { cx.render(rsx! { Fragment { div {} @@ -28,10 +36,10 @@ static App2: Component = |cx| { "asd" } }) -}; +} // Using the `fragment` method on the NodeFactory -static App3: Component = |cx| { +pub fn App3(cx: Scope) -> Element { cx.render(LazyNodes::new(move |fac| { fac.fragment_from_iter([ fac.text(format_args!("A")), @@ -40,12 +48,4 @@ static App3: Component = |cx| { fac.text(format_args!("B")), ]) })) -}; - -pub static Example: Component = |cx| { - cx.render(rsx! { - App1 {} - App2 {} - App3 {} - }) -}; +} diff --git a/examples/core_reference/global_css.rs b/examples/core_reference/global_css.rs index a7b201d69..cd007b42f 100644 --- a/examples/core_reference/global_css.rs +++ b/examples/core_reference/global_css.rs @@ -19,7 +19,7 @@ h1 {color: blue;} p {color: red;} "#; -pub static Example: Component = |cx| { +pub fn Example(cx: Scope) -> Element { cx.render(rsx! { head { style { "{STYLE}" } } body { @@ -27,4 +27,4 @@ pub static Example: Component = |cx| { p {"This is a paragraph"} } }) -}; +} diff --git a/examples/core_reference/inline_styles.rs b/examples/core_reference/inline_styles.rs index d3459bcfd..35816e760 100644 --- a/examples/core_reference/inline_styles.rs +++ b/examples/core_reference/inline_styles.rs @@ -10,7 +10,7 @@ use dioxus::prelude::*; -pub static Example: Component = |cx| { +pub fn Example(cx: Scope) -> Element { cx.render(rsx! { head { style: { background_color: "powderblue" } @@ -24,15 +24,15 @@ pub static Example: Component = |cx| { } } }) -}; +} // .... technically the rsx! macro is slightly broken at the moment and allows styles not wrapped in style {} // I haven't noticed any name collisions yet, and am tentatively leaving this behavior in.. // Don't rely on it. -static Example2: Component = |cx| { +pub fn Example2(cx: Scope) -> Element { cx.render(rsx! { div { color: "red" "hello world!" } }) -}; +} diff --git a/examples/core_reference/iterators.rs b/examples/core_reference/iterators.rs index ad376a1bd..12922344d 100644 --- a/examples/core_reference/iterators.rs +++ b/examples/core_reference/iterators.rs @@ -15,25 +15,20 @@ use dioxus::prelude::*; pub static Example: Component = |cx| { let example_data = use_state(&cx, || 0); - let v = (0..10).map(|f| { - rsx! { - li { onclick: move |_| example_data.set(f) - "ID: {f}" - ul { - (0..10).map(|k| rsx!{ - li { - "Sub iterator: {f}.{k}" - } - }) - } - } - } - }); - cx.render(rsx! { h3 {"Selected: {example_data}"} ul { - {v} + (0..10).map(|f| rsx! { + li { + onclick: move |_| example_data.set(f), + "ID: {f}" + ul { + (0..10).map(|k| rsx!{ + li { "Sub iterator: {f}.{k}" } + }) + } + } + }) } }) }; diff --git a/examples/core_reference/listener.rs b/examples/core_reference/listener.rs index b76e16046..ba87eece3 100644 --- a/examples/core_reference/listener.rs +++ b/examples/core_reference/listener.rs @@ -7,50 +7,49 @@ use dioxus::prelude::*; -pub static Example: Component = |cx| { +pub fn Example(cx: Scope) -> Element { cx.render(rsx! { ButtonList {} NonUpdatingEvents {} DisablePropagation {} }) -}; +} /// We can use `set_name` in multiple closures; the closures automatically *copy* the reference to set_name. -static ButtonList: Component = |cx| { +pub fn ButtonList(cx: Scope) -> Element { let name = use_state(&cx, || "...?"); - let names = ["jack", "jill", "john", "jane"] - .iter() - .map(move |n| rsx!(button { onclick: move |_| name.set(n), "{n}" })); - cx.render(rsx!( div { h1 { "Hello, {name}" } - {names} + + ["jack", "jill", "john", "jane"] + .iter() + .map(move |n| rsx!(button { onclick: move |_| name.set(n), "{n}" })) } )) -}; +} /// This shows how listeners may be without a visible change in the display. /// Check the console. -static NonUpdatingEvents: Component = |cx| { - rsx!(cx, div { - button { - onclick: move |_| log::info!("Did not cause any updates!") - "Click me to log!" +pub fn NonUpdatingEvents(cx: Scope) -> Element { + cx.render(rsx! { + div { + button { + onclick: move |_| log::info!("Did not cause any updates!"), + "Click me to log!" + } } }) -}; +} -static DisablePropagation: Component = |cx| { - rsx!(cx, +pub fn DisablePropagation(cx: Scope) -> Element { + cx.render(rsx! { div { onclick: move |_| log::info!("event propagated to the div!") button { - onclick: move |evt| { - log::info!("Button will allow propagation"); - } + onclick: move |evt| log::info!("Button will allow propagation"), } } - ) -}; + }) +} diff --git a/examples/core_reference/memo.rs b/examples/core_reference/memo.rs index 061646a52..0c2e0a156 100644 --- a/examples/core_reference/memo.rs +++ b/examples/core_reference/memo.rs @@ -21,11 +21,11 @@ use dioxus::prelude::*; // By default, components with no props are always memoized. // A props of () is considered empty. -pub static Example: Component = |cx| { +pub fn Example(cx: Scope) -> Element { cx.render(rsx! { div { "100% memoized!" } }) -}; +} // These props do not borrow any content, and therefore can be safely memoized. // However, the parent *must* create a new string on every render. @@ -35,11 +35,11 @@ pub struct MyProps1 { name: String, } -pub static Example1: Component = |cx| { +pub fn Example1(cx: Scope) -> Element { cx.render(rsx! { div { "100% memoized! {cx.props.name}" } }) -}; +} // These props do not borrow any content, and therefore can be safely memoized. // In contrast with the `String` example, these props use `Rc` which operates similar to strings in JavaScript. @@ -49,11 +49,11 @@ pub struct MyProps2 { name: std::rc::Rc, } -pub static Example2: Component = |cx| { +pub fn Example2(cx: Scope) -> Element { cx.render(rsx! { div { "100% memoized! {cx.props.name}" } }) -}; +} // These props *do* borrow any content, and therefore cannot be safely memoized!. #[derive(PartialEq, Props)] @@ -61,7 +61,7 @@ pub struct MyProps3<'a> { name: &'a str, } // We need to manually specify a lifetime that ensures props and scope (the component's state) share the same lifetime. -// Using the `pub static Example: Component` pattern _will_ specify a lifetime, but that lifetime will be static which might +// Using the `pub fn Example(cx: Scope): Component` pattern _will_ specify a lifetime, but that lifetime will be static which might // not exactly be what you want fn Example3(cx: Scope<'a, MyProps3<'a>>) -> DomTree { cx.render(rsx! { diff --git a/examples/core_reference/noderefs.rs b/examples/core_reference/noderefs.rs deleted file mode 100644 index a3d7053d4..000000000 --- a/examples/core_reference/noderefs.rs +++ /dev/null @@ -1,12 +0,0 @@ -use dioxus::prelude::*; -fn main() {} - -pub static Example: Component = |cx| { - let p = 10; - - cx.render(rsx! { - div { - - } - }) -}; diff --git a/examples/core_reference/signals.rs b/examples/core_reference/signals.rs deleted file mode 100644 index d15e3f70a..000000000 --- a/examples/core_reference/signals.rs +++ /dev/null @@ -1,10 +0,0 @@ -use dioxus::prelude::*; -fn main() {} - -pub static Example: Component = |cx| { - cx.render(rsx! { - div { - - } - }) -}; diff --git a/examples/core_reference/spreadpattern.rs b/examples/core_reference/spreadpattern.rs index c24d008f2..34a467f90 100644 --- a/examples/core_reference/spreadpattern.rs +++ b/examples/core_reference/spreadpattern.rs @@ -9,7 +9,7 @@ use dioxus::prelude::*; -pub static Example: Component = |cx| { +pub fn Example(cx: Scope) -> Element { let props = MyProps { count: 0, live: true, @@ -18,22 +18,24 @@ pub static Example: Component = |cx| { cx.render(rsx! { Example1 { ..props, count: 10, div {"child"} } }) -}; +} -#[derive(PartialEq, Props)] -pub struct MyProps { +#[derive(Props)] +pub struct MyProps<'a> { count: u32, live: bool, name: &'static str, + children: Element<'a>, } -pub static Example1: Component = |cx, MyProps { count, live, name }| { +pub fn Example1(cx: Scope) -> Element { + let MyProps { count, live, name } = cx.props; cx.render(rsx! { div { h1 { "Hello, {name}"} h3 {"Are we alive? {live}"} p {"Count is {count}"} - { cx.children() } + &cx.props.children } }) -}; +} diff --git a/examples/core_reference/statemanagement.rs b/examples/core_reference/statemanagement.rs deleted file mode 100644 index d15e3f70a..000000000 --- a/examples/core_reference/statemanagement.rs +++ /dev/null @@ -1,10 +0,0 @@ -use dioxus::prelude::*; -fn main() {} - -pub static Example: Component = |cx| { - cx.render(rsx! { - div { - - } - }) -}; diff --git a/examples/core_reference/suspense.rs b/examples/core_reference/suspense.rs index e624595fa..ea1f92b96 100644 --- a/examples/core_reference/suspense.rs +++ b/examples/core_reference/suspense.rs @@ -14,7 +14,7 @@ struct DogApi { } const ENDPOINT: &str = "https://dog.ceo/api/breeds/image/random"; -pub static Example: Component = |cx| { +pub fn Example(cx: Scope) -> Element { let doggo = use_suspense( cx, || surf::get(ENDPOINT).recv_json::(), @@ -35,4 +35,4 @@ pub static Example: Component = |cx| { {doggo} } )) -}; +} diff --git a/examples/core_reference/task.rs b/examples/core_reference/task.rs index d213917c4..25f634517 100644 --- a/examples/core_reference/task.rs +++ b/examples/core_reference/task.rs @@ -42,19 +42,19 @@ pub static Example: Component = |cx| { div { h1 {"count is {count}"} button { + onclick: move |_| task.stop(), "Stop counting" - onclick: move |_| task.stop() } button { + onclick: move |_| task.resume(), "Start counting" - onclick: move |_| task.resume() } button { - "Switch counting direcion" onclick: move |_| { direction *= -1; task.restart(); } + "Switch counting direcion" } } }) diff --git a/examples/core_reference/testing.rs b/examples/core_reference/testing.rs deleted file mode 100644 index aaef5fb7f..000000000 --- a/examples/core_reference/testing.rs +++ /dev/null @@ -1,9 +0,0 @@ -use dioxus::prelude::*; - -pub static Example: Component = |cx| { - cx.render(rsx! { - div { - - } - }) -}; diff --git a/examples/core_reference/tostring.rs b/examples/core_reference/tostring.rs deleted file mode 100644 index 9c8563935..000000000 --- a/examples/core_reference/tostring.rs +++ /dev/null @@ -1,25 +0,0 @@ -use dioxus::prelude::*; -use dioxus::ssr; - -pub static Example: Component = |cx| { - let as_string = use_state(&cx, || { - // Currently, SSR is only supported for whole VirtualDOMs - // This is an easy/low hanging fruit to improve upon - let mut dom = VirtualDom::new(SomeApp); - dom.rebuild(); - ssr::render_vdom(&dom) - }); - - cx.render(rsx! { - div { "{as_string}" } - }) -}; - -static SomeApp: Component = |cx| { - cx.render(rsx! { - div { style: {background_color: "blue"} - h1 {"Some amazing app or component"} - p {"Things are great"} - } - }) -}; diff --git a/examples/coroutine.rs b/examples/coroutine.rs index ee957bd34..8ef6c2bfb 100644 --- a/examples/coroutine.rs +++ b/examples/coroutine.rs @@ -22,12 +22,12 @@ //! the coroutine was initiated. `use_state` always returns the same setter, so you don't need to worry about fn main() { - dioxus::desktop::launch(App); + dioxus::desktop::launch(app); } use dioxus::prelude::*; -static App: Component = |cx| { +fn app(cx: Scope) -> Element { let p1 = use_state(&cx, || 0); let p2 = use_state(&cx, || 0); @@ -53,7 +53,7 @@ static App: Component = |cx| { Horsey { pos: *p2, "horsey 2" } } }) -}; +} #[derive(Props)] struct HorseyProps<'a> { diff --git a/examples/crm.rs b/examples/crm.rs index 11acb4346..b72fe541d 100644 --- a/examples/crm.rs +++ b/examples/crm.rs @@ -20,12 +20,11 @@ pub struct Client { } fn app(cx: Scope) -> Element { - let clients = use_ref(&cx, || vec![] as Vec); - let scene = use_state(&cx, || Scene::ClientsList); let firstname = use_state(&cx, String::new); let lastname = use_state(&cx, String::new); let description = use_state(&cx, String::new); + let clients = use_ref(&cx, || vec![] as Vec); cx.render(rsx!( body { diff --git a/examples/desktop/core.rs b/examples/desktop/core.rs deleted file mode 100644 index de3e04b5d..000000000 --- a/examples/desktop/core.rs +++ /dev/null @@ -1,5 +0,0 @@ -use dioxus_core::prelude::*; -use dioxus_core_macro::*; -use dioxus_html as dioxus_elements; - -fn main() {} diff --git a/examples/desktop/crm.rs b/examples/desktop/crm.rs deleted file mode 100644 index 5c3d50228..000000000 --- a/examples/desktop/crm.rs +++ /dev/null @@ -1,109 +0,0 @@ -use dioxus_core as dioxus; -use dioxus_core::prelude::*; -use dioxus_core_macro::*; -use dioxus_hooks::*; -use dioxus_html as dioxus_elements; - -fn main() { - dioxus_desktop::launch(App) -} - -enum Scene { - ClientsList, - NewClientForm, - Settings, -} - -#[derive(Clone, Debug, Default)] -pub struct Client { - pub first_name: String, - pub last_name: String, - pub description: String, -} - -static App: Component = |cx| { - let mut scene = use_state(&cx, || Scene::ClientsList); - let clients = use_ref(&cx, || vec![] as Vec); - - let mut firstname = use_state(&cx, String::new); - let mut lastname = use_state(&cx, String::new); - let mut description = use_state(&cx, String::new); - - let scene = match scene.get() { - Scene::ClientsList => { - rsx!(cx, div { class: "crm" - h2 { "List of clients" margin_bottom: "10px" } - div { class: "clients" margin_left: "10px" - {clients.read().iter().map(|client| rsx!( - div { class: "client" style: "margin-bottom: 50px" - p { "First Name: {client.first_name}" } - p { "Last Name: {client.last_name}" } - p {"Description: {client.description}"} - }) - )} - } - button { class: "pure-button pure-button-primary" onclick: move |_| scene.set(Scene::NewClientForm), "Add New" } - button { class: "pure-button" onclick: move |_| scene.set(Scene::Settings), "Settings" } - }) - } - Scene::NewClientForm => { - let add_new = move |_| { - clients.write().push(Client { - description: (*description).clone(), - first_name: (*firstname).clone(), - last_name: (*lastname).clone(), - }); - description.set(String::new()); - firstname.set(String::new()); - lastname.set(String::new()); - }; - rsx!(cx, div { class: "crm" - h2 {"Add new client" margin_bottom: "10px" } - form { class: "pure-form" - input { class: "new-client firstname" placeholder: "First name" value: "{firstname}" - oninput: move |evt| firstname.set(evt.value) - } - input { class: "new-client lastname" placeholder: "Last name" value: "{lastname}" - oninput: move |evt| lastname.set(evt.value) - } - textarea { class: "new-client description" placeholder: "Description" value: "{description}" - oninput: move |evt| description.set(evt.value) - } - } - button { class: "pure-button pure-button-primary", onclick: {add_new}, "Add New" } - button { class: "pure-button", onclick: move |_| scene.set(Scene::ClientsList), "Go Back" } - }) - } - Scene::Settings => { - rsx!(cx, div { - h2 { "Settings" margin_bottom: "10px" } - button { - background: "rgb(202, 60, 60)" - class: "pure-button pure-button-primary" - onclick: move |_| { - clients.write().clear(); - scene.set(Scene::ClientsList); - }, - "Remove all clients" - } - button { - class: "pure-button pure-button-primary" - onclick: move |_| scene.set(Scene::ClientsList), - "Go Back" - } - }) - } - }; - - rsx!(cx, body { - link { - rel: "stylesheet" - href: "https://unpkg.com/purecss@2.0.6/build/pure-min.css" - integrity: "sha384-Uu6IeWbM+gzNVXJcM9XV3SohHtmWE+3VGi496jvgX1jyvDTXfdK+rfZc8C1Aehk5" - crossorigin: "anonymous" - } - margin_left: "35%" - h1 {"Dioxus CRM Example"} - {scene} - }) -}; diff --git a/examples/desktop/demo.rs b/examples/desktop/demo.rs deleted file mode 100644 index d8dc51697..000000000 --- a/examples/desktop/demo.rs +++ /dev/null @@ -1,18 +0,0 @@ -use dioxus_core as dioxus; -use dioxus_core::prelude::*; -use dioxus_core_macro::*; - -use dioxus_html as dioxus_elements; - -fn main() { - dioxus_desktop::launch(App); -} - -static App: Component = |cx| { - cx.render(rsx!( - div { - "hello world!" - } - (0..10).map(|f| rsx!( div {"abc {f}"})) - )) -}; diff --git a/examples/desktop/todomvc.css b/examples/desktop/todomvc.css deleted file mode 100644 index ab9dbd7a9..000000000 --- a/examples/desktop/todomvc.css +++ /dev/null @@ -1,379 +0,0 @@ -html, -body { - margin: 0; - padding: 0; -} - -button { - margin: 0; - padding: 0; - border: 0; - background: none; - font-size: 100%; - vertical-align: baseline; - font-family: inherit; - font-weight: inherit; - color: inherit; - -webkit-appearance: none; - appearance: none; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -body { - font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif; - line-height: 1.4em; - background: #f5f5f5; - color: #4d4d4d; - min-width: 230px; - max-width: 550px; - margin: 0 auto; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - font-weight: 300; -} - -:focus { - outline: 0; -} - -.hidden { - display: none; -} - -.todoapp { - background: #fff; - margin: 130px 0 40px 0; - position: relative; - box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), - 0 25px 50px 0 rgba(0, 0, 0, 0.1); -} - -.todoapp input::-webkit-input-placeholder { - font-style: italic; - font-weight: 300; - color: #e6e6e6; -} - -.todoapp input::-moz-placeholder { - font-style: italic; - font-weight: 300; - color: #e6e6e6; -} - -.todoapp input::input-placeholder { - font-style: italic; - font-weight: 300; - color: #e6e6e6; -} - -.todoapp h1 { - position: absolute; - top: -155px; - width: 100%; - font-size: 100px; - font-weight: 100; - text-align: center; - color: rgba(175, 47, 47, 0.15); - -webkit-text-rendering: optimizeLegibility; - -moz-text-rendering: optimizeLegibility; - text-rendering: optimizeLegibility; -} - -.new-todo, -.edit { - position: relative; - margin: 0; - width: 100%; - font-size: 24px; - font-family: inherit; - font-weight: inherit; - line-height: 1.4em; - border: 0; - color: inherit; - padding: 6px; - border: 1px solid #999; - box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2); - box-sizing: border-box; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -.new-todo { - padding: 16px 16px 16px 60px; - border: none; - background: rgba(0, 0, 0, 0.003); - box-shadow: inset 0 -2px 1px rgba(0, 0, 0, 0.03); -} - -.main { - position: relative; - z-index: 2; - border-top: 1px solid #e6e6e6; -} - -.toggle-all { - text-align: center; - border: none; - /* Mobile Safari */ - opacity: 0; - position: absolute; -} - -.toggle-all+label { - width: 60px; - height: 34px; - font-size: 0; - position: absolute; - top: -52px; - left: -13px; - -webkit-transform: rotate(90deg); - transform: rotate(90deg); -} - -.toggle-all+label:before { - content: '❯'; - font-size: 22px; - color: #e6e6e6; - padding: 10px 27px 10px 27px; -} - -.toggle-all:checked+label:before { - color: #737373; -} - -.todo-list { - margin: 0; - padding: 0; - list-style: none; -} - -.todo-list li { - position: relative; - font-size: 24px; - border-bottom: 1px solid #ededed; -} - -.todo-list li:last-child { - border-bottom: none; -} - -.todo-list li.editing { - border-bottom: none; - padding: 0; -} - -.todo-list li.editing .edit { - display: block; - width: 506px; - padding: 12px 16px; - margin: 0 0 0 43px; -} - -.todo-list li.editing .view { - display: none; -} - -.todo-list li .toggle { - text-align: center; - width: 40px; - /* auto, since non-WebKit browsers doesn't support input styling */ - height: auto; - position: absolute; - top: 0; - bottom: 0; - margin: auto 0; - border: none; - /* Mobile Safari */ - -webkit-appearance: none; - appearance: none; -} - -.todo-list li .toggle { - opacity: 0; -} - -.todo-list li .toggle+label { - /* - Firefox requires `#` to be escaped - https://bugzilla.mozilla.org/show_bug.cgi?id=922433 - IE and Edge requires *everything* to be escaped to render, so we do that instead of just the `#` - https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/7157459/ - */ - background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23ededed%22%20stroke-width%3D%223%22/%3E%3C/svg%3E'); - background-repeat: no-repeat; - background-position: center left; -} - -.todo-list li .toggle:checked+label { - background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23bddad5%22%20stroke-width%3D%223%22/%3E%3Cpath%20fill%3D%22%235dc2af%22%20d%3D%22M72%2025L42%2071%2027%2056l-4%204%2020%2020%2034-52z%22/%3E%3C/svg%3E'); -} - -.todo-list li label { - word-break: break-all; - padding: 15px 15px 15px 60px; - display: block; - line-height: 1.2; - transition: color 0.4s; -} - -.todo-list li.completed label { - color: #d9d9d9; - text-decoration: line-through; -} - -.todo-list li .destroy { - display: none; - position: absolute; - top: 0; - right: 10px; - bottom: 0; - width: 40px; - height: 40px; - margin: auto 0; - font-size: 30px; - color: #cc9a9a; - margin-bottom: 11px; - transition: color 0.2s ease-out; -} - -.todo-list li .destroy:hover { - color: #af5b5e; -} - -.todo-list li .destroy:after { - content: '×'; -} - -.todo-list li:hover .destroy { - display: block; -} - -.todo-list li .edit { - display: none; -} - -.todo-list li.editing:last-child { - margin-bottom: -1px; -} - -.footer { - color: #777; - padding: 10px 15px; - height: 20px; - text-align: center; - border-top: 1px solid #e6e6e6; -} - -.footer:before { - content: ''; - position: absolute; - right: 0; - bottom: 0; - left: 0; - height: 50px; - overflow: hidden; - box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2), - 0 8px 0 -3px #f6f6f6, - 0 9px 1px -3px rgba(0, 0, 0, 0.2), - 0 16px 0 -6px #f6f6f6, - 0 17px 2px -6px rgba(0, 0, 0, 0.2); -} - -.todo-count { - float: left; - text-align: left; -} - -.todo-count strong { - font-weight: 300; -} - -.filters { - margin: 0; - padding: 0; - list-style: none; - position: absolute; - right: 0; - left: 0; -} - -.filters li { - display: inline; -} - -.filters li a { - color: inherit; - margin: 3px; - padding: 3px 7px; - text-decoration: none; - border: 1px solid transparent; - border-radius: 3px; -} - -.filters li a:hover { - border-color: rgba(175, 47, 47, 0.1); -} - -.filters li a.selected { - border-color: rgba(175, 47, 47, 0.2); -} - -.clear-completed, -html .clear-completed:active { - float: right; - position: relative; - line-height: 20px; - text-decoration: none; - cursor: pointer; -} - -.clear-completed:hover { - text-decoration: underline; -} - -.info { - margin: 65px auto 0; - color: #bfbfbf; - font-size: 10px; - text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); - text-align: center; -} - -.info p { - line-height: 1; -} - -.info a { - color: inherit; - text-decoration: none; - font-weight: 400; -} - -.info a:hover { - text-decoration: underline; -} - -/* - Hack to remove background from Mobile Safari. - Can't use it globally since it destroys checkboxes in Firefox -*/ -@media screen and (-webkit-min-device-pixel-ratio:0) { - - .toggle-all, - .todo-list li .toggle { - background: none; - } - - .todo-list li .toggle { - height: 40px; - } -} - -@media (max-width: 430px) { - .footer { - height: 50px; - } - - .filters { - bottom: 10px; - } -} diff --git a/examples/desktop/todomvc.rs b/examples/desktop/todomvc.rs deleted file mode 100644 index 64bfb2876..000000000 --- a/examples/desktop/todomvc.rs +++ /dev/null @@ -1,170 +0,0 @@ -#![allow(non_upper_case_globals, non_snake_case)] - -use dioxus_core as dioxus; -use dioxus_core::prelude::*; -use dioxus_core_macro::*; -use dioxus_hooks::*; -use dioxus_html as dioxus_elements; - -use std::collections::HashMap; - -fn main() { - dioxus_desktop::launch(App) -} - -#[derive(PartialEq)] -pub enum FilterState { - All, - Active, - Completed, -} - -#[derive(Debug, PartialEq)] -pub struct TodoItem { - pub id: u32, - pub checked: bool, - pub contents: String, -} -pub type Todos = HashMap; - -pub static App: Component = |cx| { - // Share our TodoList to the todos themselves - use_provide_state(cx, Todos::new); - - // Save state for the draft, filter - let draft = use_state(&cx, || "".to_string()); - let filter = use_state(&cx, || FilterState::All); - let mut todo_id = use_state(&cx, || 0); - - // Consume the todos - let todos = use_shared_state::(cx)?; - - // Filter the todos based on the filter state - let mut filtered_todos = todos - .read() - .iter() - .filter(|(_, item)| match *filter { - FilterState::All => true, - FilterState::Active => !item.checked, - FilterState::Completed => item.checked, - }) - .map(|f| *f.0) - .collect::>(); - filtered_todos.sort_unstable(); - - // Define the actions to manage the todolist - let mut submit_todo = move || { - if !draft.is_empty() { - todos.write().insert( - *todo_id, - TodoItem { - id: *todo_id, - checked: false, - contents: draft.get().clone(), - }, - ); - todo_id += 1; - draft.set("".to_string()); - } - }; - let clear_completed = move || { - todos.write().retain(|_, todo| todo.checked == false); - }; - - // Some assists in actually rendering the content - let show_clear_completed = todos.read().values().any(|todo| todo.checked); - let items_left = filtered_todos.len(); - let item_text = match items_left { - 1 => "item", - _ => "items", - }; - - cx.render(rsx!{ - section { class: "todoapp" - style { {[include_str!("./todomvc.css")]} } - div { - header { class: "header" - h1 {"todos"} - input { - class: "new-todo" - placeholder: "What needs to be done?" - value: "{draft}" - autofocus: "true" - oninput: move |evt| draft.set(evt.value) - onkeydown: move |evt| { - if evt.key == "Enter" { - submit_todo(); - } - } - } - } - ul { class: "todo-list", - filtered_todos.iter().map(|id| rsx!(TodoEntry { key: "{id}", id: *id })) - } - (!todos.read().is_empty()).then(|| rsx!( - footer { class: "footer", - span { class: "todo-count" strong {"{items_left} "} span {"{item_text} left"} } - ul { class: "filters" - li { class: "All", a { onclick: move |_| filter.set(FilterState::All), "All" }} - li { class: "Active", a { onclick: move |_| filter.set(FilterState::Active), "Active" }} - li { class: "Completed", a { onclick: move |_| filter.set(FilterState::Completed), "Completed" }} - } - show_clear_completed.then(|| rsx!( - button { class: "clear-completed", onclick: move |_| clear_completed(), - "Clear completed" - } - )) - } - )) - } - } - footer { class: "info" - p {"Double-click to edit a todo"} - p { "Created by ", a { "jkelleyrtp", href: "http://github.com/jkelleyrtp/" }} - p { "Part of ", a { "TodoMVC", href: "http://todomvc.com" }} - } - }) -}; - -#[derive(PartialEq, Props)] -pub struct TodoEntryProps { - id: u32, -} - -pub fn TodoEntry((cx, props): Scope) -> Element { - let todos = use_shared_state::(cx)?; - - let _todos = todos.read(); - let todo = _todos.get(&cx.props.id)?; - - let is_editing = use_state(&cx, || false); - let completed = if todo.checked { "completed" } else { "" }; - - cx.render(rsx!{ - li { class: "{completed}" - div { class: "view" - input { class: "toggle" r#type: "checkbox" id: "cbg-{todo.id}" checked: "{todo.checked}" - onchange: move |evt| { - if let Some(todo) = todos.write().get_mut(&cx.props.id) { - todo.checked = evt.value.parse().unwrap() - } - } - } - - label { r#for: "cbg-{todo.id}" pointer_events: "none" - "{todo.contents}" - } - - {is_editing.then(|| rsx!{ - input { value: "{todo.contents}" - oninput: move |evt| { - if let Some(todo) = todos.write().get_mut(&cx.props.id) { - todo.contents = evt.value - } - }, - } - })} - } - } - }) -} diff --git a/examples/framework_benchmark.rs b/examples/framework_benchmark.rs index 73a82ea2c..f43ac4205 100644 --- a/examples/framework_benchmark.rs +++ b/examples/framework_benchmark.rs @@ -2,7 +2,7 @@ use dioxus::prelude::*; use rand::prelude::*; fn main() { - dioxus::desktop::launch(App); + dioxus::desktop::launch(app); } #[derive(Clone, PartialEq)] @@ -29,7 +29,7 @@ impl Label { } } -static App: Component = |cx| { +fn app(cx: Scope) -> Element { let items = use_ref(&cx, || vec![]); let selected = use_state(&cx, || None); @@ -64,7 +64,7 @@ static App: Component = |cx| { } table { tbody { - {items.read().iter().enumerate().map(|(id, item)| { + items.read().iter().enumerate().map(|(id, item)| { let is_in_danger = if (*selected).map(|s| s == id).unwrap_or(false) {"danger"} else {""}; rsx!(tr { class: "{is_in_danger}", td { class:"col-md-1" } @@ -79,13 +79,13 @@ static App: Component = |cx| { } td { class: "col-md-6" } }) - })} + }) } } span { class: "preloadicon glyphicon glyphicon-remove", aria_hidden: "true" } } }) -}; +} #[derive(Props)] struct ActionButtonProps<'a> { @@ -95,9 +95,17 @@ struct ActionButtonProps<'a> { } fn ActionButton<'a>(cx: Scope<'a, ActionButtonProps<'a>>) -> Element { - rsx!(cx, div { class: "col-sm-6 smallpad", - button { class:"btn btn-primary btn-block", r#type: "button", id: "{cx.props.id}", onclick: move |_| (cx.props.onclick)(), - "{cx.props.name}" + cx.render(rsx! { + div { + class: "col-sm-6 smallpad", + button { + class:"btn btn-primary btn-block", + r#type: "button", + id: "{cx.props.id}", + onclick: move |_| (cx.props.onclick)(), + + "{cx.props.name}" + } } }) } diff --git a/examples/hydration.rs b/examples/hydration.rs index e0a23b46b..6ec777352 100644 --- a/examples/hydration.rs +++ b/examples/hydration.rs @@ -13,12 +13,13 @@ use dioxus::prelude::*; use dioxus::ssr; fn main() { - let vdom = VirtualDom::new(App); + let vdom = VirtualDom::new(app); let content = ssr::render_vdom_cfg(&vdom, |f| f.pre_render(true)); - dioxus::desktop::launch_cfg(App, |c| c.with_prerendered(content)); + + dioxus::desktop::launch_cfg(app, |c| c.with_prerendered(content)); } -static App: Component = |cx| { +fn app(cx: Scope) -> Element { let val = use_state(&cx, || 0); cx.render(rsx! { @@ -30,4 +31,4 @@ static App: Component = |cx| { } } }) -}; +} diff --git a/examples/inputs.rs b/examples/inputs.rs index 18edc9e8f..ed11b48d9 100644 --- a/examples/inputs.rs +++ b/examples/inputs.rs @@ -7,7 +7,7 @@ use std::sync::Arc; use dioxus::{events::FormEvent, prelude::*}; fn main() { - dioxus::desktop::launch(App); + dioxus::desktop::launch(app); } const FIELDS: &[(&str, &str)] = &[ @@ -37,7 +37,7 @@ const FIELDS: &[(&str, &str)] = &[ ("week", ""), // degrades to text most of the time ]; -static App: Component = |cx| { +fn app(cx: Scope) -> Element { cx.render(rsx! { div { margin_left: "30px", @@ -147,4 +147,4 @@ static App: Component = |cx| { } }) -}; +} diff --git a/examples/manual_edits.rs b/examples/manual_edits.rs index 5b0d0de4e..65690424e 100644 --- a/examples/manual_edits.rs +++ b/examples/manual_edits.rs @@ -33,7 +33,7 @@ fn main() { AppendChildren { many: 1 }, ]; - let app: Component = |cx| rsx!(cx, div { "some app" }); + let app: Component = |cx| cx.render(rsx!(div { "some app" })); dioxus_desktop::run(app, (), |c| c.with_edits(edits)); } diff --git a/examples/pattern_model.rs b/examples/pattern_model.rs index d9d2eb47e..0d5f07b81 100644 --- a/examples/pattern_model.rs +++ b/examples/pattern_model.rs @@ -21,7 +21,6 @@ use dioxus::desktop::wry::application::dpi::LogicalSize; use dioxus::events::*; use dioxus::prelude::*; -const STYLE: &str = include_str!("./assets/calculator.css"); fn main() { env_logger::init(); dioxus::desktop::launch_cfg(app, |cfg| { @@ -40,32 +39,36 @@ fn app(cx: Scope) -> Element { let clear_text = if clear_display { "C" } else { "AC" }; let formatted = state.read().formatted_display(); - rsx!(cx, div { id: "wrapper", - div { class: "app", style { "{STYLE}" } - div { class: "calculator", onkeypress: move |evt| state.write().handle_keydown(evt), - div { class: "calculator-display", "{formatted}"} - div { class: "calculator-keypad", - div { class: "input-keys", - div { class: "function-keys", - CalculatorKey { name: "key-clear", onclick: move |_| state.write().clear_display(), "{clear_text}" } - CalculatorKey { name: "key-sign", onclick: move |_| state.write().toggle_sign(), "±"} - CalculatorKey { name: "key-percent", onclick: move |_| state.write().toggle_percent(), "%"} + cx.render(rsx!{ + div { id: "wrapper", + div { + class: "app", + style { [include_str!("./assets/calculator.css")] } + div { class: "calculator", onkeypress: move |evt| state.write().handle_keydown(evt), + div { class: "calculator-display", "{formatted}"} + div { class: "calculator-keypad", + div { class: "input-keys", + div { class: "function-keys", + CalculatorKey { name: "key-clear", onclick: move |_| state.write().clear_display(), "{clear_text}" } + CalculatorKey { name: "key-sign", onclick: move |_| state.write().toggle_sign(), "±"} + CalculatorKey { name: "key-percent", onclick: move |_| state.write().toggle_percent(), "%"} + } + div { class: "digit-keys", + CalculatorKey { name: "key-0", onclick: move |_| state.write().input_digit(0), "0" } + CalculatorKey { name: "key-dot", onclick: move |_| state.write().input_dot(), "●" } + (1..10).map(move |k| rsx!{ + CalculatorKey { key: "{k}", name: "key-{k}", onclick: move |_| state.write().input_digit(k), "{k}" } + }) + } } - div { class: "digit-keys", - CalculatorKey { name: "key-0", onclick: move |_| state.write().input_digit(0), "0" } - CalculatorKey { name: "key-dot", onclick: move |_| state.write().input_dot(), "●" } - (1..10).map(move |k| rsx!{ - CalculatorKey { key: "{k}", name: "key-{k}", onclick: move |_| state.write().input_digit(k), "{k}" } - }) + div { class: "operator-keys", + CalculatorKey { name:"key-divide", onclick: move |_| state.write().set_operator(Operator::Div), "÷" } + CalculatorKey { name:"key-multiply", onclick: move |_| state.write().set_operator(Operator::Mul), "×" } + CalculatorKey { name:"key-subtract", onclick: move |_| state.write().set_operator(Operator::Sub), "−" } + CalculatorKey { name:"key-add", onclick: move |_| state.write().set_operator(Operator::Add), "+" } + CalculatorKey { name:"key-equals", onclick: move |_| state.write().perform_operation(), "=" } } } - div { class: "operator-keys", - CalculatorKey { name:"key-divide", onclick: move |_| state.write().set_operator(Operator::Div), "÷" } - CalculatorKey { name:"key-multiply", onclick: move |_| state.write().set_operator(Operator::Mul), "×" } - CalculatorKey { name:"key-subtract", onclick: move |_| state.write().set_operator(Operator::Sub), "−" } - CalculatorKey { name:"key-add", onclick: move |_| state.write().set_operator(Operator::Add), "+" } - CalculatorKey { name:"key-equals", onclick: move |_| state.write().perform_operation(), "=" } - } } } } diff --git a/examples/pattern_reducer.rs b/examples/pattern_reducer.rs index 26bf3acff..d646b5105 100644 --- a/examples/pattern_reducer.rs +++ b/examples/pattern_reducer.rs @@ -8,10 +8,10 @@ use dioxus::prelude::*; fn main() { env_logger::init(); - dioxus::desktop::launch(App); + dioxus::desktop::launch(app); } -pub static App: Component = |cx| { +fn app(cx: Scope) -> Element { let state = use_state(&cx, PlayerState::new); cx.render(rsx!( @@ -26,7 +26,7 @@ pub static App: Component = |cx| { } } )) -}; +} enum PlayerAction { Pause, diff --git a/examples/readme.rs b/examples/readme.rs index ae17c1cd9..a9ea60822 100644 --- a/examples/readme.rs +++ b/examples/readme.rs @@ -5,10 +5,10 @@ use dioxus::prelude::*; fn main() { - dioxus::desktop::launch(App); + dioxus::desktop::launch(app); } -static App: Component = |cx| { +fn app(cx: Scope) -> Element { let mut count = use_state(&cx, || 0); cx.render(rsx! { @@ -18,4 +18,4 @@ static App: Component = |cx| { button { onclick: move |_| count -= 1, "Down low!" } } }) -}; +} diff --git a/examples/router.rs b/examples/router.rs index c28853185..4f4b7b2c1 100644 --- a/examples/router.rs +++ b/examples/router.rs @@ -23,7 +23,7 @@ pub enum Route { NotFound, } -static App: Component = |cx| { +fn app(cx: Scope) -> Element { let route = use_router(&cx, Route::parse); cx.render(rsx! { @@ -42,7 +42,7 @@ static App: Component = |cx| { } footer {} }) -}; +} impl Route { // Generate the appropriate route from the "tail" end of the URL diff --git a/examples/rsx_compile_fail.rs b/examples/rsx_compile_fail.rs index cdc69c21e..20760ef5b 100644 --- a/examples/rsx_compile_fail.rs +++ b/examples/rsx_compile_fail.rs @@ -1,3 +1,6 @@ +//! This example just flexes the ability to use arbitrary expressions within RSX. +//! It also proves that lifetimes work properly, especially when used with use_ref + use dioxus::prelude::*; fn main() { diff --git a/examples/rsx_usage.rs b/examples/rsx_usage.rs index 04afb7407..490aea844 100644 --- a/examples/rsx_usage.rs +++ b/examples/rsx_usage.rs @@ -39,7 +39,7 @@ //! - Allow top-level fragments //! fn main() { - dioxus::desktop::launch(EXAMPLE); + dioxus::desktop::launch(app); } /// When trying to return "nothing" to Dioxus, you'll need to specify the type parameter or Rust will be sad. @@ -49,7 +49,7 @@ const NONE_ELEMENT: Option<()> = None; use baller::Baller; use dioxus::prelude::*; -pub static EXAMPLE: Component = |cx| { +fn app(cx: Scope) -> Element { let formatting = "formatting!"; let formatting_tuple = ("a", "b"); let lazy_fmt = format_args!("lazily formatted text"); @@ -192,10 +192,12 @@ pub static EXAMPLE: Component = |cx| { [helper(&cx, "hello world!")] } }) -}; +} fn helper<'a>(cx: &'a ScopeState, text: &str) -> Element<'a> { - rsx!(cx, p { "{text}" }) + cx.render(rsx! { + p { "{text}" } + }) } mod baller { @@ -227,5 +229,7 @@ pub fn Taller<'a>(cx: Scope<'a, TallerProps<'a>>) -> Element { #[inline_props] fn with_inline<'a>(cx: Scope<'a>, text: &'a str) -> Element { - rsx!(cx, p { "{text}" }) + cx.render(rsx! { + p { "{text}" } + }) } diff --git a/examples/ssr.rs b/examples/ssr.rs index 21a73b67b..b07d0a2c1 100644 --- a/examples/ssr.rs +++ b/examples/ssr.rs @@ -2,16 +2,16 @@ use dioxus::prelude::*; use dioxus::ssr; fn main() { - let mut vdom = VirtualDom::new(APP); + let mut vdom = VirtualDom::new(app); let _ = vdom.rebuild(); println!("{}", ssr::render_vdom(&vdom)); } -static APP: Component = |cx| { +fn app(cx: Scope) -> Element { cx.render(rsx!( div { h1 { "Title" } p { "Body" } } )) -}; +} diff --git a/examples/ssr/basic.rs b/examples/ssr/basic.rs index 916a9fa0e..2217b5a4a 100644 --- a/examples/ssr/basic.rs +++ b/examples/ssr/basic.rs @@ -3,8 +3,9 @@ use dioxus_core as dioxus; use dioxus_core::prelude::*; use dioxus_core_macro::*; use dioxus_html as dioxus_elements; + fn main() { - let mut dom = VirtualDom::new(App); + let mut dom = VirtualDom::new(app); dom.rebuild(); println!( "{}", @@ -12,14 +13,14 @@ fn main() { ) } -pub static App: Component = |cx| { +fn app(cx: Scope) -> Element { cx.render(rsx!( div { class: "overflow-hidden" ul { - {(0..10).map(|i| rsx!{ li { class: "flex flex-col", "entry: {i}"}})} + (0..10).map(|i| rsx!{ li { class: "flex flex-col", "entry: {i}"}}) } "hello world!" } )) -}; +} diff --git a/examples/ssr/hydration.rs b/examples/ssr/hydration.rs deleted file mode 100644 index 92eee69fa..000000000 --- a/examples/ssr/hydration.rs +++ /dev/null @@ -1,10 +0,0 @@ -//! Example: realworld usage of hydration -//! -//! -use dioxus::virtual_dom::VirtualDom; -use dioxus_core as dioxus; -use dioxus_core::prelude::*; -use dioxus_hooks::use_state; -use dioxus_html as dioxus_elements; - -fn main() {} diff --git a/examples/ssr/template.html b/examples/ssr/template.html deleted file mode 100644 index 0bacc8be5..000000000 --- a/examples/ssr/template.html +++ /dev/null @@ -1,298 +0,0 @@ - - - - - - - - - - - -
hello dioxus
- - - - diff --git a/examples/ssr/tide.rs b/examples/ssr/tide.rs deleted file mode 100644 index d7255b91d..000000000 --- a/examples/ssr/tide.rs +++ /dev/null @@ -1,61 +0,0 @@ -//! -//! -//! -use dioxus::virtual_dom::VirtualDom; -use dioxus_core as dioxus; -use dioxus_core::prelude::*; -use dioxus_core_macro::*; -use dioxus_hooks::use_state; -use dioxus_html as dioxus_elements; - -fn main() {} -// use tide::{Request, Response}; - -// #[async_std::main] -// async fn main() -> Result<(), std::io::Error> { -// let mut app = tide::new(); - -// app.at("/:name").get(|req: Request<()>| async move { -// let initial_name: String = req -// .param("name") -// .map(|f| f.parse().unwrap_or("...?".to_string())) -// .unwrap_or("...?".to_string()); - -// let mut dom = VirtualDom::new_with_props(Example, ExampleProps { initial_name }); - -// dom.rebuild(); - -// Ok(Response::builder(200) -// .body(format!("{}", dioxus_ssr::render_vdom(&dom))) -// .content_type(tide::http::mime::HTML) -// .build()) -// }); - -// println!("Server available at [http://127.0.0.1:8080/bill]"); -// app.listen("127.0.0.1:8080").await?; - -// Ok(()) -// } - -// #[derive(PartialEq, Props)] -// struct ExampleProps { -// initial_name: String, -// } - -// static Example: Component = |cx| { -// let dispaly_name = use_state(&cx, move || props.initial_name.clone()); - -// cx.render(rsx! { -// div { class: "py-12 px-4 text-center w-full max-w-2xl mx-auto", -// span { class: "text-sm font-semibold" -// "Dioxus Example: Jack and Jill" -// } -// h2 { class: "text-5xl mt-2 mb-6 leading-tight font-semibold font-heading" -// "Hello, {dispaly_name}" -// } -// ul { -// {(0..10).map(|f| rsx!( li {"Element {f}"} ))} -// } -// } -// }) -// }; diff --git a/examples/ssr/tofile.rs b/examples/ssr/tofile.rs index 9c64f14d5..da6e0eeb0 100644 --- a/examples/ssr/tofile.rs +++ b/examples/ssr/tofile.rs @@ -24,7 +24,7 @@ fn main() { .unwrap(); } -pub static App: Component = |cx| { +pub fn App(cx: Scope) -> Element { cx.render(rsx!( div { class: "overflow-hidden" link { href:"https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" rel:"stylesheet" } @@ -37,9 +37,9 @@ pub static App: Component = |cx| { Hero {} } )) -}; +} -pub static Header: Component = |cx| { +pub fn Header(cx: Scope) -> Element { cx.render(rsx! { div { header { class: "text-gray-400 bg-gray-900 body-font" @@ -63,9 +63,9 @@ pub static Header: Component = |cx| { } } }) -}; +} -pub static Hero: Component = |cx| { +pub fn Hero(cx: Scope) -> Element { // cx.render(rsx! { section{ class: "text-gray-400 bg-gray-900 body-font" @@ -102,8 +102,9 @@ pub static Hero: Component = |cx| { } } }) -}; -pub static Entry: Component = |cx| { +} + +pub fn Entry(cx: Scope) -> Element { // cx.render(rsx! { section{ class: "text-gray-400 bg-gray-900 body-font" @@ -114,9 +115,9 @@ pub static Entry: Component = |cx| { } } }) -}; +} -pub static StacksIcon: Component = |cx| { +pub fn StacksIcon(cx: Scope) -> Element { cx.render(rsx!( svg { xmlns: "http://www.w3.org/2000/svg" @@ -130,8 +131,9 @@ pub static StacksIcon: Component = |cx| { path { d: "M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"} } )) -}; -pub static RightArrowIcon: Component = |cx| { +} + +pub fn RightArrowIcon(cx: Scope) -> Element { cx.render(rsx!( svg { fill: "none" @@ -144,4 +146,4 @@ pub static RightArrowIcon: Component = |cx| { path { d: "M5 12h14M12 5l7 7-7 7"} } )) -}; +} diff --git a/examples/tailwind.rs b/examples/tailwind.rs index 9bd87c15e..2d5244f17 100644 --- a/examples/tailwind.rs +++ b/examples/tailwind.rs @@ -14,7 +14,7 @@ fn main() { const STYLE: &str = "body {overflow:hidden;}"; -pub static App: Component = |cx| { +pub fn App(cx: Scope) -> Element { cx.render(rsx!( div { class: "overflow-hidden", style { "{STYLE}" } @@ -28,9 +28,9 @@ pub static App: Component = |cx| { Hero {} } )) -}; +} -pub static Header: Component = |cx| { +pub fn Header(cx: Scope) -> Element { cx.render(rsx! { div { header { class: "text-gray-400 bg-gray-900 body-font", @@ -54,9 +54,9 @@ pub static Header: Component = |cx| { } } }) -}; +} -pub static Hero: Component = |cx| { +pub fn Hero(cx: Scope) -> Element { // cx.render(rsx! { section{ class: "text-gray-400 bg-gray-900 body-font", @@ -93,8 +93,8 @@ pub static Hero: Component = |cx| { } } }) -}; -pub static Entry: Component = |cx| { +} +pub fn Entry(cx: Scope) -> Element { // cx.render(rsx! { section{ class: "text-gray-400 bg-gray-900 body-font", @@ -105,9 +105,9 @@ pub static Entry: Component = |cx| { } } }) -}; +} -pub static StacksIcon: Component = |cx| { +pub fn StacksIcon(cx: Scope) -> Element { cx.render(rsx!( svg { // xmlns: "http://www.w3.org/2000/svg" @@ -121,8 +121,8 @@ pub static StacksIcon: Component = |cx| { path { d: "M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"} } )) -}; -pub static RightArrowIcon: Component = |cx| { +} +pub fn RightArrowIcon(cx: Scope) -> Element { cx.render(rsx!( svg { fill: "none", @@ -135,4 +135,4 @@ pub static RightArrowIcon: Component = |cx| { path { d: "M5 12h14M12 5l7 7-7 7"} } )) -}; +} diff --git a/examples/tasks.rs b/examples/tasks.rs index 0838c1fa2..0a63019d7 100644 --- a/examples/tasks.rs +++ b/examples/tasks.rs @@ -12,13 +12,14 @@ fn main() { fn app(cx: Scope) -> Element { let count = use_state(&cx, || 0); - use_future(&cx, || { - for_async![count]; + use_future(&cx, move || { + let count = UseState::for_async(&count); + // for_async![count]; async move { - loop { - tokio::time::sleep(Duration::from_millis(1000)).await; - count += 1; - } + // loop { + // tokio::time::sleep(Duration::from_millis(1000)).await; + // count += 1; + // } } }); diff --git a/examples/todomvc.rs b/examples/todomvc.rs index 8a62e0ddd..907779894 100644 --- a/examples/todomvc.rs +++ b/examples/todomvc.rs @@ -4,7 +4,7 @@ use im_rc::HashMap; use std::rc::Rc; fn main() { - dioxus::desktop::launch(App); + dioxus::desktop::launch(app); } #[derive(PartialEq)] @@ -21,8 +21,7 @@ pub struct TodoItem { pub contents: String, } -const STYLE: &str = include_str!("./assets/todomvc.css"); -const App: Component = |cx| { +fn app(cx: Scope) -> Element { let draft = use_state(&cx, || "".to_string()); let todos = use_state(&cx, || HashMap::>::new()); let filter = use_state(&cx, || FilterState::All); @@ -48,40 +47,43 @@ const App: Component = |cx| { _ => "items", }; - rsx!(cx, div { id: "app", - style {"{STYLE}"} - div { - header { class: "header", - h1 {"todos"} - input { - class: "new-todo", - placeholder: "What needs to be done?", - value: "{draft}", - oninput: move |evt| draft.set(evt.value.clone()), + cx.render(rsx!( + div { id: "app", + style { [include_str!("./assets/todomvc.css")] } + div { + header { + class: "header", + h1 { "todos" } + input { + class: "new-todo", + placeholder: "What needs to be done?", + value: "{draft}", + oninput: move |evt| draft.set(evt.value.clone()), + } } + todolist, + (!todos.is_empty()).then(|| rsx!( + footer { + span { + strong {"{items_left}"} + span {"{item_text} left"} + } + ul { class: "filters", + li { class: "All", a { href: "", onclick: move |_| filter.set(FilterState::All), "All" }} + li { class: "Active", a { href: "active", onclick: move |_| filter.set(FilterState::Active), "Active" }} + li { class: "Completed", a { href: "completed", onclick: move |_| filter.set(FilterState::Completed), "Completed" }} + } + } + )) + } + footer { class: "info", + p {"Double-click to edit a todo"} + p { "Created by ", a { href: "http://github.com/jkelleyrtp/", "jkelleyrtp" }} + p { "Part of ", a { href: "http://todomvc.com", "TodoMVC" }} } - todolist, - (!todos.is_empty()).then(|| rsx!( - footer { - span { - strong {"{items_left}"} - span {"{item_text} left"} - } - ul { class: "filters", - li { class: "All", a { href: "", onclick: move |_| filter.set(FilterState::All), "All" }} - li { class: "Active", a { href: "active", onclick: move |_| filter.set(FilterState::Active), "Active" }} - li { class: "Completed", a { href: "completed", onclick: move |_| filter.set(FilterState::Completed), "Completed" }} - } - } - )) } - footer { class: "info", - p {"Double-click to edit a todo"} - p { "Created by ", a { href: "http://github.com/jkelleyrtp/", "jkelleyrtp" }} - p { "Part of ", a { href: "http://todomvc.com", "TodoMVC" }} - } - }) -}; + )) +} #[derive(PartialEq, Props)] pub struct TodoEntryProps { @@ -93,18 +95,20 @@ pub fn TodoEntry(cx: Scope) -> Element { let contents = use_state(&cx, || String::from("")); let todo = &cx.props.todo; - rsx!(cx, li { - "{todo.id}" - input { - class: "toggle", - r#type: "checkbox", - "{todo.checked}" - } - {is_editing.then(|| rsx!{ + cx.render(rsx! { + li { + "{todo.id}" input { - value: "{contents}", - oninput: move |evt| contents.set(evt.value.clone()) + class: "toggle", + r#type: "checkbox", + "{todo.checked}" } - })} + is_editing.then(|| rsx!{ + input { + value: "{contents}", + oninput: move |evt| contents.set(evt.value.clone()) + } + }) + } }) } diff --git a/examples/weather_app.rs b/examples/weather_app.rs index 66e90dbb7..e174fd397 100644 --- a/examples/weather_app.rs +++ b/examples/weather_app.rs @@ -7,22 +7,23 @@ use dioxus::prelude::*; fn main() { - dioxus::desktop::launch(App); + dioxus::desktop::launch(app); } const ENDPOINT: &str = "https://api.openweathermap.org/data/2.5/weather"; -static App: Component = |cx| { +fn app(cx: Scope) -> Element { // let body = use_suspense( - cx, + &cx, || async { - let content = reqwest::get(ENDPOINT) - .await - .unwrap() - .json::() - .await - .unwrap(); + todo!() + // let content = reqwest::get(ENDPOINT) + // .await + // .unwrap() + // .json::() + // .await + // .unwrap(); }, |props| { // @@ -35,13 +36,12 @@ static App: Component = |cx| { {body} } }) -}; +} #[derive(PartialEq, Props)] struct WeatherProps {} -static WeatherDisplay: Component = |cx| { - // +fn WeatherDisplay(cx: Scope) -> Element { cx.render(rsx!( div { class: "flex items-center justify-center flex-col", div { class: "flex items-center justify-center", @@ -64,4 +64,4 @@ static WeatherDisplay: Component = |cx| { } } )) -}; +} diff --git a/examples/web_tick.rs b/examples/web_tick.rs index 74cc1897f..ae33d769a 100644 --- a/examples/web_tick.rs +++ b/examples/web_tick.rs @@ -13,10 +13,10 @@ use dioxus::prelude::*; fn main() { - dioxus::desktop::launch(App); + dioxus::desktop::launch(app); } -static App: Component = |cx| { +fn app(cx: Scope) -> Element { let mut rng = SmallRng::from_entropy(); cx.render(rsx! { @@ -29,7 +29,7 @@ static App: Component = |cx| { } } }) -}; +} #[derive(PartialEq, Props)] struct RowProps { diff --git a/examples/xss_safety.rs b/examples/xss_safety.rs index c694d646f..c08f31616 100644 --- a/examples/xss_safety.rs +++ b/examples/xss_safety.rs @@ -1,3 +1,7 @@ +//! XSS Safety +//! +//! This example proves that Dioxus is safe from XSS attacks. + use dioxus::prelude::*; fn main() { @@ -9,19 +13,11 @@ fn app(cx: Scope) -> Element { cx.render(rsx! { div { - "hello world!" - - h1 { "{contents}" } - - h3 { [contents.as_str()] } - + h3 { "{contents}" } input { value: "{contents}", - oninput: move |e| { - contents.set(e.value.clone()); - eprintln!("asd"); - }, - "type": "text", + r#type: "text", + oninput: move |e| contents.set(e.value.clone()), } } }) diff --git a/packages/core-macro/src/inlineprops.rs b/packages/core-macro/src/inlineprops.rs index acbf778d3..ac71d5601 100644 --- a/packages/core-macro/src/inlineprops.rs +++ b/packages/core-macro/src/inlineprops.rs @@ -99,8 +99,8 @@ impl ToTokens for InlinePropsBody { }; out_tokens.append_all(quote! { - #[allow(non_camel_case)] #modifiers + #[allow(non_camel_case_types)] #vis struct #struct_name #generics { #(#fields),* } diff --git a/packages/hooks/src/usestate.rs b/packages/hooks/src/usestate.rs deleted file mode 100644 index 03f15a58b..000000000 --- a/packages/hooks/src/usestate.rs +++ /dev/null @@ -1,277 +0,0 @@ -use dioxus_core::prelude::*; -use std::{ - cell::{Cell, Ref, RefCell, RefMut}, - fmt::{Debug, Display}, - rc::Rc, -}; - -/// Store state between component renders! -/// -/// ## Dioxus equivalent of useState, designed for Rust -/// -/// The Dioxus version of `useState` for state management inside components. It allows you to ergonomically store and -/// modify state between component renders. When the state is updated, the component will re-render. -/// -/// Dioxus' use_state basically wraps a RefCell with helper methods and integrates it with the VirtualDOM update system. -/// -/// [`use_state`] exposes a few helper methods to modify the underlying state: -/// - `.set(new)` allows you to override the "work in progress" value with a new value -/// - `.get_mut()` allows you to modify the WIP value -/// - `.get_wip()` allows you to access the WIP value -/// - `.deref()` provides the previous value (often done implicitly, though a manual dereference with `*` might be required) -/// -/// Additionally, a ton of std::ops traits are implemented for the `UseState` wrapper, meaning any mutative type operations -/// will automatically be called on the WIP value. -/// -/// ## Combinators -/// -/// On top of the methods to set/get state, `use_state` also supports fancy combinators to extend its functionality: -/// - `.classic()` and `.split()` convert the hook into the classic React-style hook -/// ```rust -/// let (state, set_state) = use_state(&cx, || 10).split() -/// ``` -/// -/// -/// Usage: -/// -/// ```ignore -/// const Example: Component = |cx| { -/// let counter = use_state(&cx, || 0); -/// -/// cx.render(rsx! { -/// div { -/// h1 { "Counter: {counter}" } -/// button { onclick: move |_| counter += 1, "Increment" } -/// button { onclick: move |_| counter -= 1, "Decrement" } -/// } -/// )) -/// } -/// ``` -pub fn use_state<'a, T: 'static>( - cx: &'a ScopeState, - initial_state_fn: impl FnOnce() -> T, -) -> UseState<'a, T> { - let hook = cx.use_hook(move |_| { - let first_val = initial_state_fn(); - UseStateInner { - current_val: Rc::new(first_val), - update_callback: cx.schedule_update(), - wip: Rc::new(RefCell::new(None)), - update_scheuled: Cell::new(false), - } - }); - - hook.update_scheuled.set(false); - let mut new_val = hook.wip.borrow_mut(); - if new_val.is_some() { - // if there's only one reference (weak or otherwise), we can just swap the values - if let Some(val) = Rc::get_mut(&mut hook.current_val) { - *val = new_val.take().unwrap(); - } else { - hook.current_val = Rc::new(new_val.take().unwrap()); - } - } - - UseState { inner: &*hook } -} -struct UseStateInner { - current_val: Rc, - update_scheuled: Cell, - update_callback: Rc, - wip: Rc>>, -} - -pub struct UseState<'a, T: 'static> { - inner: &'a UseStateInner, -} - -impl Copy for UseState<'_, T> {} -impl<'a, T> Clone for UseState<'a, T> -where - T: 'static, -{ - fn clone(&self) -> Self { - UseState { inner: self.inner } - } -} - -impl Debug for UseState<'_, T> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{:?}", self.inner.current_val) - } -} -impl<'a, T: 'static> UseState<'a, T> { - /// Tell the Dioxus Scheduler that we need to be processed - pub fn needs_update(&self) { - if !self.inner.update_scheuled.get() { - self.inner.update_scheuled.set(true); - (self.inner.update_callback)(); - } - } - - pub fn set(&self, new_val: T) { - *self.inner.wip.borrow_mut() = Some(new_val); - self.needs_update(); - } - - pub fn get(&self) -> &'a T { - &self.inner.current_val - } - - pub fn get_rc(&self) -> &'a Rc { - &self.inner.current_val - } - - /// Get the current status of the work-in-progress data - pub fn get_wip(&self) -> Ref> { - self.inner.wip.borrow() - } - - /// Get the current status of the work-in-progress data - pub fn get_wip_mut(&self) -> RefMut> { - self.inner.wip.borrow_mut() - } - - pub fn classic(self) -> (&'a T, Rc) { - (&self.inner.current_val, self.setter()) - } - - pub fn setter(&self) -> Rc { - let slot = self.inner.wip.clone(); - Rc::new(move |new| { - *slot.borrow_mut() = Some(new); - }) - } - - pub fn for_async(self) -> UseState<'static, T> { - todo!() - } - - pub fn wtih(self, f: impl FnOnce(&mut T)) { - let mut val = self.inner.wip.borrow_mut(); - - if let Some(inner) = val.as_mut() { - f(inner); - } - } -} - -impl<'a, T: 'static + ToOwned> UseState<'a, T> { - /// Gain mutable access to the new value via [`RefMut`]. - /// - /// If `modify` is called, then the component will re-render. - /// - /// This method is only available when the value is a `ToOwned` type. - /// - /// Mutable access is derived by calling "ToOwned" (IE cloning) on the current value. - /// - /// To get a reference to the current value, use `.get()` - pub fn modify(self) -> RefMut<'a, T> { - // make sure we get processed - self.needs_update(); - - // Bring out the new value, cloning if it we need to - // "get_mut" is locked behind "ToOwned" to make it explicit that cloning occurs to use this - RefMut::map(self.inner.wip.borrow_mut(), |slot| { - if slot.is_none() { - *slot = Some(self.inner.current_val.as_ref().to_owned()); - } - slot.as_mut().unwrap() - }) - } - - pub fn inner(self) -> T { - self.inner.current_val.as_ref().to_owned() - } -} - -impl<'a, T> std::ops::Deref for UseState<'a, T> { - type Target = T; - - fn deref(&self) -> &Self::Target { - self.get() - } -} - -// enable displaty for the handle -impl<'a, T: 'static + Display> std::fmt::Display for UseState<'a, T> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.inner.current_val) - } -} -impl<'a, V, T: PartialEq> PartialEq for UseState<'a, T> { - fn eq(&self, other: &V) -> bool { - self.get() == other - } -} -impl<'a, O, T: std::ops::Not + Copy> std::ops::Not for UseState<'a, T> { - type Output = O; - - fn not(self) -> Self::Output { - !*self.get() - } -} - -/* - -Convenience methods for UseState. - -Note! - -This is not comprehensive. -This is *just* meant to make common operations easier. -*/ - -use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign}; - -impl<'a, T: Copy + Add> Add for UseState<'a, T> { - type Output = T; - - fn add(self, rhs: T) -> Self::Output { - self.inner.current_val.add(rhs) - } -} -impl<'a, T: Copy + Add> AddAssign for UseState<'a, T> { - fn add_assign(&mut self, rhs: T) { - self.set(self.inner.current_val.add(rhs)); - } -} -impl<'a, T: Copy + Sub> Sub for UseState<'a, T> { - type Output = T; - - fn sub(self, rhs: T) -> Self::Output { - self.inner.current_val.sub(rhs) - } -} -impl<'a, T: Copy + Sub> SubAssign for UseState<'a, T> { - fn sub_assign(&mut self, rhs: T) { - self.set(self.inner.current_val.sub(rhs)); - } -} - -/// MUL -impl<'a, T: Copy + Mul> Mul for UseState<'a, T> { - type Output = T; - - fn mul(self, rhs: T) -> Self::Output { - self.inner.current_val.mul(rhs) - } -} -impl<'a, T: Copy + Mul> MulAssign for UseState<'a, T> { - fn mul_assign(&mut self, rhs: T) { - self.set(self.inner.current_val.mul(rhs)); - } -} -/// DIV -impl<'a, T: Copy + Div> Div for UseState<'a, T> { - type Output = T; - - fn div(self, rhs: T) -> Self::Output { - self.inner.current_val.div(rhs) - } -} -impl<'a, T: Copy + Div> DivAssign for UseState<'a, T> { - fn div_assign(&mut self, rhs: T) { - self.set(self.inner.current_val.div(rhs)); - } -} diff --git a/packages/hooks/src/usestate/handle.rs b/packages/hooks/src/usestate/handle.rs new file mode 100644 index 000000000..b9751044a --- /dev/null +++ b/packages/hooks/src/usestate/handle.rs @@ -0,0 +1,211 @@ +use super::owned::UseStateOwned; +use std::{ + cell::{Ref, RefMut}, + fmt::{Debug, Display}, + rc::Rc, +}; + +pub struct UseState<'a, T: 'static>(pub(crate) &'a UseStateOwned); + +impl Copy for UseState<'_, T> {} + +impl<'a, T: 'static> Clone for UseState<'a, T> { + fn clone(&self) -> Self { + UseState(self.0) + } +} + +impl Debug for UseState<'_, T> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self.0.current_val) + } +} + +impl<'a, T: 'static> UseState<'a, T> { + /// Tell the Dioxus Scheduler that we need to be processed + pub fn needs_update(&self) { + if !self.0.update_scheuled.get() { + self.0.update_scheuled.set(true); + (self.0.update_callback)(); + } + } + + pub fn set(&self, new_val: T) { + *self.0.wip.borrow_mut() = Some(new_val); + self.needs_update(); + } + + pub fn get(&self) -> &'a T { + &self.0.current_val + } + + pub fn get_rc(&self) -> &'a Rc { + &self.0.current_val + } + + /// Get the current status of the work-in-progress data + pub fn get_wip(&self) -> Ref> { + self.0.wip.borrow() + } + + /// Get the current status of the work-in-progress data + pub fn get_wip_mut(&self) -> RefMut> { + self.0.wip.borrow_mut() + } + + pub fn classic(self) -> (&'a T, Rc) { + (&self.0.current_val, self.setter()) + } + + pub fn setter(&self) -> Rc { + let slot = self.0.wip.clone(); + Rc::new(move |new| *slot.borrow_mut() = Some(new)) + } + + pub fn wtih(&self, f: impl FnOnce(&mut T)) { + let mut val = self.0.wip.borrow_mut(); + + if let Some(inner) = val.as_mut() { + f(inner); + } + } + + pub fn for_async(&self) -> UseStateOwned { + let UseStateOwned { + current_val, + wip, + update_callback, + update_scheuled, + } = self.0; + + UseStateOwned { + current_val: current_val.clone(), + wip: wip.clone(), + update_callback: update_callback.clone(), + update_scheuled: update_scheuled.clone(), + } + } +} + +impl<'a, T: 'static + ToOwned> UseState<'a, T> { + /// Gain mutable access to the new value via [`RefMut`]. + /// + /// If `modify` is called, then the component will re-render. + /// + /// This method is only available when the value is a `ToOwned` type. + /// + /// Mutable access is derived by calling "ToOwned" (IE cloning) on the current value. + /// + /// To get a reference to the current value, use `.get()` + pub fn modify(self) -> RefMut<'a, T> { + // make sure we get processed + self.needs_update(); + + // Bring out the new value, cloning if it we need to + // "get_mut" is locked behind "ToOwned" to make it explicit that cloning occurs to use this + RefMut::map(self.0.wip.borrow_mut(), |slot| { + if slot.is_none() { + *slot = Some(self.0.current_val.as_ref().to_owned()); + } + slot.as_mut().unwrap() + }) + } + + pub fn inner(self) -> T { + self.0.current_val.as_ref().to_owned() + } +} + +impl<'a, T> std::ops::Deref for UseState<'a, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + self.get() + } +} + +// enable displaty for the handle +impl<'a, T: 'static + Display> std::fmt::Display for UseState<'a, T> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0.current_val) + } +} +impl<'a, V, T: PartialEq> PartialEq for UseState<'a, T> { + fn eq(&self, other: &V) -> bool { + self.get() == other + } +} +impl<'a, O, T: std::ops::Not + Copy> std::ops::Not for UseState<'a, T> { + type Output = O; + + fn not(self) -> Self::Output { + !*self.get() + } +} + +/* + +Convenience methods for UseState. + +Note! + +This is not comprehensive. +This is *just* meant to make common operations easier. +*/ + +use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign}; + +impl<'a, T: Copy + Add> Add for UseState<'a, T> { + type Output = T; + + fn add(self, rhs: T) -> Self::Output { + self.0.current_val.add(rhs) + } +} +impl<'a, T: Copy + Add> AddAssign for UseState<'a, T> { + fn add_assign(&mut self, rhs: T) { + self.set(self.0.current_val.add(rhs)); + } +} + +/// Sub +impl<'a, T: Copy + Sub> Sub for UseState<'a, T> { + type Output = T; + + fn sub(self, rhs: T) -> Self::Output { + self.0.current_val.sub(rhs) + } +} +impl<'a, T: Copy + Sub> SubAssign for UseState<'a, T> { + fn sub_assign(&mut self, rhs: T) { + self.set(self.0.current_val.sub(rhs)); + } +} + +/// MUL +impl<'a, T: Copy + Mul> Mul for UseState<'a, T> { + type Output = T; + + fn mul(self, rhs: T) -> Self::Output { + self.0.current_val.mul(rhs) + } +} +impl<'a, T: Copy + Mul> MulAssign for UseState<'a, T> { + fn mul_assign(&mut self, rhs: T) { + self.set(self.0.current_val.mul(rhs)); + } +} + +/// DIV +impl<'a, T: Copy + Div> Div for UseState<'a, T> { + type Output = T; + + fn div(self, rhs: T) -> Self::Output { + self.0.current_val.div(rhs) + } +} +impl<'a, T: Copy + Div> DivAssign for UseState<'a, T> { + fn div_assign(&mut self, rhs: T) { + self.set(self.0.current_val.div(rhs)); + } +} diff --git a/packages/hooks/src/usestate/mod.rs b/packages/hooks/src/usestate/mod.rs new file mode 100644 index 000000000..cf125c2a4 --- /dev/null +++ b/packages/hooks/src/usestate/mod.rs @@ -0,0 +1,78 @@ +mod handle; +mod owned; +pub use handle::*; +pub use owned::*; + +use dioxus_core::prelude::*; +use std::{ + cell::{Cell, RefCell}, + rc::Rc, +}; + +/// Store state between component renders! +/// +/// ## Dioxus equivalent of useState, designed for Rust +/// +/// The Dioxus version of `useState` for state management inside components. It allows you to ergonomically store and +/// modify state between component renders. When the state is updated, the component will re-render. +/// +/// Dioxus' use_state basically wraps a RefCell with helper methods and integrates it with the VirtualDOM update system. +/// +/// [`use_state`] exposes a few helper methods to modify the underlying state: +/// - `.set(new)` allows you to override the "work in progress" value with a new value +/// - `.get_mut()` allows you to modify the WIP value +/// - `.get_wip()` allows you to access the WIP value +/// - `.deref()` provides the previous value (often done implicitly, though a manual dereference with `*` might be required) +/// +/// Additionally, a ton of std::ops traits are implemented for the `UseState` wrapper, meaning any mutative type operations +/// will automatically be called on the WIP value. +/// +/// ## Combinators +/// +/// On top of the methods to set/get state, `use_state` also supports fancy combinators to extend its functionality: +/// - `.classic()` and `.split()` convert the hook into the classic React-style hook +/// ```rust +/// let (state, set_state) = use_state(&cx, || 10).split() +/// ``` +/// +/// +/// Usage: +/// +/// ```ignore +/// const Example: Component = |cx| { +/// let counter = use_state(&cx, || 0); +/// +/// cx.render(rsx! { +/// div { +/// h1 { "Counter: {counter}" } +/// button { onclick: move |_| counter += 1, "Increment" } +/// button { onclick: move |_| counter -= 1, "Decrement" } +/// } +/// )) +/// } +/// ``` +pub fn use_state<'a, T: 'static>( + cx: &'a ScopeState, + initial_state_fn: impl FnOnce() -> T, +) -> UseState<'a, T> { + let hook = cx.use_hook(move |_| UseStateOwned { + current_val: Rc::new(initial_state_fn()), + update_callback: cx.schedule_update(), + wip: Rc::new(RefCell::new(None)), + update_scheuled: Cell::new(false), + }); + + hook.update_scheuled.set(false); + let mut new_val = hook.wip.borrow_mut(); + + if new_val.is_some() { + // if there's only one reference (weak or otherwise), we can just swap the values + if let Some(val) = Rc::get_mut(&mut hook.current_val) { + *val = new_val.take().unwrap(); + } else { + hook.current_val = Rc::new(new_val.take().unwrap()); + } + } + + UseState(hook) +} diff --git a/packages/hooks/src/usestate/owned.rs b/packages/hooks/src/usestate/owned.rs new file mode 100644 index 000000000..16468e0fd --- /dev/null +++ b/packages/hooks/src/usestate/owned.rs @@ -0,0 +1,102 @@ +use std::{ + cell::{Cell, Ref, RefCell, RefMut}, + fmt::{Debug, Display}, + rc::Rc, +}; +pub struct UseStateOwned { + // this will always be outdated + pub(crate) current_val: Rc, + pub(crate) wip: Rc>>, + pub(crate) update_callback: Rc, + pub(crate) update_scheuled: Cell, +} + +impl UseStateOwned { + pub fn get(&self) -> Ref { + Ref::map(self.wip.borrow(), |x| x.as_ref().unwrap()) + } + + pub fn set(&self, new_val: T) { + *self.wip.borrow_mut() = Some(new_val); + (self.update_callback)(); + } + + pub fn modify(&self) -> RefMut { + RefMut::map(self.wip.borrow_mut(), |x| x.as_mut().unwrap()) + } + + pub fn with(&self, f: impl FnOnce(&mut T)) { + // + } +} + +use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign}; + +impl Debug for UseStateOwned { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self.current_val) + } +} + +// enable displaty for the handle +impl<'a, T: 'static + Display> std::fmt::Display for UseStateOwned { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.current_val) + } +} + +impl<'a, T: Copy + Add> Add for UseStateOwned { + type Output = T; + + fn add(self, rhs: T) -> Self::Output { + self.current_val.add(rhs) + } +} + +impl<'a, T: Copy + Add> AddAssign for UseStateOwned { + fn add_assign(&mut self, rhs: T) { + self.set(self.current_val.add(rhs)); + } +} + +/// Sub +impl<'a, T: Copy + Sub> Sub for UseStateOwned { + type Output = T; + + fn sub(self, rhs: T) -> Self::Output { + self.current_val.sub(rhs) + } +} +impl<'a, T: Copy + Sub> SubAssign for UseStateOwned { + fn sub_assign(&mut self, rhs: T) { + self.set(self.current_val.sub(rhs)); + } +} + +/// MUL +impl<'a, T: Copy + Mul> Mul for UseStateOwned { + type Output = T; + + fn mul(self, rhs: T) -> Self::Output { + self.current_val.mul(rhs) + } +} +impl<'a, T: Copy + Mul> MulAssign for UseStateOwned { + fn mul_assign(&mut self, rhs: T) { + self.set(self.current_val.mul(rhs)); + } +} + +/// DIV +impl<'a, T: Copy + Div> Div for UseStateOwned { + type Output = T; + + fn div(self, rhs: T) -> Self::Output { + self.current_val.div(rhs) + } +} +impl<'a, T: Copy + Div> DivAssign for UseStateOwned { + fn div_assign(&mut self, rhs: T) { + self.set(self.current_val.div(rhs)); + } +}