hooks.rs 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. //! Built-in hooks
  2. //!
  3. //! This module contains all the low-level built-in hooks that require first 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::{any::Any, cell::RefCell, future::Future, ops::Deref, rc::Rc};
  14. /// Awaits the given task, forcing the component to re-render when the value is ready.
  15. ///
  16. /// Returns the handle to the task and the value (if it is ready, else None).
  17. ///
  18. /// ```
  19. /// static Example: FC<()> = |(cx, props)| {
  20. /// let (task, value) = use_task(|| async {
  21. /// timer::sleep(Duration::from_secs(1)).await;
  22. /// "Hello World"
  23. /// });
  24. ///
  25. /// match contents {
  26. /// Some(contents) => rsx!(cx, div { "{title}" }),
  27. /// None => rsx!(cx, div { "Loading..." }),
  28. /// }
  29. /// };
  30. /// ```
  31. pub fn use_task<'src, Out, Fut, Init>(
  32. cx: Context<'src>,
  33. task_initializer: Init,
  34. ) -> (&'src TaskHandle, &'src Option<Out>)
  35. where
  36. Out: 'static,
  37. Fut: Future<Output = Out> + 'static,
  38. Init: FnOnce() -> Fut + 'src,
  39. {
  40. struct TaskHook<T> {
  41. handle: TaskHandle,
  42. task_dump: Rc<RefCell<Option<T>>>,
  43. value: Option<T>,
  44. }
  45. // whenever the task is complete, save it into th
  46. cx.use_hook(
  47. move |_| {
  48. let task_fut = task_initializer();
  49. let task_dump = Rc::new(RefCell::new(None));
  50. let slot = task_dump.clone();
  51. let updater = cx.schedule_update_any();
  52. let originator = cx.scope.our_arena_idx;
  53. let handle = cx.submit_task(Box::pin(task_fut.then(move |output| async move {
  54. *slot.as_ref().borrow_mut() = Some(output);
  55. updater(originator);
  56. originator
  57. })));
  58. TaskHook {
  59. task_dump,
  60. value: None,
  61. handle,
  62. }
  63. },
  64. |hook| {
  65. if let Some(val) = hook.task_dump.as_ref().borrow_mut().take() {
  66. hook.value = Some(val);
  67. }
  68. (&hook.handle, &hook.value)
  69. },
  70. |_| {},
  71. )
  72. }
  73. /// Asynchronously render new nodes once the given future has completed.
  74. ///
  75. /// # Easda
  76. ///
  77. ///
  78. ///
  79. ///
  80. /// # Example
  81. ///
  82. ///
  83. pub fn use_suspense<'src, Out, Fut, Cb>(
  84. cx: Context<'src>,
  85. task_initializer: impl FnOnce() -> Fut,
  86. user_callback: Cb,
  87. ) -> DomTree<'src>
  88. where
  89. Fut: Future<Output = Out> + 'static,
  90. Out: 'static,
  91. Cb: for<'a> Fn(SuspendedContext<'a>, &Out) -> DomTree<'a> + 'static,
  92. {
  93. /*
  94. General strategy:
  95. - Create a slot for the future to dump its output into
  96. - Create a new future feeding off the user's future that feeds the output into that slot
  97. - Submit that future as a task
  98. - Take the task handle id and attach that to our suspended node
  99. - when the hook runs, check if the value exists
  100. - if it does, then we can render the node directly
  101. - if it doesn't, then we render a suspended node along with with the callback and task id
  102. */
  103. cx.use_hook(
  104. move |_| {
  105. let value = Rc::new(RefCell::new(None));
  106. let slot = value.clone();
  107. let originator = cx.scope.our_arena_idx;
  108. let handle = cx.submit_task(Box::pin(task_initializer().then(
  109. move |output| async move {
  110. *slot.borrow_mut() = Some(Box::new(output) as Box<dyn Any>);
  111. originator
  112. },
  113. )));
  114. SuspenseHook { handle, value }
  115. },
  116. move |hook| match hook.value.borrow().as_ref() {
  117. Some(value) => {
  118. let out = value.downcast_ref::<Out>().unwrap();
  119. let sus = SuspendedContext {
  120. inner: Context { scope: cx.scope },
  121. };
  122. user_callback(sus, out)
  123. }
  124. None => {
  125. let value = hook.value.clone();
  126. cx.render(LazyNodes::new(|f| {
  127. let bump = f.bump();
  128. use bumpalo::boxed::Box as BumpBox;
  129. let f: &mut dyn FnMut(SuspendedContext<'src>) -> DomTree<'src> =
  130. bump.alloc(move |sus| {
  131. let val = value.borrow();
  132. let out = val
  133. .as_ref()
  134. .unwrap()
  135. .as_ref()
  136. .downcast_ref::<Out>()
  137. .unwrap();
  138. user_callback(sus, out)
  139. });
  140. let callback = unsafe { BumpBox::from_raw(f) };
  141. VNode::Suspended(bump.alloc(VSuspended {
  142. dom_id: empty_cell(),
  143. task_id: hook.handle.our_id,
  144. callback: RefCell::new(Some(callback)),
  145. }))
  146. }))
  147. }
  148. },
  149. |_| {},
  150. )
  151. }
  152. pub(crate) struct SuspenseHook {
  153. pub handle: TaskHandle,
  154. pub value: Rc<RefCell<Option<Box<dyn Any>>>>,
  155. }
  156. pub struct SuspendedContext<'a> {
  157. pub(crate) inner: Context<'a>,
  158. }
  159. impl<'src> SuspendedContext<'src> {
  160. pub fn render<F: FnOnce(NodeFactory<'src>) -> VNode<'src>>(
  161. self,
  162. lazy_nodes: LazyNodes<'src, F>,
  163. ) -> DomTree<'src> {
  164. let bump = &self.inner.scope.frames.wip_frame().bump;
  165. Some(lazy_nodes.into_vnode(NodeFactory { bump }))
  166. }
  167. }
  168. #[derive(Clone, Copy)]
  169. pub struct NodeRef<'src, T: 'static>(&'src RefCell<Option<T>>);
  170. impl<'a, T> Deref for NodeRef<'a, T> {
  171. type Target = RefCell<Option<T>>;
  172. fn deref(&self) -> &Self::Target {
  173. self.0
  174. }
  175. }
  176. pub fn use_node_ref<T, P>(cx: Context) -> NodeRef<T> {
  177. cx.use_hook(|_| RefCell::new(None), |f| NodeRef { 0: f }, |_| {})
  178. }