From 2bbc609082225012a017b3c05477bb35e6258b24 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 12 Mar 2024 16:50:03 -0500 Subject: [PATCH 1/5] add a way to subscribe to a dependency tuple for the use_resource hook --- Cargo.lock | 61 +++++++++++++----------------- packages/hooks/src/dependency.rs | 10 ++--- packages/hooks/src/use_resource.rs | 42 +++++++++++++++++++- 3 files changed, 73 insertions(+), 40 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6a6ced99a..d701b0c26 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2201,7 +2201,7 @@ version = "0.5.0-alpha.0" dependencies = [ "criterion 0.3.6", "dioxus-config-macro", - "dioxus-core 0.5.0-alpha.0", + "dioxus-core", "dioxus-core-macro", "dioxus-desktop", "dioxus-fullstack", @@ -2268,7 +2268,7 @@ dependencies = [ "dioxus-autofmt", "dioxus-check", "dioxus-cli-config", - "dioxus-core 0.5.0-alpha.0", + "dioxus-core", "dioxus-hot-reload", "dioxus-html", "dioxus-rsx", @@ -2356,20 +2356,6 @@ dependencies = [ "tracing-subscriber", ] -[[package]] -name = "dioxus-core" -version = "0.5.0-alpha.0" -source = "git+https://github.com/DioxusLabs/dioxus#44d09fcd2de4af32c8a3a107cabdd3e6860baeeb" -dependencies = [ - "futures-channel", - "futures-util", - "longest-increasing-subsequence", - "rustc-hash", - "slab", - "tracing", - "tracing-subscriber", -] - [[package]] name = "dioxus-core-macro" version = "0.5.0-alpha.0" @@ -2400,7 +2386,7 @@ dependencies = [ "core-foundation", "dioxus", "dioxus-cli-config", - "dioxus-core 0.5.0-alpha.0", + "dioxus-core", "dioxus-hooks", "dioxus-hot-reload", "dioxus-html", @@ -2501,7 +2487,7 @@ name = "dioxus-hooks" version = "0.5.0-alpha.0" dependencies = [ "dioxus", - "dioxus-core 0.5.0-alpha.0", + "dioxus-core", "dioxus-debug-cell", "dioxus-signals", "futures-channel", @@ -2519,7 +2505,7 @@ name = "dioxus-hot-reload" version = "0.5.0-alpha.0" dependencies = [ "chrono", - "dioxus-core 0.5.0-alpha.0", + "dioxus-core", "dioxus-html", "dioxus-rsx", "execute", @@ -2536,7 +2522,7 @@ name = "dioxus-html" version = "0.5.0-alpha.0" dependencies = [ "async-trait", - "dioxus-core 0.5.0-alpha.0", + "dioxus-core", "dioxus-html-internal-macro", "dioxus-rsx", "enumset", @@ -2569,7 +2555,7 @@ dependencies = [ name = "dioxus-interpreter-js" version = "0.5.0-alpha.0" dependencies = [ - "dioxus-core 0.5.0-alpha.0", + "dioxus-core", "dioxus-html", "js-sys", "md5", @@ -2585,7 +2571,7 @@ name = "dioxus-lib" version = "0.5.0-alpha.0" dependencies = [ "dioxus-config-macro", - "dioxus-core 0.5.0-alpha.0", + "dioxus-core", "dioxus-core-macro", "dioxus-hooks", "dioxus-html", @@ -2600,7 +2586,7 @@ dependencies = [ "axum", "dioxus", "dioxus-cli-config", - "dioxus-core 0.5.0-alpha.0", + "dioxus-core", "dioxus-hot-reload", "dioxus-html", "dioxus-interpreter-js", @@ -2634,7 +2620,7 @@ dependencies = [ "anymap 1.0.0-beta.2", "dashmap", "dioxus", - "dioxus-core 0.5.0-alpha.0", + "dioxus-core", "dioxus-native-core", "dioxus-native-core-macro", "keyboard-types", @@ -2743,7 +2729,7 @@ dependencies = [ name = "dioxus-rsx" version = "0.5.0-alpha.0" dependencies = [ - "dioxus-core 0.5.0-alpha.0", + "dioxus-core", "internment", "krates", "proc-macro2", @@ -2758,7 +2744,7 @@ name = "dioxus-signals" version = "0.5.0-alpha.0" dependencies = [ "dioxus", - "dioxus-core 0.5.0-alpha.0", + "dioxus-core", "futures-channel", "futures-util", "generational-box", @@ -2782,7 +2768,7 @@ dependencies = [ "async-trait", "chrono", "dioxus", - "dioxus-core 0.5.0-alpha.0", + "dioxus-core", "dioxus-html", "dioxus-signals", "fern", @@ -2813,7 +2799,7 @@ dependencies = [ "criterion 0.3.6", "crossterm 0.26.1", "dioxus", - "dioxus-core 0.5.0-alpha.0", + "dioxus-core", "dioxus-hot-reload", "dioxus-html", "dioxus-native-core", @@ -2831,7 +2817,7 @@ dependencies = [ "async-trait", "console_error_panic_hook", "dioxus", - "dioxus-core 0.5.0-alpha.0", + "dioxus-core", "dioxus-html", "dioxus-interpreter-js", "dioxus-ssr", @@ -5714,15 +5700,18 @@ dependencies = [ [[package]] name = "manganis" -version = "0.1.1" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c10916db4ed51967d92545eb5629ce1e1cc05c74da2cd2aab6ab6c57c2e838b6" dependencies = [ - "dioxus-core 0.5.0-alpha.0 (git+https://github.com/DioxusLabs/dioxus)", "manganis-macro", ] [[package]] name = "manganis-cli-support" -version = "0.1.1" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "081bb598e2046ee191f90f454f772f83d4327b665e6fcb090ea3ed808712ba55" dependencies = [ "anyhow", "cargo-lock 9.0.0", @@ -5747,7 +5736,9 @@ dependencies = [ [[package]] name = "manganis-common" -version = "0.1.1" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82ee0f9002a9f5d7e3b662e55562996c60401bae16da23f3790c52e0823216ea" dependencies = [ "anyhow", "base64", @@ -5761,7 +5752,9 @@ dependencies = [ [[package]] name = "manganis-macro" -version = "0.1.1" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da50cc6074480dc6c255a09c9d48dd4a379e6162feb5d308ec33d5ba9da4e579" dependencies = [ "manganis-common", "proc-macro2", diff --git a/packages/hooks/src/dependency.rs b/packages/hooks/src/dependency.rs index d6ab65a5f..a00e29070 100644 --- a/packages/hooks/src/dependency.rs +++ b/packages/hooks/src/dependency.rs @@ -1,7 +1,7 @@ /// A dependency is a trait that can be used to determine if a effect or selector should be re-run. pub trait Dependency: Sized + Clone { /// The output of the dependency - type Out: Clone + PartialEq; + type Out: Clone + PartialEq + 'static; /// Returns the output of the dependency. fn out(&self) -> Self::Out; /// Returns true if the dependency has changed. @@ -16,10 +16,10 @@ impl Dependency for () { } /// A dependency is a trait that can be used to determine if a effect or selector should be re-run. -pub trait Dep: 'static + PartialEq + Clone {} -impl Dep for T where T: 'static + PartialEq + Clone {} +pub trait DependencyElement: 'static + PartialEq + Clone {} +impl DependencyElement for T where T: 'static + PartialEq + Clone {} -impl Dependency for &A { +impl Dependency for &A { type Out = A; fn out(&self) -> Self::Out { (*self).clone() @@ -33,7 +33,7 @@ macro_rules! impl_dep { impl< $($el),* > Dependency for ($(&$el,)*) where $( - $el: Dep + $el: DependencyElement ),* { type Out = ($($el,)*); diff --git a/packages/hooks/src/use_resource.rs b/packages/hooks/src/use_resource.rs index a4157fbba..1c4b169ac 100644 --- a/packages/hooks/src/use_resource.rs +++ b/packages/hooks/src/use_resource.rs @@ -1,6 +1,6 @@ #![allow(missing_docs)] -use crate::{use_callback, use_signal, UseCallback}; +use crate::{dependency, use_callback, use_signal, UseCallback}; use dioxus_core::prelude::*; use dioxus_core::{ prelude::{spawn, use_hook}, @@ -125,6 +125,46 @@ pub enum UseResourceState { } impl Resource { + /// Adds an explicit dependency to the resource. If the dependency changes, the resource's future will rerun. + /// + /// Signals will automatically be added as dependencies, so you don't need to call this method for them. + /// + /// NOTE: You must follow the rules of hooks when calling this method. + /// + /// ```rust + /// # use dioxus::prelude::*; + /// # async fn sleep(delay: u32) {} + /// + /// #[component] + /// fn Comp(delay: u32) -> Element { + /// // Because the resource subscribes to `delay` by adding it as a dependency, the resource's future will rerun every time `delay` changes. + /// let current_weather = use_resource(move || async move { + /// sleep(delay).await; + /// "Sunny" + /// }) + /// .use_dependencies((&delay,)); + /// + /// rsx! { + /// // the value of the resource can be polled to + /// // conditionally render elements based off if it's future + /// // finished (Some(Ok(_)), errored Some(Err(_)), + /// // or is still running (None) + /// match &*current_weather.read_unchecked() { + /// Some(weather) => rsx! { "{weather}" }, + /// None => rsx! { p { "Loading..." } } + /// } + /// } + /// } + /// ``` + pub fn use_dependencies(self, dependency: impl dependency::Dependency) -> Self { + let mut dependencies_signal = use_signal(|| dependency.out()); + let changed = { dependency.changed(&*dependencies_signal.read()) }; + if changed { + dependencies_signal.set(dependency.out()); + } + self + } + /// Restart the resource's future. /// /// Will not cancel the previous future, but will ignore any values that it From 0951a389f7831572e4444a1d9e93b98ae7eaf486 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 13 Mar 2024 09:54:48 -0500 Subject: [PATCH 2/5] add a way to add manual dependency tuple to memos and effects --- packages/hooks/src/lib.rs | 2 - packages/hooks/src/use_effect.rs | 64 ++++++++++- packages/hooks/src/use_memo.rs | 101 +----------------- packages/hooks/src/use_resource.rs | 5 +- packages/{hooks => signals}/src/dependency.rs | 0 packages/signals/src/lib.rs | 3 + packages/signals/src/memo.rs | 36 ++++++- 7 files changed, 106 insertions(+), 105 deletions(-) rename packages/{hooks => signals}/src/dependency.rs (100%) diff --git a/packages/hooks/src/lib.rs b/packages/hooks/src/lib.rs index a179e22ee..6717254fc 100644 --- a/packages/hooks/src/lib.rs +++ b/packages/hooks/src/lib.rs @@ -53,8 +53,6 @@ macro_rules! to_owned { $(to_owned![$($rest)*])? }; } -mod dependency; -pub use dependency::*; mod use_callback; pub use use_callback::*; diff --git a/packages/hooks/src/use_effect.rs b/packages/hooks/src/use_effect.rs index 069b58e3d..0bec415bc 100644 --- a/packages/hooks/src/use_effect.rs +++ b/packages/hooks/src/use_effect.rs @@ -2,6 +2,9 @@ use dioxus_core::prelude::*; use dioxus_signals::ReactiveContext; use futures_util::StreamExt; +use crate::use_signal; +use dioxus_signals::*; + /// `use_effect` will subscribe to any changes in the signal values it captures /// effects will always run after first mount and then whenever the signal values change /// If the use_effect call was skipped due to an early return, the effect will no longer activate. @@ -19,15 +22,15 @@ use futures_util::StreamExt; /// } /// ``` #[track_caller] -pub fn use_effect(mut callback: impl FnMut() + 'static) { +pub fn use_effect(mut callback: impl FnMut() + 'static) -> Effect { // let mut run_effect = use_hook(|| CopyValue::new(true)); // use_hook_did_run(move |did_run| run_effect.set(did_run)); let location = std::panic::Location::caller(); use_hook(|| { + let (rc, mut changed) = ReactiveContext::new_with_origin(location); spawn(async move { - let (rc, mut changed) = ReactiveContext::new_with_origin(location); loop { // Run the effect rc.run_in(&mut callback); @@ -39,5 +42,60 @@ pub fn use_effect(mut callback: impl FnMut() + 'static) { wait_for_next_render().await; } }); - }); + Effect { rc } + }) +} + +/// A handle to an effect. +#[derive(Clone, Copy)] +pub struct Effect { + rc: ReactiveContext, +} + +impl Effect { + /// Adds an explicit dependency to the effect. If the dependency changes, the effect's closure will rerun. + /// + /// Signals will automatically be added as dependencies, so you don't need to call this method for them. + /// + /// NOTE: You must follow the rules of hooks when calling this method. + /// + /// ```rust + /// # use dioxus::prelude::*; + /// # async fn sleep(delay: u32) {} + /// + /// #[component] + /// fn Comp(delay: u32) -> Element { + /// // Because the resource subscribes to `delay` by adding it as a dependency, the effect's closure will rerun every time `delay` changes. + /// let current_weather = use_resource(move || async move { + /// sleep(delay).await; + /// "Sunny" + /// }) + /// .use_dependencies((&delay,)); + /// + /// rsx! { + /// // the value of the resource can be polled to + /// // conditionally render elements based off if it's future + /// // finished (Some(Ok(_)), errored Some(Err(_)), + /// // or is still running (None) + /// match &*current_weather.read_unchecked() { + /// Some(weather) => rsx! { "{weather}" }, + /// None => rsx! { p { "Loading..." } } + /// } + /// } + /// } + /// ``` + pub fn use_dependencies(mut self, dependency: impl Dependency) -> Self { + let mut dependencies_signal = use_signal(|| dependency.out()); + let changed = { dependency.changed(&*dependencies_signal.read()) }; + if changed { + dependencies_signal.set(dependency.out()); + self.mark_dirty(); + } + self + } + + /// Marks the effect as dirty, causing it to rerun on the next render. + pub fn mark_dirty(&mut self) { + self.rc.mark_dirty(); + } } diff --git a/packages/hooks/src/use_memo.rs b/packages/hooks/src/use_memo.rs index 2f423e07a..57c0eb30f 100644 --- a/packages/hooks/src/use_memo.rs +++ b/packages/hooks/src/use_memo.rs @@ -1,14 +1,10 @@ -use crate::dependency::Dependency; -use crate::{use_callback, use_signal}; +use crate::use_callback; use dioxus_core::prelude::*; -use dioxus_signals::Memo; -use dioxus_signals::{ReactiveContext, ReadOnlySignal, Readable, Signal, SignalData}; -use dioxus_signals::{Storage, Writable}; -use futures_util::StreamExt; +use dioxus_signals::{Memo, Signal}; -/// Creates a new unsync Selector. The selector will be run immediately and whenever any signal it reads changes. +/// Creates a new Memo. The memo will be run immediately and whenever any signal it reads changes. /// -/// Selectors can be used to efficiently compute derived data from signals. +/// Memos can be used to efficiently compute derived data from signals. /// /// ```rust /// use dioxus::prelude::*; @@ -29,92 +25,3 @@ pub fn use_memo(f: impl FnMut() -> R + 'static) -> Memo { #[allow(clippy::redundant_closure)] use_hook(|| Signal::memo(move || callback())) } - -/// Creates a new unsync Selector with some local dependencies. The selector will be run immediately and whenever any signal it reads or any dependencies it tracks changes -/// -/// Selectors can be used to efficiently compute derived data from signals. -/// -/// ```rust -/// use dioxus::prelude::*; -/// -/// fn App() -> Element { -/// let mut local_state = use_signal(|| 0); -/// let double = use_memo_with_dependencies((&local_state(),), move |(local_state,)| local_state * 2); -/// local_state.set(1); -/// -/// rsx! { "{double}" } -/// } -/// ``` -#[track_caller] -pub fn use_memo_with_dependencies( - dependencies: D, - f: impl FnMut(D::Out) -> R + 'static, -) -> ReadOnlySignal -where - D::Out: 'static, -{ - use_maybe_sync_memo_with_dependencies(dependencies, f) -} - -/// Creates a new Selector that may be sync with some local dependencies. The selector will be run immediately and whenever any signal it reads or any dependencies it tracks changes -/// -/// Selectors can be used to efficiently compute derived data from signals. -/// -/// ```rust -/// use dioxus::prelude::*; -/// use dioxus_signals::*; -/// -/// fn App() -> Element { -/// let mut local_state = use_signal(|| 0i32); -/// let double: ReadOnlySignal = use_maybe_sync_memo_with_dependencies((&local_state(),), move |(local_state,)| local_state * 2); -/// local_state.set(1); -/// -/// rsx! { "{double}" } -/// } -/// ``` -#[track_caller] -pub fn use_maybe_sync_memo_with_dependencies< - R: PartialEq, - D: Dependency, - S: Storage>, ->( - dependencies: D, - mut f: impl FnMut(D::Out) -> R + 'static, -) -> ReadOnlySignal -where - D::Out: 'static, -{ - let mut dependencies_signal = use_signal(|| dependencies.out()); - - let selector = use_hook(|| { - // Get the current reactive context - let (rc, mut changed) = ReactiveContext::new(); - - // Create a new signal in that context, wiring up its dependencies and subscribers - let mut state: Signal = - rc.run_in(|| Signal::new_maybe_sync(f(dependencies_signal.read().clone()))); - - spawn(async move { - loop { - // Wait for context to change - let _ = changed.next().await; - - let new = rc.run_in(|| f(dependencies_signal.read().clone())); - if new != *state.peek() { - *state.write() = new; - } - } - }); - - // And just return the readonly variant of that signal - ReadOnlySignal::new_maybe_sync(state) - }); - - // This will cause a re-run of the selector if the dependencies change - let changed = { dependencies.changed(&*dependencies_signal.read()) }; - if changed { - dependencies_signal.set(dependencies.out()); - } - - selector -} diff --git a/packages/hooks/src/use_resource.rs b/packages/hooks/src/use_resource.rs index 1c4b169ac..1276c5398 100644 --- a/packages/hooks/src/use_resource.rs +++ b/packages/hooks/src/use_resource.rs @@ -1,6 +1,6 @@ #![allow(missing_docs)] -use crate::{dependency, use_callback, use_signal, UseCallback}; +use crate::{use_callback, use_signal, UseCallback}; use dioxus_core::prelude::*; use dioxus_core::{ prelude::{spawn, use_hook}, @@ -156,11 +156,12 @@ impl Resource { /// } /// } /// ``` - pub fn use_dependencies(self, dependency: impl dependency::Dependency) -> Self { + pub fn use_dependencies(mut self, dependency: impl Dependency) -> Self { let mut dependencies_signal = use_signal(|| dependency.out()); let changed = { dependency.changed(&*dependencies_signal.read()) }; if changed { dependencies_signal.set(dependency.out()); + self.restart(); } self } diff --git a/packages/hooks/src/dependency.rs b/packages/signals/src/dependency.rs similarity index 100% rename from packages/hooks/src/dependency.rs rename to packages/signals/src/dependency.rs diff --git a/packages/signals/src/lib.rs b/packages/signals/src/lib.rs index 1492fe205..812470b3a 100644 --- a/packages/signals/src/lib.rs +++ b/packages/signals/src/lib.rs @@ -19,6 +19,9 @@ pub use map::*; // mod comparer; // pub use comparer::*; +mod dependency; +pub use dependency::*; + mod memo; pub use memo::*; diff --git a/packages/signals/src/memo.rs b/packages/signals/src/memo.rs index 786b813fe..18e235af8 100644 --- a/packages/signals/src/memo.rs +++ b/packages/signals/src/memo.rs @@ -1,6 +1,6 @@ use crate::write::Writable; use crate::{read::Readable, ReactiveContext, ReadableRef, Signal}; -use crate::{CopyValue, ReadOnlySignal}; +use crate::{CopyValue, Dependency, ReadOnlySignal}; use std::rc::Rc; use std::{ cell::RefCell, @@ -153,6 +153,40 @@ impl Memo { pub fn id(&self) -> generational_box::GenerationalBoxId { self.inner.id() } + + /// Adds an explicit dependency to the memo. If the dependency changes, the memo will rerun. + /// + /// Signals will automatically be added as dependencies, so you don't need to call this method for them. + /// + /// NOTE: You must follow the rules of hooks when calling this method. + /// + /// ```rust + /// # use dioxus::prelude::*; + /// # async fn sleep(delay: u32) {} + /// + /// #[component] + /// fn Comp(count: u32) -> Element { + /// // Because the resource subscribes to `delay` by adding it as a dependency, the memo will rerun every time `count` changes. + /// let new_count = use_memo(move || async move { + /// count + 1 + /// }) + /// .use_dependencies((&count,)); + /// + /// todo!() + /// } + /// ``` + pub fn use_dependencies(self, dependency: impl Dependency) -> Self + where + T: PartialEq, + { + let mut dependencies_signal = use_hook(|| CopyValue::new(dependency.out())); + let changed = { dependency.changed(&*dependencies_signal.read()) }; + if changed { + dependencies_signal.set(dependency.out()); + self.recompute(); + } + self + } } impl Readable for Memo From 3866aa297752df2f1ccd38181c8c580e037906fc Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 13 Mar 2024 10:03:00 -0500 Subject: [PATCH 3/5] simplify use_dependencies docs --- packages/hooks/src/use_effect.rs | 18 ++++-------------- packages/hooks/src/use_resource.rs | 13 ++----------- packages/signals/src/memo.rs | 4 ++-- 3 files changed, 8 insertions(+), 27 deletions(-) diff --git a/packages/hooks/src/use_effect.rs b/packages/hooks/src/use_effect.rs index 0bec415bc..dd7a303cc 100644 --- a/packages/hooks/src/use_effect.rs +++ b/packages/hooks/src/use_effect.rs @@ -65,23 +65,13 @@ impl Effect { /// /// #[component] /// fn Comp(delay: u32) -> Element { - /// // Because the resource subscribes to `delay` by adding it as a dependency, the effect's closure will rerun every time `delay` changes. - /// let current_weather = use_resource(move || async move { - /// sleep(delay).await; - /// "Sunny" + /// // Because the effect subscribes to `delay` by adding it as a dependency, the effect's closure will rerun every time `delay` changes. + /// use_effect(move || { + /// println!("It is safe to manually manipulate the dom here"); /// }) /// .use_dependencies((&delay,)); /// - /// rsx! { - /// // the value of the resource can be polled to - /// // conditionally render elements based off if it's future - /// // finished (Some(Ok(_)), errored Some(Err(_)), - /// // or is still running (None) - /// match &*current_weather.read_unchecked() { - /// Some(weather) => rsx! { "{weather}" }, - /// None => rsx! { p { "Loading..." } } - /// } - /// } + /// todo!() /// } /// ``` pub fn use_dependencies(mut self, dependency: impl Dependency) -> Self { diff --git a/packages/hooks/src/use_resource.rs b/packages/hooks/src/use_resource.rs index 1276c5398..ac60ccb6c 100644 --- a/packages/hooks/src/use_resource.rs +++ b/packages/hooks/src/use_resource.rs @@ -23,7 +23,7 @@ use std::{cell::Cell, future::Future, rc::Rc}; /// }); /// /// // Because the resource's future subscribes to `country` by reading it (`country.read()`), -/// // everytime `country` changes the resource's future will run again and thus provide a new value. +/// // every time `country` changes the resource's future will run again and thus provide a new value. /// let current_weather = use_resource(move || async move { get_weather(&country.read().clone()).await }); /// /// rsx! { @@ -144,16 +144,7 @@ impl Resource { /// }) /// .use_dependencies((&delay,)); /// - /// rsx! { - /// // the value of the resource can be polled to - /// // conditionally render elements based off if it's future - /// // finished (Some(Ok(_)), errored Some(Err(_)), - /// // or is still running (None) - /// match &*current_weather.read_unchecked() { - /// Some(weather) => rsx! { "{weather}" }, - /// None => rsx! { p { "Loading..." } } - /// } - /// } + /// todo!() /// } /// ``` pub fn use_dependencies(mut self, dependency: impl Dependency) -> Self { diff --git a/packages/signals/src/memo.rs b/packages/signals/src/memo.rs index 18e235af8..8d60dd9f0 100644 --- a/packages/signals/src/memo.rs +++ b/packages/signals/src/memo.rs @@ -166,8 +166,8 @@ impl Memo { /// /// #[component] /// fn Comp(count: u32) -> Element { - /// // Because the resource subscribes to `delay` by adding it as a dependency, the memo will rerun every time `count` changes. - /// let new_count = use_memo(move || async move { + /// // Because the memo subscribes to `count` by adding it as a dependency, the memo will rerun every time `count` changes. + /// let new_count = use_memo(move || { /// count + 1 /// }) /// .use_dependencies((&count,)); From 81182f99fff639d9a6f9583891fc418838f49d4a Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 13 Mar 2024 12:00:18 -0500 Subject: [PATCH 4/5] fix and improve signal dependencies example --- packages/signals/examples/dependancies.rs | 32 ++++++++++++++++------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/packages/signals/examples/dependancies.rs b/packages/signals/examples/dependancies.rs index 34159d34a..af869be2b 100644 --- a/packages/signals/examples/dependancies.rs +++ b/packages/signals/examples/dependancies.rs @@ -14,16 +14,28 @@ fn app() -> Element { } }); - let mut local_state = use_signal(|| 0); - - let computed = use_memo_with_dependencies((&local_state(),), move |(local_state,)| { - local_state * 2 + signal.cloned() - }); - - println!("Running app"); - rsx! { - button { onclick: move |_| local_state.set(local_state() + 1), "Add one" } - div { "{computed}" } + "Parent count: {signal}" + Child { + non_reactive_prop: signal() + } + } +} + +#[component] +fn Child(non_reactive_prop: i32) -> Element { + let mut signal = use_signal(|| 0); + + // You can manually specify the dependencies with `use_dependencies` for values that are not reactive like props + let computed = + use_memo(move || non_reactive_prop + signal()).use_dependencies((&non_reactive_prop,)); + + rsx! { + button { + onclick: move |_| signal += 1, + "Child count: {signal}" + } + + "Sum: {computed}" } } From df41df75d00bfb167259439a51fe6b2926f957de Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Fri, 15 Mar 2024 11:54:09 -0500 Subject: [PATCH 5/5] switch to use_reactive --- packages/hooks/src/lib.rs | 3 + packages/hooks/src/use_effect.rs | 58 ++++++++----------- packages/hooks/src/use_memo.rs | 18 ++++++ .../src/use_reactive.rs} | 58 +++++++++++++++++++ packages/hooks/src/use_resource.rs | 57 +++++++----------- packages/signals/examples/dependancies.rs | 17 +++++- packages/signals/src/lib.rs | 3 - packages/signals/src/memo.rs | 36 +----------- 8 files changed, 138 insertions(+), 112 deletions(-) rename packages/{signals/src/dependency.rs => hooks/src/use_reactive.rs} (57%) diff --git a/packages/hooks/src/lib.rs b/packages/hooks/src/lib.rs index 6717254fc..3af9dddda 100644 --- a/packages/hooks/src/lib.rs +++ b/packages/hooks/src/lib.rs @@ -69,6 +69,9 @@ pub use use_coroutine::*; mod use_future; pub use use_future::*; +mod use_reactive; +pub use use_reactive::*; + // mod use_sorted; // pub use use_sorted::*; diff --git a/packages/hooks/src/use_effect.rs b/packages/hooks/src/use_effect.rs index b364562ea..5a7792756 100644 --- a/packages/hooks/src/use_effect.rs +++ b/packages/hooks/src/use_effect.rs @@ -2,8 +2,7 @@ use dioxus_core::prelude::*; use dioxus_signals::ReactiveContext; use futures_util::StreamExt; -use crate::use_signal; -use dioxus_signals::*; +use crate::use_callback; /// `use_effect` will subscribe to any changes in the signal values it captures /// effects will always run after first mount and then whenever the signal values change @@ -22,11 +21,31 @@ use dioxus_signals::*; /// } /// } /// ``` +/// +/// ## With non-reactive dependencies +/// To add non-reactive dependencies, you can use the `use_reactive` hook. +/// +/// Signals will automatically be added as dependencies, so you don't need to call this method for them. +/// +/// ```rust +/// # use dioxus::prelude::*; +/// # async fn sleep(delay: u32) {} +/// +/// #[component] +/// fn Comp(count: u32) -> Element { +/// // Because the memo subscribes to `count` by adding it as a dependency, the memo will rerun every time `count` changes. +/// use_effect(use_reactive((&count, |(count,)| println!("Manually manipulate the dom") ))); +/// +/// todo!() +/// } +/// ``` #[track_caller] -pub fn use_effect(mut callback: impl FnMut() + 'static) -> Effect { +pub fn use_effect(callback: impl FnMut() + 'static) -> Effect { // let mut run_effect = use_hook(|| CopyValue::new(true)); // use_hook_did_run(move |did_run| run_effect.set(did_run)); + let callback = use_callback(callback); + let location = std::panic::Location::caller(); use_hook(|| { @@ -34,7 +53,7 @@ pub fn use_effect(mut callback: impl FnMut() + 'static) -> Effect { spawn(async move { loop { // Run the effect - rc.run_in(&mut callback); + rc.run_in(&*callback); // Wait for context to change let _ = changed.next().await; @@ -54,37 +73,6 @@ pub struct Effect { } impl Effect { - /// Adds an explicit dependency to the effect. If the dependency changes, the effect's closure will rerun. - /// - /// Signals will automatically be added as dependencies, so you don't need to call this method for them. - /// - /// NOTE: You must follow the rules of hooks when calling this method. - /// - /// ```rust - /// # use dioxus::prelude::*; - /// # async fn sleep(delay: u32) {} - /// - /// #[component] - /// fn Comp(delay: u32) -> Element { - /// // Because the effect subscribes to `delay` by adding it as a dependency, the effect's closure will rerun every time `delay` changes. - /// use_effect(move || { - /// println!("It is safe to manually manipulate the dom here"); - /// }) - /// .use_dependencies((&delay,)); - /// - /// todo!() - /// } - /// ``` - pub fn use_dependencies(mut self, dependency: impl Dependency) -> Self { - let mut dependencies_signal = use_signal(|| dependency.out()); - let changed = { dependency.changed(&*dependencies_signal.read()) }; - if changed { - dependencies_signal.set(dependency.out()); - self.mark_dirty(); - } - self - } - /// Marks the effect as dirty, causing it to rerun on the next render. pub fn mark_dirty(&mut self) { self.rc.mark_dirty(); diff --git a/packages/hooks/src/use_memo.rs b/packages/hooks/src/use_memo.rs index 57c0eb30f..bd5b98171 100644 --- a/packages/hooks/src/use_memo.rs +++ b/packages/hooks/src/use_memo.rs @@ -19,6 +19,24 @@ use dioxus_signals::{Memo, Signal}; /// rsx! { "{double}" } /// } /// ``` +/// +/// ## With non-reactive dependencies +/// To add non-reactive dependencies, you can use the `use_reactive` hook. +/// +/// Signals will automatically be added as dependencies, so you don't need to call this method for them. +/// +/// ```rust +/// # use dioxus::prelude::*; +/// # async fn sleep(delay: u32) {} +/// +/// #[component] +/// fn Comp(count: u32) -> Element { +/// // Because the memo subscribes to `count` by adding it as a dependency, the memo will rerun every time `count` changes. +/// let new_count = use_memo(use_reactive((&count, |(count,)| count + 1))); +/// +/// todo!() +/// } +/// ``` #[track_caller] pub fn use_memo(f: impl FnMut() -> R + 'static) -> Memo { let callback = use_callback(f); diff --git a/packages/signals/src/dependency.rs b/packages/hooks/src/use_reactive.rs similarity index 57% rename from packages/signals/src/dependency.rs rename to packages/hooks/src/use_reactive.rs index a00e29070..96a7289e4 100644 --- a/packages/signals/src/dependency.rs +++ b/packages/hooks/src/use_reactive.rs @@ -1,3 +1,7 @@ +use dioxus_signals::{Readable, Writable}; + +use crate::use_signal; + /// A dependency is a trait that can be used to determine if a effect or selector should be re-run. pub trait Dependency: Sized + Clone { /// The output of the dependency @@ -65,3 +69,57 @@ impl_dep!(A = a1 a2, B = b1 b2, C = c1 c2, D = d1 d2, E = e1 e2,); impl_dep!(A = a1 a2, B = b1 b2, C = c1 c2, D = d1 d2, E = e1 e2, F = f1 f2,); impl_dep!(A = a1 a2, B = b1 b2, C = c1 c2, D = d1 d2, E = e1 e2, F = f1 f2, G = g1 g2,); impl_dep!(A = a1 a2, B = b1 b2, C = c1 c2, D = d1 d2, E = e1 e2, F = f1 f2, G = g1 g2, H = h1 h2,); + +/// Takes some non-reactive data, and a closure and returns a closure that will subscribe to that non-reactive data as if it were reactive. +/// +/// # Example +/// +/// ```rust +/// use dioxus::prelude::*; +/// +/// let data = 5; +/// +/// use_effect(use_reactive((&data,), |(data,)| { +/// println!("Data changed: {}", data); +/// })); +/// ``` +pub fn use_reactive( + non_reactive_data: D, + mut closure: impl FnMut(D::Out) -> O + 'static, +) -> impl FnMut() -> O + 'static { + let mut first_run = false; + let mut last_state = use_signal(|| { + first_run = true; + non_reactive_data.out() + }); + if !first_run && non_reactive_data.changed(&*last_state.peek()) { + last_state.set(non_reactive_data.out()); + } + move || closure(last_state()) +} + +/// A helper macro for `use_reactive` that merges uses the closure syntax to elaborate the dependency array +/// +/// Takes some non-reactive data, and a closure and returns a closure that will subscribe to that non-reactive data as if it were reactive. +/// +/// # Example +/// +/// ```rust +/// use dioxus::prelude::*; +/// +/// let data = 5; +/// +/// use_effect(use_reactive!(|data| { +/// println!("Data changed: {}", data); +/// })); +/// ``` +#[macro_export] +macro_rules! use_reactive { + (|| $($rest:tt)*) => { use_reactive( (), move |_| $($rest)* ) }; + (| $($args:tt),* | $($rest:tt)*) => { + use_reactive( + ($(&$args),*), + move |($($args),*)| $($rest)* + ) + }; +} diff --git a/packages/hooks/src/use_resource.rs b/packages/hooks/src/use_resource.rs index b78330ec8..3af0a0a30 100644 --- a/packages/hooks/src/use_resource.rs +++ b/packages/hooks/src/use_resource.rs @@ -51,8 +51,26 @@ use std::{cell::Cell, future::Future, rc::Rc}; /// } ///} /// ``` +/// +/// ## With non-reactive dependencies +/// To add non-reactive dependencies, you can use the `use_reactive` hook. +/// +/// Signals will automatically be added as dependencies, so you don't need to call this method for them. +/// +/// ```rust +/// # use dioxus::prelude::*; +/// # async fn sleep(delay: u32) {} +/// +/// #[component] +/// fn Comp(count: u32) -> Element { +/// // Because the memo subscribes to `count` by adding it as a dependency, the memo will rerun every time `count` changes. +/// let new_count = use_resource(use_reactive((&count, |(count,)| async move {count + 1} ))); +/// +/// todo!() +/// } +/// ``` #[must_use = "Consider using `cx.spawn` to run a future without reading its value"] -pub fn use_resource(future: impl Fn() -> F + 'static) -> Resource +pub fn use_resource(mut future: impl FnMut() -> F + 'static) -> Resource where T: 'static, F: Future + 'static, @@ -66,10 +84,9 @@ where let cb = use_callback(move || { // Create the user's task - #[allow(clippy::redundant_closure)] - let fut = rc.run_in(|| future()); + let fut = rc.run_in(&mut future); - // Spawn a wrapper task that polls the innner future and watch its dependencies + // Spawn a wrapper task that polls the inner future and watch its dependencies spawn(async move { // move the future here and pin it so we can poll it let fut = fut; @@ -144,38 +161,6 @@ pub enum UseResourceState { } impl Resource { - /// Adds an explicit dependency to the resource. If the dependency changes, the resource's future will rerun. - /// - /// Signals will automatically be added as dependencies, so you don't need to call this method for them. - /// - /// NOTE: You must follow the rules of hooks when calling this method. - /// - /// ```rust - /// # use dioxus::prelude::*; - /// # async fn sleep(delay: u32) {} - /// - /// #[component] - /// fn Comp(delay: u32) -> Element { - /// // Because the resource subscribes to `delay` by adding it as a dependency, the resource's future will rerun every time `delay` changes. - /// let current_weather = use_resource(move || async move { - /// sleep(delay).await; - /// "Sunny" - /// }) - /// .use_dependencies((&delay,)); - /// - /// todo!() - /// } - /// ``` - pub fn use_dependencies(mut self, dependency: impl Dependency) -> Self { - let mut dependencies_signal = use_signal(|| dependency.out()); - let changed = { dependency.changed(&*dependencies_signal.read()) }; - if changed { - dependencies_signal.set(dependency.out()); - self.restart(); - } - self - } - /// Restart the resource's future. /// /// Will not cancel the previous future, but will ignore any values that it diff --git a/packages/signals/examples/dependancies.rs b/packages/signals/examples/dependancies.rs index af869be2b..70319da40 100644 --- a/packages/signals/examples/dependancies.rs +++ b/packages/signals/examples/dependancies.rs @@ -26,9 +26,18 @@ fn app() -> Element { fn Child(non_reactive_prop: i32) -> Element { let mut signal = use_signal(|| 0); - // You can manually specify the dependencies with `use_dependencies` for values that are not reactive like props - let computed = - use_memo(move || non_reactive_prop + signal()).use_dependencies((&non_reactive_prop,)); + // You can manually specify the dependencies with `use_reactive` for values that are not reactive like props + let computed = use_memo(use_reactive!( + |(non_reactive_prop,)| non_reactive_prop + signal() + )); + use_effect(use_reactive!(|(non_reactive_prop,)| println!( + "{}", + non_reactive_prop + signal() + ))); + let fut = use_resource(use_reactive!(|(non_reactive_prop,)| async move { + tokio::time::sleep(std::time::Duration::from_secs(1)).await; + non_reactive_prop + signal() + })); rsx! { button { @@ -37,5 +46,7 @@ fn Child(non_reactive_prop: i32) -> Element { } "Sum: {computed}" + + "{fut():?}" } } diff --git a/packages/signals/src/lib.rs b/packages/signals/src/lib.rs index 812470b3a..1492fe205 100644 --- a/packages/signals/src/lib.rs +++ b/packages/signals/src/lib.rs @@ -19,9 +19,6 @@ pub use map::*; // mod comparer; // pub use comparer::*; -mod dependency; -pub use dependency::*; - mod memo; pub use memo::*; diff --git a/packages/signals/src/memo.rs b/packages/signals/src/memo.rs index d3df894fb..cc850510a 100644 --- a/packages/signals/src/memo.rs +++ b/packages/signals/src/memo.rs @@ -1,6 +1,6 @@ use crate::write::Writable; use crate::{read::Readable, ReactiveContext, ReadableRef, Signal}; -use crate::{CopyValue, Dependency, ReadOnlySignal}; +use crate::{CopyValue, ReadOnlySignal}; use std::rc::Rc; use std::{ cell::RefCell, @@ -115,40 +115,6 @@ impl Memo { pub fn id(&self) -> generational_box::GenerationalBoxId { self.inner.id() } - - /// Adds an explicit dependency to the memo. If the dependency changes, the memo will rerun. - /// - /// Signals will automatically be added as dependencies, so you don't need to call this method for them. - /// - /// NOTE: You must follow the rules of hooks when calling this method. - /// - /// ```rust - /// # use dioxus::prelude::*; - /// # async fn sleep(delay: u32) {} - /// - /// #[component] - /// fn Comp(count: u32) -> Element { - /// // Because the memo subscribes to `count` by adding it as a dependency, the memo will rerun every time `count` changes. - /// let new_count = use_memo(move || { - /// count + 1 - /// }) - /// .use_dependencies((&count,)); - /// - /// todo!() - /// } - /// ``` - pub fn use_dependencies(self, dependency: impl Dependency) -> Self - where - T: PartialEq, - { - let mut dependencies_signal = use_hook(|| CopyValue::new(dependency.out())); - let changed = { dependency.changed(&*dependencies_signal.read()) }; - if changed { - dependencies_signal.set(dependency.out()); - self.recompute(); - } - self - } } impl Readable for Memo