diff --git a/packages/hooks/src/use_effect.rs b/packages/hooks/src/use_effect.rs index e72afe27f..6fb2fcf9e 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) } }