From 83f7ef9a447547c720e310b10e1682dc7762bdbc Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sun, 19 Nov 2023 09:58:00 -0600 Subject: [PATCH 1/3] add an optional cleanup closure to the use effect hook --- examples/readme.rs | 6 +++ packages/hooks/src/use_effect.rs | 79 ++++++++++++++++++++++++++++---- 2 files changed, 76 insertions(+), 9 deletions(-) diff --git a/examples/readme.rs b/examples/readme.rs index f4668488b..b39c04765 100644 --- a/examples/readme.rs +++ b/examples/readme.rs @@ -11,6 +11,12 @@ fn main() { fn app(cx: Scope) -> Element { let mut count = use_state(cx, || 0); + use_effect(cx, (), move |()| async {}); + + use_effect(cx, (count.get(),), move |(count,)| async move { + move || println!("Count unmounted from {}", count) + }); + cx.render(rsx! { h1 { "High-Five counter: {count}" } button { onclick: move |_| count += 1, "Up high!" } diff --git a/packages/hooks/src/use_effect.rs b/packages/hooks/src/use_effect.rs index e72afe27f..1f5faecdd 100644 --- a/packages/hooks/src/use_effect.rs +++ b/packages/hooks/src/use_effect.rs @@ -1,5 +1,10 @@ use dioxus_core::{ScopeState, TaskId}; -use std::{any::Any, cell::Cell, future::Future}; +use std::{ + any::Any, + cell::{Cell, RefCell}, + future::Future, + rc::Rc, +}; use crate::UseFutureDep; @@ -14,7 +19,7 @@ use crate::UseFutureDep; /// ## Arguments /// /// - `dependencies`: a tuple of references to values that are `PartialEq` + `Clone`. -/// - `future`: a closure that takes the `dependencies` as arguments and returns a `'static` future. +/// - `future`: a closure that takes the `dependencies` as arguments and returns a `'static` future. That future may return nothing or a closure that will be executed when the dependencies change to clean up the effect. /// /// ## Examples /// @@ -33,6 +38,16 @@ use crate::UseFutureDep; /// } /// }); /// +/// // Only fetch the user data when the id changes. +/// use_effect(cx, (id,), |(id,)| { +/// to_owned![name]; +/// async move { +/// let user = fetch_user(id).await; +/// name.set(user.name); +/// move || println!("Cleaning up from {}", id); +/// } +/// }); +/// /// let name = name.get().clone().unwrap_or("Loading...".to_string()); /// /// render!( @@ -45,34 +60,80 @@ use crate::UseFutureDep; /// render!(Profile { id: 0 }) /// } /// ``` -pub fn use_effect(cx: &ScopeState, dependencies: D, future: impl FnOnce(D::Out) -> F) +pub fn use_effect(cx: &ScopeState, dependencies: D, future: impl FnOnce(D::Out) -> R) where - T: 'static, - F: Future + 'static, D: UseFutureDep, + R: UseEffectReturn, { struct UseEffect { needs_regen: bool, task: Cell>, dependencies: Vec>, + cleanup: UseEffectCleanup, + } + + impl Drop for UseEffect { + fn drop(&mut self) { + if let Some(cleanup) = self.cleanup.borrow_mut().take() { + cleanup(); + } + } } let state = cx.use_hook(move || UseEffect { needs_regen: true, task: Cell::new(None), dependencies: Vec::new(), + cleanup: Rc::new(RefCell::new(None)), }); if dependencies.clone().apply(&mut state.dependencies) || state.needs_regen { + // Call the cleanup function if it exists + if let Some(cleanup) = state.cleanup.borrow_mut().take() { + cleanup(); + } + // We don't need regen anymore state.needs_regen = false; // Create the new future - let fut = future(dependencies.out()); + let return_value = future(dependencies.out()); - state.task.set(Some(cx.push_future(async move { - fut.await; - }))); + if let Some(task) = return_value.apply(state.cleanup.clone(), cx) { + state.task.set(Some(task)); + } + } +} + +type UseEffectCleanup = Rc>>>; + +/// Something that can be returned from a `use_effect` hook. +pub trait UseEffectReturn { + fn apply(self, oncleanup: UseEffectCleanup, cx: &ScopeState) -> Option; +} + +impl UseEffectReturn<()> for T +where + T: Future + 'static, +{ + fn apply(self, _: UseEffectCleanup, cx: &ScopeState) -> Option { + Some(cx.push_future(self)) + } +} + +#[doc(hidden)] +pub struct CleanupFutureMarker; +impl UseEffectReturn for T +where + T: Future + 'static, + F: FnOnce() + 'static, +{ + fn apply(self, oncleanup: UseEffectCleanup, cx: &ScopeState) -> Option { + let task = cx.push_future(async move { + let cleanup = self.await; + *oncleanup.borrow_mut() = Some(Box::new(cleanup) as Box); + }); + Some(task) } } From 7a459e15d8316abfdb5b39d25deb4fff9318fbfe Mon Sep 17 00:00:00 2001 From: ealmloff Date: Tue, 21 Nov 2023 16:28:06 -0600 Subject: [PATCH 2/3] Update packages/hooks/src/use_effect.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Marc EspĂ­n --- packages/hooks/src/use_effect.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/hooks/src/use_effect.rs b/packages/hooks/src/use_effect.rs index 1f5faecdd..6fb2fcf9e 100644 --- a/packages/hooks/src/use_effect.rs +++ b/packages/hooks/src/use_effect.rs @@ -44,7 +44,7 @@ use crate::UseFutureDep; /// async move { /// let user = fetch_user(id).await; /// name.set(user.name); -/// move || println!("Cleaning up from {}", id); +/// move || println!("Cleaning up from {}", id) /// } /// }); /// From 017d7e96bf1ed70463f01d3fb9dc52824179269e Mon Sep 17 00:00:00 2001 From: ealmloff Date: Tue, 21 Nov 2023 16:28:31 -0600 Subject: [PATCH 3/3] restore readme.rs --- examples/readme.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/examples/readme.rs b/examples/readme.rs index b39c04765..f4668488b 100644 --- a/examples/readme.rs +++ b/examples/readme.rs @@ -11,12 +11,6 @@ fn main() { fn app(cx: Scope) -> Element { let mut count = use_state(cx, || 0); - use_effect(cx, (), move |()| async {}); - - use_effect(cx, (count.get(),), move |(count,)| async move { - move || println!("Count unmounted from {}", count) - }); - cx.render(rsx! { h1 { "High-Five counter: {count}" } button { onclick: move |_| count += 1, "Up high!" }