context.rs 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. //! Public APIs for managing component state, tasks, and lifecycles.
  2. use crate::innerlude::*;
  3. use std::{any::TypeId, ops::Deref, rc::Rc};
  4. /// Components in Dioxus use the "Context" object to interact with their lifecycle.
  5. ///
  6. /// This lets components access props, schedule updates, integrate hooks, and expose shared state.
  7. ///
  8. /// Note: all of these methods are *imperative* - they do not act as hooks! They are meant to be used by hooks
  9. /// to provide complex behavior. For instance, calling "add_shared_state" on every render is considered a leak. This method
  10. /// exists for the `use_provide_state` hook to provide a shared state object.
  11. ///
  12. /// For the most part, the only method you should be using regularly is `render`.
  13. ///
  14. /// ## Example
  15. ///
  16. /// ```ignore
  17. /// #[derive(Properties)]
  18. /// struct Props {
  19. /// name: String
  20. /// }
  21. ///
  22. /// fn example(cx: Context<Props>) -> VNode {
  23. /// html! {
  24. /// <div> "Hello, {cx.name}" </div>
  25. /// }
  26. /// }
  27. /// ```
  28. pub struct Context<'src, T> {
  29. pub props: &'src T,
  30. pub scope: &'src Scope,
  31. }
  32. impl<'src, T> Copy for Context<'src, T> {}
  33. impl<'src, T> Clone for Context<'src, T> {
  34. fn clone(&self) -> Self {
  35. Self {
  36. props: self.props,
  37. scope: self.scope,
  38. }
  39. }
  40. }
  41. // We currently deref to props, but it might make more sense to deref to Scope?
  42. // This allows for code that takes cx.xyz instead of cx.props.xyz
  43. impl<'a, T> Deref for Context<'a, T> {
  44. type Target = &'a T;
  45. fn deref(&self) -> &Self::Target {
  46. &self.props
  47. }
  48. }
  49. impl<'src, P> Context<'src, P> {
  50. /// Access the children elements passed into the component
  51. ///
  52. /// This enables patterns where a component is passed children from its parent.
  53. ///
  54. /// ## Details
  55. ///
  56. /// Unlike React, Dioxus allows *only* lists of children to be passed from parent to child - not arbitrary functions
  57. /// or classes. If you want to generate nodes instead of accepting them as a list, consider declaring a closure
  58. /// on the props that takes Context.
  59. ///
  60. /// If a parent passes children into a component, the child will always re-render when the parent re-renders. In other
  61. /// words, a component cannot be automatically memoized if it borrows nodes from its parent, even if the component's
  62. /// props are valid for the static lifetime.
  63. ///
  64. /// ## Example
  65. ///
  66. /// ```rust
  67. /// const App: FC<()> = |cx| {
  68. /// cx.render(rsx!{
  69. /// CustomCard {
  70. /// h1 {}
  71. /// p {}
  72. /// }
  73. /// })
  74. /// }
  75. ///
  76. /// const CustomCard: FC<()> = |cx| {
  77. /// cx.render(rsx!{
  78. /// div {
  79. /// h1 {"Title card"}
  80. /// {cx.children()}
  81. /// }
  82. /// })
  83. /// }
  84. /// ```
  85. pub fn children(&self) -> ScopeChildren<'src> {
  86. self.scope.child_nodes()
  87. }
  88. /// Create a subscription that schedules a future render for the reference component
  89. ///
  90. /// ## Notice: you should prefer using prepare_update and get_scope_id
  91. ///
  92. pub fn schedule_update(&self) -> Rc<dyn Fn() + 'static> {
  93. let cb = self.scope.vdom.schedule_update();
  94. let id = self.get_scope_id();
  95. Rc::new(move || cb(id))
  96. }
  97. pub fn prepare_update(&self) -> Rc<dyn Fn(ScopeId)> {
  98. self.scope.vdom.schedule_update()
  99. }
  100. pub fn schedule_effect(&self) -> Rc<dyn Fn() + 'static> {
  101. todo!()
  102. }
  103. pub fn schedule_layout_effect(&self) {
  104. todo!()
  105. }
  106. /// Get's this component's unique identifier.
  107. ///
  108. pub fn get_scope_id(&self) -> ScopeId {
  109. self.scope.our_arena_idx.clone()
  110. }
  111. /// Take a lazy VNode structure and actually build it with the context of the VDom's efficient VNode allocator.
  112. ///
  113. /// This function consumes the context and absorb the lifetime, so these VNodes *must* be returned.
  114. ///
  115. /// ## Example
  116. ///
  117. /// ```ignore
  118. /// fn Component(cx: Context<()>) -> VNode {
  119. /// // Lazy assemble the VNode tree
  120. /// let lazy_tree = html! {<div> "Hello World" </div>};
  121. ///
  122. /// // Actually build the tree and allocate it
  123. /// cx.render(lazy_tree)
  124. /// }
  125. ///```
  126. pub fn render<F: FnOnce(NodeFactory<'src>) -> VNode<'src>>(
  127. self,
  128. lazy_nodes: LazyNodes<'src, F>,
  129. ) -> DomTree<'src> {
  130. let bump = &self.scope.frames.wip_frame().bump;
  131. Some(lazy_nodes.into_vnode(NodeFactory { bump }))
  132. }
  133. /// `submit_task` will submit the future to be polled.
  134. ///
  135. /// This is useful when you have some async task that needs to be progressed.
  136. ///
  137. /// This method takes ownership over the task you've provided, and must return (). This means any work that needs to
  138. /// happen must occur within the future or scheduled for after the future completes (through schedule_update )
  139. ///
  140. /// ## Explanation
  141. /// Dioxus will step its internal event loop if the future returns if the future completes while waiting.
  142. ///
  143. /// Tasks can't return anything, but they can be controlled with the returned handle
  144. ///
  145. /// Tasks will only run until the component renders again. Because `submit_task` is valid for the &'src lifetime, it
  146. /// is considered "stable"
  147. ///
  148. ///
  149. ///
  150. pub fn submit_task(&self, task: FiberTask) -> TaskHandle {
  151. self.scope.vdom.submit_task(task)
  152. }
  153. /// Add a state globally accessible to child components via tree walking
  154. pub fn add_shared_state<T: 'static>(self, val: T) {
  155. self.scope
  156. .shared_contexts
  157. .borrow_mut()
  158. .insert(TypeId::of::<T>(), Rc::new(val))
  159. .map(|_| {
  160. log::warn!("A shared state was replaced with itself. This is does not result in a panic, but is probably not what you are trying to do");
  161. });
  162. }
  163. /// Walk the tree to find a shared state with the TypeId of the generic type
  164. ///
  165. pub fn consume_shared_state<T: 'static>(self) -> Option<Rc<T>> {
  166. let mut scope = Some(self.scope);
  167. let mut parent = None;
  168. let ty = TypeId::of::<T>();
  169. while let Some(inner) = scope {
  170. log::debug!(
  171. "Searching {:#?} for valid shared_context",
  172. inner.our_arena_idx
  173. );
  174. let shared_ctx = {
  175. let shared_contexts = inner.shared_contexts.borrow();
  176. log::debug!(
  177. "This component has {} shared contexts",
  178. shared_contexts.len()
  179. );
  180. shared_contexts.get(&ty).map(|f| f.clone())
  181. };
  182. if let Some(shared_cx) = shared_ctx {
  183. log::debug!("found matching cx");
  184. let rc = shared_cx
  185. .clone()
  186. .downcast::<T>()
  187. .expect("Should not fail, already validated the type from the hashmap");
  188. parent = Some(rc);
  189. break;
  190. } else {
  191. match inner.parent_idx {
  192. Some(parent_id) => {
  193. scope = unsafe { inner.vdom.get_scope(parent_id) };
  194. }
  195. None => break,
  196. }
  197. }
  198. }
  199. parent
  200. }
  201. /// Store a value between renders
  202. ///
  203. /// This is *the* foundational hook for all other hooks.
  204. ///
  205. /// - Initializer: closure used to create the initial hook state
  206. /// - Runner: closure used to output a value every time the hook is used
  207. /// - Cleanup: closure used to teardown the hook once the dom is cleaned up
  208. ///
  209. /// ```ignore
  210. /// // use_ref is the simplest way of storing a value between renders
  211. /// pub fn use_ref<T: 'static>(initial_value: impl FnOnce() -> T + 'static) -> Rc<RefCell<T>> {
  212. /// use_hook(
  213. /// || Rc::new(RefCell::new(initial_value())),
  214. /// |state| state.clone(),
  215. /// |_| {},
  216. /// )
  217. /// }
  218. /// ```
  219. pub fn use_hook<State, Output, Init, Run, Cleanup>(
  220. self,
  221. initializer: Init,
  222. runner: Run,
  223. _cleanup: Cleanup,
  224. ) -> Output
  225. where
  226. State: 'static,
  227. Output: 'src,
  228. Init: FnOnce(usize) -> State,
  229. Run: FnOnce(&'src mut State) -> Output,
  230. Cleanup: FnOnce(State),
  231. {
  232. // If the idx is the same as the hook length, then we need to add the current hook
  233. if self.scope.hooks.at_end() {
  234. let new_state = initializer(self.scope.hooks.len());
  235. self.scope.hooks.push(new_state);
  236. }
  237. const ERR_MSG: &str = r###"
  238. Unable to retrive the hook that was initialized in this index.
  239. Consult the `rules of hooks` to understand how to use hooks properly.
  240. You likely used the hook in a conditional. Hooks rely on consistent ordering between renders.
  241. Any function prefixed with "use" should not be called conditionally.
  242. "###;
  243. runner(self.scope.hooks.next::<State>().expect(ERR_MSG))
  244. }
  245. }