From 0951a389f7831572e4444a1d9e93b98ae7eaf486 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 13 Mar 2024 09:54:48 -0500 Subject: [PATCH] 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