use_effect.rs 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. use dioxus_core::{ScopeState, TaskId};
  2. use std::{
  3. any::Any,
  4. cell::{Cell, RefCell},
  5. future::Future,
  6. rc::Rc,
  7. };
  8. use crate::UseFutureDep;
  9. /// A hook that provides a future that executes after the hooks have been applied.
  10. ///
  11. /// Whenever the hooks dependencies change, the future will be re-evaluated.
  12. /// If a future is pending when the dependencies change, the previous future
  13. /// will be allowed to continue.
  14. ///
  15. /// **Note:** If your dependency list is always empty, use [`use_on_create`](crate::use_on_create).
  16. ///
  17. /// ## Arguments
  18. ///
  19. /// - `dependencies`: a tuple of references to values that are `PartialEq` + `Clone`.
  20. /// - `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.
  21. ///
  22. /// ## Examples
  23. ///
  24. /// ```rust, no_run
  25. /// # use dioxus::prelude::*;
  26. /// #[component]
  27. /// fn Profile(cx: Scope, id: usize) -> Element {
  28. /// let name = use_state(cx, || None);
  29. ///
  30. /// // Only fetch the user data when the id changes.
  31. /// use_effect(cx, (id,), |(id,)| {
  32. /// to_owned![name];
  33. /// async move {
  34. /// let user = fetch_user(id).await;
  35. /// name.set(user.name);
  36. /// }
  37. /// });
  38. ///
  39. /// // Only fetch the user data when the id changes.
  40. /// use_effect(cx, (id,), |(id,)| {
  41. /// to_owned![name];
  42. /// async move {
  43. /// let user = fetch_user(id).await;
  44. /// name.set(user.name);
  45. /// move || println!("Cleaning up from {}", id)
  46. /// }
  47. /// });
  48. ///
  49. /// let name = name.get().clone().unwrap_or("Loading...".to_string());
  50. ///
  51. /// render!(
  52. /// p { "{name}" }
  53. /// )
  54. /// }
  55. ///
  56. /// #[component]
  57. /// fn App(cx: Scope) -> Element {
  58. /// render!(Profile { id: 0 })
  59. /// }
  60. /// ```
  61. pub fn use_effect<T, R, D>(cx: &ScopeState, dependencies: D, future: impl FnOnce(D::Out) -> R)
  62. where
  63. D: UseFutureDep,
  64. R: UseEffectReturn<T>,
  65. {
  66. struct UseEffect {
  67. needs_regen: bool,
  68. task: Cell<Option<TaskId>>,
  69. dependencies: Vec<Box<dyn Any>>,
  70. cleanup: UseEffectCleanup,
  71. }
  72. impl Drop for UseEffect {
  73. fn drop(&mut self) {
  74. if let Some(cleanup) = self.cleanup.borrow_mut().take() {
  75. cleanup();
  76. }
  77. }
  78. }
  79. let state = cx.use_hook(move || UseEffect {
  80. needs_regen: true,
  81. task: Cell::new(None),
  82. dependencies: Vec::new(),
  83. cleanup: Rc::new(RefCell::new(None)),
  84. });
  85. if dependencies.clone().apply(&mut state.dependencies) || state.needs_regen {
  86. // Call the cleanup function if it exists
  87. if let Some(cleanup) = state.cleanup.borrow_mut().take() {
  88. cleanup();
  89. }
  90. // We don't need regen anymore
  91. state.needs_regen = false;
  92. // Create the new future
  93. let return_value = future(dependencies.out());
  94. if let Some(task) = return_value.apply(state.cleanup.clone(), cx) {
  95. state.task.set(Some(task));
  96. }
  97. }
  98. }
  99. type UseEffectCleanup = Rc<RefCell<Option<Box<dyn FnOnce()>>>>;
  100. /// Something that can be returned from a `use_effect` hook.
  101. pub trait UseEffectReturn<T> {
  102. fn apply(self, oncleanup: UseEffectCleanup, cx: &ScopeState) -> Option<TaskId>;
  103. }
  104. impl<T> UseEffectReturn<()> for T
  105. where
  106. T: Future<Output = ()> + 'static,
  107. {
  108. fn apply(self, _: UseEffectCleanup, cx: &ScopeState) -> Option<TaskId> {
  109. Some(cx.push_future(self))
  110. }
  111. }
  112. #[doc(hidden)]
  113. pub struct CleanupFutureMarker;
  114. impl<T, F> UseEffectReturn<CleanupFutureMarker> for T
  115. where
  116. T: Future<Output = F> + 'static,
  117. F: FnOnce() + 'static,
  118. {
  119. fn apply(self, oncleanup: UseEffectCleanup, cx: &ScopeState) -> Option<TaskId> {
  120. let task = cx.push_future(async move {
  121. let cleanup = self.await;
  122. *oncleanup.borrow_mut() = Some(Box::new(cleanup) as Box<dyn FnOnce()>);
  123. });
  124. Some(task)
  125. }
  126. }
  127. #[cfg(test)]
  128. mod tests {
  129. use super::*;
  130. #[allow(unused)]
  131. #[test]
  132. fn test_use_future() {
  133. use dioxus_core::prelude::*;
  134. struct MyProps {
  135. a: String,
  136. b: i32,
  137. c: i32,
  138. d: i32,
  139. e: i32,
  140. }
  141. fn app(cx: Scope<MyProps>) -> Element {
  142. // should only ever run once
  143. use_effect(cx, (), |_| async move {
  144. //
  145. });
  146. // runs when a is changed
  147. use_effect(cx, (&cx.props.a,), |(a,)| async move {
  148. //
  149. });
  150. // runs when a or b is changed
  151. use_effect(cx, (&cx.props.a, &cx.props.b), |(a, b)| async move {
  152. //
  153. });
  154. todo!()
  155. }
  156. }
  157. }