hooks.rs 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. //! Built-in hooks
  2. //!
  3. //! This module contains all the low-level built-in hooks that require 1st party support to work.
  4. //!
  5. //! Hooks:
  6. //! - use_hook
  7. //! - use_state_provider
  8. //! - use_state_consumer
  9. //! - use_task
  10. //! - use_suspense
  11. use crate::innerlude::*;
  12. use futures_util::FutureExt;
  13. use std::{
  14. any::{Any, TypeId},
  15. cell::{Cell, RefCell},
  16. future::Future,
  17. rc::Rc,
  18. };
  19. /// This hook enables the ability to expose state to children further down the VirtualDOM Tree.
  20. ///
  21. /// This is a hook, so it may not be called conditionally!
  22. ///
  23. /// The init method is ran *only* on first use, otherwise it is ignored. However, it uses hooks (ie `use`)
  24. /// so don't put it in a conditional.
  25. ///
  26. /// When the component is dropped, so is the context. Be aware of this behavior when consuming
  27. /// the context via Rc/Weak.
  28. ///
  29. ///
  30. ///
  31. pub fn use_provide_state<'src, Pr, T, F>(cx: Context<'src, Pr>, init: F) -> &'src Rc<T>
  32. where
  33. T: 'static,
  34. F: FnOnce() -> T,
  35. {
  36. let ty = TypeId::of::<T>();
  37. let contains_key = cx.scope.shared_contexts.borrow().contains_key(&ty);
  38. let is_initialized = cx.use_hook(
  39. |_| false,
  40. |s| {
  41. let i = s.clone();
  42. *s = true;
  43. i
  44. },
  45. |_| {},
  46. );
  47. match (is_initialized, contains_key) {
  48. // Do nothing, already initialized and already exists
  49. (true, true) => {}
  50. // Needs to be initialized
  51. (false, false) => {
  52. log::debug!("Initializing context...");
  53. cx.add_shared_state(init());
  54. log::info!(
  55. "There are now {} shared contexts for scope {:?}",
  56. cx.scope.shared_contexts.borrow().len(),
  57. cx.scope.our_arena_idx,
  58. );
  59. }
  60. _ => debug_assert!(false, "Cannot initialize two contexts of the same type"),
  61. };
  62. use_consume_state::<T, _>(cx)
  63. }
  64. /// There are hooks going on here!
  65. pub fn use_consume_state<'src, T: 'static, P>(cx: Context<'src, P>) -> &'src Rc<T> {
  66. use_try_consume_state::<T, _>(cx).unwrap()
  67. }
  68. /// Uses a context, storing the cached value around
  69. ///
  70. /// If a context is not found on the first search, then this call will be "dud", always returning "None" even if a
  71. /// context was added later. This allows using another hook as a fallback
  72. ///
  73. pub fn use_try_consume_state<'src, T: 'static, P>(cx: Context<'src, P>) -> Option<&'src Rc<T>> {
  74. struct UseContextHook<C>(Option<Rc<C>>);
  75. cx.use_hook(
  76. move |_| UseContextHook(cx.consume_shared_state::<T>()),
  77. move |hook| hook.0.as_ref(),
  78. |_| {},
  79. )
  80. }
  81. /// Awaits the given task, forcing the component to re-render when the value is ready.
  82. ///
  83. ///
  84. ///
  85. ///
  86. pub fn use_task<'src, Out, Fut, Init, P>(
  87. cx: Context<'src, P>,
  88. task_initializer: Init,
  89. ) -> (&'src TaskHandle, &'src Option<Out>)
  90. where
  91. Out: 'static,
  92. Fut: Future<Output = Out> + 'static,
  93. Init: FnOnce() -> Fut + 'src,
  94. {
  95. struct TaskHook<T> {
  96. handle: TaskHandle,
  97. task_dump: Rc<RefCell<Option<T>>>,
  98. value: Option<T>,
  99. }
  100. // whenever the task is complete, save it into th
  101. cx.use_hook(
  102. move |hook_idx| {
  103. let task_fut = task_initializer();
  104. let task_dump = Rc::new(RefCell::new(None));
  105. let slot = task_dump.clone();
  106. let updater = cx.prepare_update();
  107. let update_id = cx.get_scope_id();
  108. let originator = cx.scope.our_arena_idx.clone();
  109. let handle = cx.submit_task(Box::pin(task_fut.then(move |output| async move {
  110. *slot.as_ref().borrow_mut() = Some(output);
  111. updater(update_id);
  112. EventTrigger {
  113. event: VirtualEvent::AsyncEvent { hook_idx },
  114. originator,
  115. priority: EventPriority::Low,
  116. real_node_id: None,
  117. }
  118. })));
  119. TaskHook {
  120. task_dump,
  121. value: None,
  122. handle,
  123. }
  124. },
  125. |hook| {
  126. if let Some(val) = hook.task_dump.as_ref().borrow_mut().take() {
  127. hook.value = Some(val);
  128. }
  129. (&hook.handle, &hook.value)
  130. },
  131. |_| {},
  132. )
  133. }
  134. /// Asynchronously render new nodes once the given future has completed.
  135. ///
  136. /// # Easda
  137. ///
  138. ///
  139. ///
  140. ///
  141. /// # Example
  142. ///
  143. ///
  144. pub fn use_suspense<'src, Out, Fut, Cb, P>(
  145. cx: Context<'src, P>,
  146. task_initializer: impl FnOnce() -> Fut,
  147. user_callback: Cb,
  148. ) -> DomTree<'src>
  149. where
  150. Fut: Future<Output = Out> + 'static,
  151. Out: 'static,
  152. Cb: for<'a> Fn(SuspendedContext<'a>, &Out) -> DomTree<'a> + 'static,
  153. {
  154. cx.use_hook(
  155. move |hook_idx| {
  156. let value = Rc::new(RefCell::new(None));
  157. let dom_node_id = Rc::new(empty_cell());
  158. let domnode = dom_node_id.clone();
  159. let slot = value.clone();
  160. let callback: SuspendedCallback = Box::new(move |ctx: SuspendedContext| {
  161. let v: std::cell::Ref<Option<Box<dyn Any>>> = slot.as_ref().borrow();
  162. match v.as_ref() {
  163. Some(a) => {
  164. let v: &dyn Any = a.as_ref();
  165. let real_val = v.downcast_ref::<Out>().unwrap();
  166. user_callback(ctx, real_val)
  167. }
  168. None => {
  169. //
  170. Some(VNode {
  171. dom_id: empty_cell(),
  172. key: None,
  173. kind: VNodeKind::Suspended {
  174. node: domnode.clone(),
  175. },
  176. })
  177. }
  178. }
  179. });
  180. let originator = cx.scope.our_arena_idx.clone();
  181. let task_fut = task_initializer();
  182. let domnode = dom_node_id.clone();
  183. let slot = value.clone();
  184. cx.submit_task(Box::pin(task_fut.then(move |output| async move {
  185. // When the new value arrives, set the hooks internal slot
  186. // Dioxus will call the user's callback to generate new nodes outside of the diffing system
  187. *slot.borrow_mut() = Some(Box::new(output) as Box<dyn Any>);
  188. EventTrigger {
  189. event: VirtualEvent::SuspenseEvent { hook_idx, domnode },
  190. originator,
  191. priority: EventPriority::Low,
  192. real_node_id: None,
  193. }
  194. })));
  195. SuspenseHook {
  196. value,
  197. callback,
  198. dom_node_id,
  199. }
  200. },
  201. move |hook| {
  202. let cx = Context {
  203. scope: &cx.scope,
  204. props: &(),
  205. };
  206. let csx = SuspendedContext { inner: cx };
  207. (&hook.callback)(csx)
  208. },
  209. |_| {},
  210. )
  211. }
  212. pub(crate) struct SuspenseHook {
  213. pub value: Rc<RefCell<Option<Box<dyn Any>>>>,
  214. pub callback: SuspendedCallback,
  215. pub dom_node_id: Rc<Cell<Option<ElementId>>>,
  216. }
  217. type SuspendedCallback = Box<dyn for<'a> Fn(SuspendedContext<'a>) -> DomTree<'a>>;
  218. pub struct SuspendedContext<'a> {
  219. pub(crate) inner: Context<'a, ()>,
  220. }
  221. impl<'src> SuspendedContext<'src> {
  222. pub fn render<F: FnOnce(NodeFactory<'src>) -> VNode<'src>>(
  223. self,
  224. lazy_nodes: LazyNodes<'src, F>,
  225. ) -> DomTree<'src> {
  226. let scope_ref = self.inner.scope;
  227. Some(lazy_nodes.into_vnode(NodeFactory { scope: scope_ref }))
  228. }
  229. }