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