From 08fa2382b023c13d83624f21c85d6b0a42eb10ae Mon Sep 17 00:00:00 2001 From: Ilya Maximov Date: Thu, 17 Mar 2022 22:55:26 +0100 Subject: [PATCH 01/23] add eval to `DesktopContext` --- packages/desktop/src/desktop_context.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/desktop/src/desktop_context.rs b/packages/desktop/src/desktop_context.rs index 93162842b..def9a21d0 100644 --- a/packages/desktop/src/desktop_context.rs +++ b/packages/desktop/src/desktop_context.rs @@ -119,6 +119,11 @@ impl DesktopContext { pub fn devtool(&self) { let _ = self.proxy.send_event(DevTool); } + + /// run (evaluate) a script in the WebView context + pub fn eval(&self, script: impl std::fmt::Display) { + let _ = self.proxy.send_event(Eval(script.to_string())); + } } #[derive(Debug)] @@ -144,6 +149,8 @@ pub enum UserWindowEvent { SetDecorations(bool), DevTool, + + Eval(String), } pub(super) fn handler( @@ -185,5 +192,7 @@ pub(super) fn handler( SetDecorations(state) => window.set_decorations(state), DevTool => webview.devtool(), + + Eval(code) => webview.evaluate_script(code.as_str()).expect("failed to eval script"), } } From 5cbf2698fdd9c7d94075d266dba1f2c2d7872e9d Mon Sep 17 00:00:00 2001 From: Ilya Maximov Date: Thu, 17 Mar 2022 23:47:11 +0100 Subject: [PATCH 02/23] add `use_eval()`to desktop and web --- packages/desktop/src/desktop_context.rs | 17 ++++++++++++++++- packages/desktop/src/lib.rs | 2 +- packages/web/src/lib.rs | 2 ++ packages/web/src/util.rs | 23 +++++++++++++++++++++++ src/lib.rs | 6 ++++++ 5 files changed, 48 insertions(+), 2 deletions(-) diff --git a/packages/desktop/src/desktop_context.rs b/packages/desktop/src/desktop_context.rs index def9a21d0..b15e26ea3 100644 --- a/packages/desktop/src/desktop_context.rs +++ b/packages/desktop/src/desktop_context.rs @@ -193,6 +193,21 @@ pub(super) fn handler( DevTool => webview.devtool(), - Eval(code) => webview.evaluate_script(code.as_str()).expect("failed to eval script"), + Eval(code) => webview + .evaluate_script(code.as_str()) + .expect("failed to eval script"), } } + +/// Get a closure that executes any JavaScript in the WebView context. +/// +/// # Panics +/// +/// The closure will cause the message processing thread to panic if the +/// provided script is not valid JavaScript code or if it returns an uncaught +/// error. +pub fn use_eval(cx: &ScopeState) -> impl Fn(S) + '_ { + let desktop = use_window(&cx); + + move |script| desktop.eval(script) +} diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index 4b2615d54..5f41b98ce 100644 --- a/packages/desktop/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -11,7 +11,7 @@ mod events; mod protocol; use desktop_context::UserWindowEvent; -pub use desktop_context::{use_window, DesktopContext}; +pub use desktop_context::{use_window, use_eval, DesktopContext}; pub use wry; pub use wry::application as tao; diff --git a/packages/web/src/lib.rs b/packages/web/src/lib.rs index c82dc4975..1ec9fe068 100644 --- a/packages/web/src/lib.rs +++ b/packages/web/src/lib.rs @@ -62,12 +62,14 @@ use dioxus::VirtualDom; pub use dioxus_core as dioxus; use dioxus_core::prelude::Component; use futures_util::FutureExt; +pub use crate::util::use_eval; mod cache; mod cfg; mod dom; mod rehydrate; mod ric_raf; +mod util; /// Launch the VirtualDOM given a root component and a configuration. /// diff --git a/packages/web/src/util.rs b/packages/web/src/util.rs index 638cf3dc2..696af7b21 100644 --- a/packages/web/src/util.rs +++ b/packages/web/src/util.rs @@ -1 +1,24 @@ //! Utilities specific to websys + +use dioxus_core::*; + +/// Get a closure that executes any JavaScript in the webpage. +/// +/// # Safety +/// +/// Please be very careful with this function. A script with too many dynamic +/// parts is practically asking for a hacker to find an XSS vulnerability in +/// it. **This applies especially to web targets, where the JavaScript context +/// has access to most, if not all of your application data.** +/// +/// # Panics +/// +/// The closure will panic if the provided script is not valid JavaScript code +/// or if it returns an uncaught error. +pub fn use_eval(_cx: &ScopeState) -> impl Fn(S) { + |script| { + js_sys::Function::new_no_args(&script.to_string()) + .call0(&wasm_bindgen::JsValue::NULL) + .expect("failed to eval script"); + } +} diff --git a/src/lib.rs b/src/lib.rs index cebffcf17..2ba5a9045 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -44,3 +44,9 @@ pub mod prelude { #[cfg(feature = "fermi")] pub use fermi::{use_atom_ref, use_init_atom_root, use_read, use_set, Atom, AtomRef}; } + +#[cfg(all(target = "wasm", feature = "web"))] +pub use dioxus_web::use_eval; + +#[cfg(all(not(target = "wasm"), feature = "desktop"))] +pub use dioxus_desktop::use_eval; \ No newline at end of file From b4a5fe9845a3a184081acabca94f0cdfcc2ce668 Mon Sep 17 00:00:00 2001 From: Ilya Maximov Date: Sat, 19 Mar 2022 02:57:39 +0100 Subject: [PATCH 03/23] use `ToString` --- packages/desktop/src/desktop_context.rs | 4 ++-- packages/web/src/util.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/desktop/src/desktop_context.rs b/packages/desktop/src/desktop_context.rs index b15e26ea3..5a2d50f31 100644 --- a/packages/desktop/src/desktop_context.rs +++ b/packages/desktop/src/desktop_context.rs @@ -121,7 +121,7 @@ impl DesktopContext { } /// run (evaluate) a script in the WebView context - pub fn eval(&self, script: impl std::fmt::Display) { + pub fn eval(&self, script: impl std::string::ToString) { let _ = self.proxy.send_event(Eval(script.to_string())); } } @@ -206,7 +206,7 @@ pub(super) fn handler( /// The closure will cause the message processing thread to panic if the /// provided script is not valid JavaScript code or if it returns an uncaught /// error. -pub fn use_eval(cx: &ScopeState) -> impl Fn(S) + '_ { +pub fn use_eval(cx: &ScopeState) -> impl Fn(S) + '_ { let desktop = use_window(&cx); move |script| desktop.eval(script) diff --git a/packages/web/src/util.rs b/packages/web/src/util.rs index 696af7b21..3e8f92806 100644 --- a/packages/web/src/util.rs +++ b/packages/web/src/util.rs @@ -15,7 +15,7 @@ use dioxus_core::*; /// /// The closure will panic if the provided script is not valid JavaScript code /// or if it returns an uncaught error. -pub fn use_eval(_cx: &ScopeState) -> impl Fn(S) { +pub fn use_eval(_cx: &ScopeState) -> impl Fn(S) { |script| { js_sys::Function::new_no_args(&script.to_string()) .call0(&wasm_bindgen::JsValue::NULL) From 40869b36ad2ba90ae0794b18e2bffc99103e3486 Mon Sep 17 00:00:00 2001 From: Ilya Maximov Date: Sat, 19 Mar 2022 03:08:08 +0100 Subject: [PATCH 04/23] stuff `use_eval` into `dioxus::hooks` --- src/lib.rs | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 2ba5a9045..2e50d9750 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,8 +2,16 @@ pub use dioxus_core as core; -#[cfg(feature = "hooks")] -pub use dioxus_hooks as hooks; +pub mod hooks { + #[cfg(feature = "hooks")] + pub use dioxus_hooks::*; + + #[cfg(all(target = "wasm", feature = "web"))] + pub use dioxus_web::use_eval; + + #[cfg(all(not(target = "wasm"), feature = "desktop"))] + pub use dioxus_desktop::use_eval; +} #[cfg(feature = "router")] pub use dioxus_router as router; @@ -35,7 +43,7 @@ pub mod prelude { pub use dioxus_core::prelude::*; pub use dioxus_core_macro::{format_args_f, inline_props, rsx, Props}; pub use dioxus_elements::{GlobalAttributes, SvgAttributes}; - pub use dioxus_hooks::*; + pub use crate::hooks::*; pub use dioxus_html as dioxus_elements; #[cfg(feature = "router")] @@ -44,9 +52,3 @@ pub mod prelude { #[cfg(feature = "fermi")] pub use fermi::{use_atom_ref, use_init_atom_root, use_read, use_set, Atom, AtomRef}; } - -#[cfg(all(target = "wasm", feature = "web"))] -pub use dioxus_web::use_eval; - -#[cfg(all(not(target = "wasm"), feature = "desktop"))] -pub use dioxus_desktop::use_eval; \ No newline at end of file From 5e475fadd900fc336cf7a891d08e414bf9f914d3 Mon Sep 17 00:00:00 2001 From: Ilya Maximov Date: Sat, 19 Mar 2022 03:11:30 +0100 Subject: [PATCH 05/23] add a desktop eval example --- examples/eval.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 examples/eval.rs diff --git a/examples/eval.rs b/examples/eval.rs new file mode 100644 index 000000000..c0801b507 --- /dev/null +++ b/examples/eval.rs @@ -0,0 +1,25 @@ +use dioxus::prelude::*; + +fn main() { + dioxus::desktop::launch(app); +} + +fn app(cx: Scope) -> Element { + let script = use_state(&cx, String::new); + let eval = use_eval(&cx); + + cx.render(rsx! { + div { + input { + placeholder: "Enter an expression", + value: "{script}", + oninput: move |e| script.set(e.value.clone()), + } + button { + onclick: move |_| eval(script), + + "Execute" + } + } + }) +} From c194fa8372b5ad5f86325623906c5c6bb0986d3b Mon Sep 17 00:00:00 2001 From: Ilya Maximov Date: Sat, 19 Mar 2022 03:13:34 +0100 Subject: [PATCH 06/23] remove panics from desktop docs `WebView::evaluate_script()` always returns `Ok()` --- packages/desktop/src/desktop_context.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/desktop/src/desktop_context.rs b/packages/desktop/src/desktop_context.rs index 5a2d50f31..4475fe978 100644 --- a/packages/desktop/src/desktop_context.rs +++ b/packages/desktop/src/desktop_context.rs @@ -195,17 +195,11 @@ pub(super) fn handler( Eval(code) => webview .evaluate_script(code.as_str()) - .expect("failed to eval script"), + .expect("eval shouldn't panic"), } } /// Get a closure that executes any JavaScript in the WebView context. -/// -/// # Panics -/// -/// The closure will cause the message processing thread to panic if the -/// provided script is not valid JavaScript code or if it returns an uncaught -/// error. pub fn use_eval(cx: &ScopeState) -> impl Fn(S) + '_ { let desktop = use_window(&cx); From 6e8fdc1e4d615f9ace527a6dc2e87abe101da826 Mon Sep 17 00:00:00 2001 From: Ilya Maximov Date: Mon, 21 Mar 2022 21:57:29 +0100 Subject: [PATCH 07/23] make the closure dyn --- packages/desktop/src/desktop_context.rs | 6 +++--- packages/web/src/util.rs | 14 ++++++++------ 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/packages/desktop/src/desktop_context.rs b/packages/desktop/src/desktop_context.rs index 4475fe978..4ad2ff3a7 100644 --- a/packages/desktop/src/desktop_context.rs +++ b/packages/desktop/src/desktop_context.rs @@ -200,8 +200,8 @@ pub(super) fn handler( } /// Get a closure that executes any JavaScript in the WebView context. -pub fn use_eval(cx: &ScopeState) -> impl Fn(S) + '_ { - let desktop = use_window(&cx); +pub fn use_eval(cx: &ScopeState) -> &dyn Fn(S) { + let desktop = use_window(&cx).clone(); - move |script| desktop.eval(script) + cx.use_hook(|_| move |script| desktop.eval(script)) } diff --git a/packages/web/src/util.rs b/packages/web/src/util.rs index 3e8f92806..3e7232035 100644 --- a/packages/web/src/util.rs +++ b/packages/web/src/util.rs @@ -15,10 +15,12 @@ use dioxus_core::*; /// /// The closure will panic if the provided script is not valid JavaScript code /// or if it returns an uncaught error. -pub fn use_eval(_cx: &ScopeState) -> impl Fn(S) { - |script| { - js_sys::Function::new_no_args(&script.to_string()) - .call0(&wasm_bindgen::JsValue::NULL) - .expect("failed to eval script"); - } +pub fn use_eval(cx: &ScopeState) -> &dyn Fn(S) { + cx.use_hook(|_| { + |script: S| { + js_sys::Function::new_no_args(&script.to_string()) + .call0(&wasm_bindgen::JsValue::NULL) + .expect("failed to eval script"); + } + }) } From f4b58c9bb6131abeca3d0c97c836ca27ec42d13e Mon Sep 17 00:00:00 2001 From: Ilya Maximov Date: Mon, 21 Mar 2022 21:57:51 +0100 Subject: [PATCH 08/23] add the example to the list --- examples/README.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/examples/README.md b/examples/README.md index e108c56cd..4a4ccbf9c 100644 --- a/examples/README.md +++ b/examples/README.md @@ -41,6 +41,7 @@ These examples are not necessarily meant to be run, but rather serve as a refere | [Anti-patterns](./antipatterns.rs) | A collection of discouraged patterns | ✅ | | [Complete rsx reference](./rsx_usage.rs) | A complete reference for all rsx! usage | ✅ | | [Event Listeners](./listener.rs) | Attach closures to events on elements | ✅ | +| [Eval](./eval.rs) | Evaluate dynamic JavaScript code | ✅ | ## Show me some examples! @@ -51,7 +52,7 @@ In our collection of examples, guides, and tutorials, we have: - The reference (a collection of examples with heavy documentation) - The general examples - The platform-specific examples (web, ssr, desktop, mobile, server) - + Here's what a few common tasks look like in Dioxus: Nested components with children and internal state: @@ -80,7 +81,7 @@ Controlled inputs: ```rust fn App(cx: Scope) -> Element { let value = use_state(&cx, String::new); - cx.render(rsx!( + cx.render(rsx!( input { "type": "text", value: "{value}", @@ -96,16 +97,16 @@ fn App(cx: Scope) -> Element { let list = (0..10).map(|i| { rsx!(li { key: "{i}", "Value: {i}" }) }); - + let title = match list.len() { 0 => rsx!("Not enough"), _ => rsx!("Plenty!"), }; if should_show { - cx.render(rsx!( + cx.render(rsx!( title, - ul { list } + ul { list } )) } else { None @@ -165,18 +166,18 @@ fn App(cx: Scope) -> Element { Route::Home => rsx!( Home {} ), Route::Post(id) => rsx!( Post { id: id }) } - })) + })) } ``` -Suspense +Suspense ```rust fn App(cx: Scope) -> Element { let doggo = use_suspense(cx, || async { reqwest::get("https://dog.ceo/api/breeds/image/random").await.unwrap().json::().await.unwrap() }, |response| cx.render(rsx!( img { src: "{response.message}" })) ); - + cx.render(rsx!{ div { "One doggo coming right up:", From d31edfa7d7055c9e36eea3d1002e98c8771e5a0f Mon Sep 17 00:00:00 2001 From: Ilya Maximov Date: Mon, 21 Mar 2022 22:02:04 +0100 Subject: [PATCH 09/23] rustfmt when the --- packages/desktop/src/lib.rs | 2 +- packages/web/src/lib.rs | 2 +- src/lib.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index 5f41b98ce..596956c53 100644 --- a/packages/desktop/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -11,7 +11,7 @@ mod events; mod protocol; use desktop_context::UserWindowEvent; -pub use desktop_context::{use_window, use_eval, DesktopContext}; +pub use desktop_context::{use_eval, use_window, DesktopContext}; pub use wry; pub use wry::application as tao; diff --git a/packages/web/src/lib.rs b/packages/web/src/lib.rs index 1ec9fe068..da3955d1b 100644 --- a/packages/web/src/lib.rs +++ b/packages/web/src/lib.rs @@ -57,12 +57,12 @@ use std::rc::Rc; pub use crate::cfg::WebConfig; +pub use crate::util::use_eval; use dioxus::SchedulerMsg; use dioxus::VirtualDom; pub use dioxus_core as dioxus; use dioxus_core::prelude::Component; use futures_util::FutureExt; -pub use crate::util::use_eval; mod cache; mod cfg; diff --git a/src/lib.rs b/src/lib.rs index 2e50d9750..748772aa4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -40,10 +40,10 @@ pub mod events { } pub mod prelude { + pub use crate::hooks::*; pub use dioxus_core::prelude::*; pub use dioxus_core_macro::{format_args_f, inline_props, rsx, Props}; pub use dioxus_elements::{GlobalAttributes, SvgAttributes}; - pub use crate::hooks::*; pub use dioxus_html as dioxus_elements; #[cfg(feature = "router")] From e57c0890ec31312f207db60da9fdbbf1b6855e77 Mon Sep 17 00:00:00 2001 From: Kris Warner Date: Fri, 25 Mar 2022 22:26:42 -0400 Subject: [PATCH 10/23] Delete redundant sentence --- docs/guide/src/interactivity/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guide/src/interactivity/index.md b/docs/guide/src/interactivity/index.md index b6b9abc88..50f26e545 100644 --- a/docs/guide/src/interactivity/index.md +++ b/docs/guide/src/interactivity/index.md @@ -157,7 +157,7 @@ fn App(cx: Scope)-> Element { } ``` -Using asynchronous code can be difficult! This is just scratching the surface of what's possible. We have an entire chapter on using async properly in your Dioxus Apps. We have an entire section dedicated to using `async` properly later in this book. +Using asynchronous code can be difficult! This is just scratching the surface of what's possible. We have an entire chapter on using async properly in your Dioxus Apps. ### How do I tell Dioxus that my state changed? From 2ffd90c98bdffabdf195d97526eed6df25862e6c Mon Sep 17 00:00:00 2001 From: Kris Warner Date: Fri, 25 Mar 2022 22:32:33 -0400 Subject: [PATCH 11/23] Remove bullets from numbered list --- docs/guide/src/interactivity/index.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/guide/src/interactivity/index.md b/docs/guide/src/interactivity/index.md index 50f26e545..02c39b5b1 100644 --- a/docs/guide/src/interactivity/index.md +++ b/docs/guide/src/interactivity/index.md @@ -173,9 +173,9 @@ With these building blocks, we can craft new hooks similar to `use_state` that l In general, Dioxus should be plenty fast for most use cases. However, there are some rules you should consider following to ensure your apps are quick. -- 1) **Don't call set_state _while rendering_**. This will cause Dioxus to unnecessarily re-check the component for updates or enter an infinite loop. -- 2) **Break your state apart into smaller sections.** Hooks are explicitly designed to "unshackle" your state from the typical model-view-controller paradigm, making it easy to reuse useful bits of code with a single function. -- 3) **Move local state down**. Dioxus will need to re-check child components of your app if the root component is constantly being updated. You'll get best results if rapidly-changing state does not cause major re-renders. +1) **Don't call set_state _while rendering_**. This will cause Dioxus to unnecessarily re-check the component for updates or enter an infinite loop. +2) **Break your state apart into smaller sections.** Hooks are explicitly designed to "unshackle" your state from the typical model-view-controller paradigm, making it easy to reuse useful bits of code with a single function. +3) **Move local state down**. Dioxus will need to re-check child components of your app if the root component is constantly being updated. You'll get best results if rapidly-changing state does not cause major re-renders. - event handlers as string attributes - `value`, `checked`, and `selected` @@ -31,7 +30,7 @@ fn BlogPost(cx: Scope) -> Element { ``` > Note! This attribute is called "dangerous_inner_html" because it is **dangerous** to pass it data you don't trust. If you're not careful, you can easily expose cross-site-scripting (XSS) attacks to your users. -> +> > If you're handling untrusted input, make sure to sanitize your HTML before passing it into `dangerous_inner_html` – or just pass it to a Text Element to escape any HTML tags. @@ -49,9 +48,9 @@ rsx!{ } } ``` -wouldn't actually render the `hidden` attribute: +wouldn't actually render the `hidden` attribute: ```html -
hello
+
hello
``` Not all attributes work like this however. *Only the following attributes* have this behavior: @@ -87,9 +86,9 @@ For any other attributes, a value of `"false"` will be sent directly to the DOM. ## Stopping form input and navigation with `prevent_default` -Currently, calling `prevent_default` on events in EventHandlers is not possible from Desktop/Mobile. Until this is supported, it's possible to prevent default using the `prevent_default` attribute. +Currently, calling `prevent_default` on events in EventHandlers is not possible from Desktop/Mobile. Until this is supported, it's possible to prevent default using the `prevent_default` attribute. -> Note: you cannot conditionally prevent default with this approach. This is a limitation until synchronous event handling is available across the Webview boundary +> Note: you cannot conditionally prevent default with this approach. This is a limitation until synchronous event handling is available across the Webview boundary To use `prevent_default`, simply attach the `prevent_default` attribute to a given element and set it to the name of the event handler you want to prevent default on. We can attach this attribute multiple times for multiple attributes. @@ -104,7 +103,7 @@ rsx!{ } } ``` - diff --git a/docs/guide/src/elements/vnodes.md b/docs/guide/src/elements/vnodes.md index 95b979c7c..5312306f0 100644 --- a/docs/guide/src/elements/vnodes.md +++ b/docs/guide/src/elements/vnodes.md @@ -8,6 +8,7 @@ In this chapter, we'll cover: - Element properties ## Declaring our first Element + Because Dioxus is mostly used with HTML/CSS renderers, the default Element "collection" is HTML. Provided the `html` feature is not disabled, we can declare Elements using the `rsx!` macro: ```rust @@ -29,6 +30,7 @@ Produces: ``` We can construct any valid HTML tag with the `tag {}` pattern and expect the resulting HTML structure to resemble our declaration. + ## Composing Elements Of course, we need more complex structures to make our apps actually useful! Just like HTML, the `rsx!` macro lets us nest Elements inside of each other. @@ -150,7 +152,7 @@ All element attributes must occur *before* child elements. The `rsx!` macro will Listeners are a special type of Attribute that only accept functions. Listeners let us attach functionality to our Elements by running a provided closure whenever the specified Listener is triggered. -We'll cover listeners in more depth in the Adding Interactivity chapter, but for now, just know that every listener must start with the `on` keyword and accepts closures. +We'll cover listeners in more depth in the Adding Interactivity chapter, but for now, just know that every listener starts with "on" and accepts closures. ```rust rsx!( diff --git a/docs/guide/src/hello_world.md b/docs/guide/src/hello_world.md index 6bcc77e30..d01122959 100644 --- a/docs/guide/src/hello_world.md +++ b/docs/guide/src/hello_world.md @@ -115,13 +115,13 @@ At this point, you could call `cargo run` and be greeted with a simple `Hello, W ### Dissecting our example -The `use` statement at the top of our app imports everything from the the `prelude` module. `use`-ing the prelude imports the right traits, types, and macros needed for working with Dioxus. +The `use` statement at the top of our app imports everything from the `prelude` module. `use`-ing the prelude imports the right traits, types, and macros needed for working with Dioxus. ```rust use dioxus::prelude::*; ``` -This initialization code launches a Tokio runtime on a helper thread where your code will run. Then, the WebView renderer will be launched on the main-thread. Due to platform requirements, the main thread is blocked by your app's event loop. +This initialization code launches a Tokio runtime on a helper thread where your code will run. Then, the WebView renderer will be launched on the main thread. Due to platform requirements, the main thread is blocked by your app's event loop. ```rust fn main() { diff --git a/docs/guide/src/interactivity/event_handlers.md b/docs/guide/src/interactivity/event_handlers.md index ee6a7675f..89139adb4 100644 --- a/docs/guide/src/interactivity/event_handlers.md +++ b/docs/guide/src/interactivity/event_handlers.md @@ -1,6 +1,6 @@ # Event handlers -To make our boring UIs less static and more interesting, we want to add the ability to interact to user input. To do this, we need to add some event handlers. +To make our boring UIs less static and more interesting, we want to add the ability to interact with user input. To do this, we need to add some event handlers. ## The most basic events: clicks @@ -82,7 +82,7 @@ div { } ``` -In this particular layout, a click on the inner div is transitively also a click on the outer div. If we didn't want the outer div to be triggered every time we trigger the inner div, then we'd want to call "cancel_bubble". +In this particular layout, a click on the inner div is transitively also a click on the outer div. If we didn't want the outer div to be triggered every time we trigger the inner div, then we'd want to call `cancel_bubble()`. This will prevent any listeners above the current listener from being triggered. @@ -102,7 +102,7 @@ div { ## Prevent Default -With HTML based renderers, the browser will automatically perform some action. For text inputs, this would be entering the provided key. For forms, this might involve navigating the page. +With HTML-based renderers, the browser will automatically perform some action. For text inputs, this would be entering the provided key. For forms, this might involve navigating the page. In some instances, you don't want this default behavior. In these cases, instead of handling the event directly, you'd want to prevent any default handlers. diff --git a/docs/guide/src/interactivity/user_input.md b/docs/guide/src/interactivity/user_input.md index 958e58553..e878afbbe 100644 --- a/docs/guide/src/interactivity/user_input.md +++ b/docs/guide/src/interactivity/user_input.md @@ -60,14 +60,13 @@ cx.render(rsx!{ ## Uncontrolled Inputs -When working with large sets of inputs, you might be quickly tired of creating `use_state` for each value. Additionally, the pattern of one use_state per interaction might deteriorate when you need to have a flexible number of inputs. In these cases, we use "uncontrolled" inputs. Here, we don't drive the value of the input from the use_state, choosing to leave it in an "uncontrolled" state. +When working with large sets of inputs, you might be quickly tired of creating `use_state` for each value. Additionally, the pattern of one `use_state` per interaction might deteriorate when you need to have a flexible number of inputs. In these cases, we use "uncontrolled" inputs. Here, we don't drive the value of the input from the `use_state`, choosing to leave it in an "uncontrolled" state. -This approach can be more performant, more flexible, but more prone to UI inconsistencies than its controlled counterpart. +This approach can be more performant and more flexible, but more prone to UI inconsistencies than its controlled counterpart. To use the "uncontrolled" pattern, we simply omit setting the value of the input. Instead, we can react to the change directly on the input itself, or from a form element higher up in the tree. - -For this example, we don't attach any `use_state` handles into the labels. Instead, we simply attach an "oninput" handler to the form element. This will run each time any of the child inputs change, allowing us to perform tasks like form validation. +For this example, we don't attach any `use_state` handles into the labels. Instead, we simply attach an `oninput` handler to the form element. This will run each time any of the child inputs change, allowing us to perform tasks like form validation. ```rust form { @@ -81,4 +80,3 @@ form { input { name: "date", } } ``` - diff --git a/docs/guide/src/interactivity/usestate.md b/docs/guide/src/interactivity/usestate.md index 256ac94a3..64aa81d59 100644 --- a/docs/guide/src/interactivity/usestate.md +++ b/docs/guide/src/interactivity/usestate.md @@ -3,7 +3,7 @@ The first fundamental hook for state management is `use_state`. This particular hook is designed to work well with the entire Dioxus ecosystem including futures, children, and memoization. -## Basic usage. +## Basic usage The simplest use case of `use_state` is a simple counter. The handle returned by `use_state` implements `Add` and `AddAssign`. Writing through `AddAssign` will automatically mark the component as dirty, forcing an update. diff --git a/docs/guide/src/state/index.md b/docs/guide/src/state/index.md index 5dc86539f..ac025aff1 100644 --- a/docs/guide/src/state/index.md +++ b/docs/guide/src/state/index.md @@ -1,6 +1,6 @@ # Managing State -Every app you'll build with Dioxus will have some sort of state that needs to be maintained and updated as your users interact with it. However, managing state can be particular challenging at times, and is frequently the source of bugs in many GUI frameworks. +Every app you'll build with Dioxus will have some sort of state that needs to be maintained and updated as your users interact with it. However, managing state can be particularly challenging at times, and is frequently the source of bugs in many GUI frameworks. In this chapter, we'll cover the various ways to manage state, the appropriate terminology, various patterns, and some problems you might run into. @@ -11,7 +11,7 @@ Why do people say state management is so difficult? What does it mean? Generally, state management is the code you need to write to ensure that your app renders the *correct* content. If the user inputs a name, then you need to display the appropriate response - like alerts, validation, and disable/enable various elements on the page. Things can quickly become tricky if you need loading screens and cancellable tasks. -For the simplest of apps, all your state can enter the app from the root props. This is common in server-side rendering - we can collect all of the required state *before* rendering the content. +For the simplest of apps, all of your state can enter the app from the root props. This is common in server-side rendering - we can collect all of the required state *before* rendering the content. ```rust let all_content = get_all_content().await; @@ -36,4 +36,3 @@ To deal with complexity, you have a couple of options: - Lift state upwards to be spread across multiple components (fan out). - Use the Context API to share state globally. - Use a dedicated state management solution like Fermi. - From fe3577faaf97e3da7e29ec2b01b14c1f8fa0d56f Mon Sep 17 00:00:00 2001 From: YuKun Liu <41265098+mrxiaozhuox@users.noreply.github.com> Date: Mon, 28 Mar 2022 10:42:09 +0800 Subject: [PATCH 20/23] fix: `usestate` docs --- packages/hooks/src/usestate.rs | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/packages/hooks/src/usestate.rs b/packages/hooks/src/usestate.rs index 68612aca1..60dc172b3 100644 --- a/packages/hooks/src/usestate.rs +++ b/packages/hooks/src/usestate.rs @@ -92,9 +92,9 @@ impl UseState { /// /// ```rust, ignore /// fn component(cx: Scope) -> Element { - /// let (count, set_count) = use_state(&cx, || 0); + /// let count = use_state(&cx, || 0); /// cx.spawn({ - /// let set_count = set_count.to_owned(); + /// let set_count = count.to_owned(); /// async move { /// let current = set_count.current(); /// } @@ -119,11 +119,11 @@ impl UseState { /// /// ```rust, ignore /// fn component(cx: Scope) -> Element { - /// let (value, set_value) = use_state(&cx, || 0); + /// let value = use_state(&cx, || 0); /// /// rsx!{ /// Component { - /// handler: set_val.setter() + /// handler: value.setter() /// } /// } /// } @@ -144,16 +144,16 @@ impl UseState { /// # use dioxus_core::prelude::*; /// # use dioxus_hooks::*; /// fn component(cx: Scope) -> Element { - /// let (value, set_value) = use_state(&cx, || 0); + /// let value = use_state(&cx, || 0); /// /// // to increment the value - /// set_value.modify(|v| v + 1); + /// value.modify(|v| v + 1); /// /// // usage in async /// cx.spawn({ - /// let set_value = set_value.to_owned(); + /// let value = value.to_owned(); /// async move { - /// set_value.modify(|v| v + 1); + /// value.modify(|v| v + 1); /// } /// }); /// @@ -185,9 +185,9 @@ impl UseState { /// # use dioxus_core::prelude::*; /// # use dioxus_hooks::*; /// fn component(cx: Scope) -> Element { - /// let (value, set_value) = use_state(&cx, || 0); + /// let value = use_state(&cx, || 0); /// - /// let as_rc = set_value.get(); + /// let as_rc = value.get(); /// assert_eq!(as_rc.as_ref(), &0); /// /// # todo!() @@ -207,12 +207,12 @@ impl UseState { /// /// ```rust, ignore /// fn component(cx: Scope) -> Element { - /// let (count, set_count) = use_state(&cx, || 0); + /// let count = use_state(&cx, || 0); /// cx.spawn({ - /// let set_count = set_count.to_owned(); + /// let count = count.to_owned(); /// async move { /// // for the component to re-render - /// set_count.needs_update(); + /// count.needs_update(); /// } /// }) /// } @@ -237,9 +237,9 @@ impl UseState { /// # Examples /// /// ``` - /// let (val, set_val) = use_state(&cx, || 0); + /// let val = use_state(&cx, || 0); /// - /// set_val.with_mut(|v| *v = 1); + /// val.with_mut(|v| *v = 1); /// ``` pub fn with_mut(&self, apply: impl FnOnce(&mut T)) { let mut slot = self.slot.borrow_mut(); @@ -269,9 +269,9 @@ impl UseState { /// # Examples /// /// ``` - /// let (val, set_val) = use_state(&cx, || 0); + /// let val = use_state(&cx, || 0); /// - /// *set_val.make_mut() += 1; + /// *val.make_mut() += 1; /// ``` #[must_use] pub fn make_mut(&self) -> RefMut { From 8e1e06d8c3648994d4c77d1174241c95901a597e Mon Sep 17 00:00:00 2001 From: YuKun Liu Date: Mon, 28 Mar 2022 13:11:33 +0800 Subject: [PATCH 21/23] fix: docs --- docs/guide/src/interactivity/useref.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/guide/src/interactivity/useref.md b/docs/guide/src/interactivity/useref.md index 15b85dcf5..e8eda8e83 100644 --- a/docs/guide/src/interactivity/useref.md +++ b/docs/guide/src/interactivity/useref.md @@ -60,9 +60,9 @@ The best use case of `use_ref` is to manage "complex" data local to your compone ```rust -let val = use_state(&cx, || vec![1, 3, 3, 7]); -let val = use_state(&cx, || (0..10000).collect::x>()); -let val = use_state(&cx, || Configuration { +let val = use_ref(&cx, || vec![1, 3, 3, 7]); +let val = use_ref(&cx, || (0..10000).collect::x>()); +let val = use_ref(&cx, || Configuration { a: "asdasd", // .. more complex fields }); @@ -82,6 +82,7 @@ fn app(cx: Scope) -> Element { }) } +#[inline_props] fn Child(cx: Scope, val: UseRef) -> Element { // ... } From 93405c2a409d09d8ecbfc58997483ee5430e0443 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 4 Apr 2022 12:18:51 -0400 Subject: [PATCH 22/23] Fix: diffing should poproots too --- packages/core/src/diff.rs | 1 + packages/core/src/mutations.rs | 8 ++++++++ tests/diffing.rs | 1 + 3 files changed, 10 insertions(+) diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index a0be7fd3c..3d8b9082d 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -426,6 +426,7 @@ impl<'b> DiffState<'b> { self.mutations.push_root(root); let created = self.create_children(new.children); self.mutations.append_children(created as u32); + self.mutations.pop_root(); } (_, _) => self.diff_children(old.children, new.children), }; diff --git a/packages/core/src/mutations.rs b/packages/core/src/mutations.rs index 2d3a4505f..2d3115ce3 100644 --- a/packages/core/src/mutations.rs +++ b/packages/core/src/mutations.rs @@ -185,6 +185,9 @@ pub enum DomEdit<'bump> { /// The namespace of the attribute. ns: Option<&'bump str>, }, + + /// Manually pop a root node from the stack. + PopRoot {}, } use fxhash::FxHashSet; @@ -205,6 +208,11 @@ impl<'a> Mutations<'a> { self.edits.push(PushRoot { root: id }); } + // Navigation + pub(crate) fn pop_root(&mut self) { + self.edits.push(PopRoot {}); + } + pub(crate) fn replace_with(&mut self, root: ElementId, m: u32) { let root = root.as_u64(); self.edits.push(ReplaceWith { m, root }); diff --git a/tests/diffing.rs b/tests/diffing.rs index b8d5b3273..a876ffc03 100644 --- a/tests/diffing.rs +++ b/tests/diffing.rs @@ -752,6 +752,7 @@ fn add_nested_elements() { PushRoot { root: 1 }, CreateElement { root: 2, tag: "div" }, AppendChildren { many: 1 }, + PopRoot {}, ] ); } From dfc69c0905b68d546cc5c32086ec1d7e4653edce Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 4 Apr 2022 12:29:00 -0400 Subject: [PATCH 23/23] fix: compile fail for web --- packages/interpreter/src/bindings.rs | 3 +++ packages/interpreter/src/interpreter.js | 3 +++ packages/web/src/dom.rs | 1 + 3 files changed, 7 insertions(+) diff --git a/packages/interpreter/src/bindings.rs b/packages/interpreter/src/bindings.rs index 3f0dc5ce1..a08ef9c13 100644 --- a/packages/interpreter/src/bindings.rs +++ b/packages/interpreter/src/bindings.rs @@ -17,6 +17,9 @@ extern "C" { #[wasm_bindgen(method)] pub fn PushRoot(this: &Interpreter, root: u64); + #[wasm_bindgen(method)] + pub fn PopRoot(this: &Interpreter); + #[wasm_bindgen(method)] pub fn AppendChildren(this: &Interpreter, many: u32); diff --git a/packages/interpreter/src/interpreter.js b/packages/interpreter/src/interpreter.js index 273b0a9df..4725fff31 100644 --- a/packages/interpreter/src/interpreter.js +++ b/packages/interpreter/src/interpreter.js @@ -27,6 +27,9 @@ export class Interpreter { const node = this.nodes[root]; this.stack.push(node); } + PopRoot() { + this.stack.pop(); + } AppendChildren(many) { let root = this.stack[this.stack.length - (1 + many)]; let to_add = this.stack.splice(this.stack.length - many); diff --git a/packages/web/src/dom.rs b/packages/web/src/dom.rs index c2bf38df0..390c7800c 100644 --- a/packages/web/src/dom.rs +++ b/packages/web/src/dom.rs @@ -105,6 +105,7 @@ impl WebsysDom { for edit in edits.drain(..) { match edit { DomEdit::PushRoot { root } => self.interpreter.PushRoot(root), + DomEdit::PopRoot {} => self.interpreter.PopRoot(), DomEdit::AppendChildren { many } => self.interpreter.AppendChildren(many), DomEdit::ReplaceWith { root, m } => self.interpreter.ReplaceWith(root, m), DomEdit::InsertAfter { root, n } => self.interpreter.InsertAfter(root, n),