global_context.rs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. use crate::{runtime::Runtime, Element, ScopeId, Task};
  2. use futures_util::{future::poll_fn, Future};
  3. use std::sync::Arc;
  4. /// Get the current scope id
  5. pub fn current_scope_id() -> Option<ScopeId> {
  6. Runtime::with(|rt| rt.current_scope_id()).flatten()
  7. }
  8. #[doc(hidden)]
  9. /// Check if the virtual dom is currently inside of the body of a component
  10. pub fn vdom_is_rendering() -> bool {
  11. Runtime::with(|rt| rt.rendering.get()).unwrap_or_default()
  12. }
  13. /// Consume context from the current scope
  14. pub fn try_consume_context<T: 'static + Clone>() -> Option<T> {
  15. Runtime::with_current_scope(|cx| cx.consume_context::<T>()).flatten()
  16. }
  17. /// Consume context from the current scope
  18. pub fn consume_context<T: 'static + Clone>() -> T {
  19. Runtime::with_current_scope(|cx| cx.consume_context::<T>())
  20. .flatten()
  21. .unwrap_or_else(|| panic!("Could not find context {}", std::any::type_name::<T>()))
  22. }
  23. /// Consume context from the current scope
  24. pub fn consume_context_from_scope<T: 'static + Clone>(scope_id: ScopeId) -> Option<T> {
  25. Runtime::with(|rt| {
  26. rt.get_state(scope_id)
  27. .and_then(|cx| cx.consume_context::<T>())
  28. })
  29. .flatten()
  30. }
  31. /// Check if the current scope has a context
  32. pub fn has_context<T: 'static + Clone>() -> Option<T> {
  33. Runtime::with_current_scope(|cx| cx.has_context::<T>()).flatten()
  34. }
  35. /// Provide context to the current scope
  36. pub fn provide_context<T: 'static + Clone>(value: T) -> T {
  37. Runtime::with_current_scope(|cx| cx.provide_context(value)).expect("to be in a dioxus runtime")
  38. }
  39. /// Provide a context to the root scope
  40. pub fn provide_root_context<T: 'static + Clone>(value: T) -> Option<T> {
  41. Runtime::with_current_scope(|cx| cx.provide_root_context(value))
  42. }
  43. /// Suspends the current component
  44. pub fn suspend() -> Option<Element> {
  45. Runtime::with_current_scope(|cx| cx.suspend());
  46. None
  47. }
  48. /// Spawns the future but does not return the [`TaskId`]
  49. pub fn spawn(fut: impl Future<Output = ()> + 'static) -> Task {
  50. Runtime::with_current_scope(|cx| cx.spawn(fut)).expect("to be in a dioxus runtime")
  51. }
  52. /// Spawn a future that Dioxus won't clean up when this component is unmounted
  53. ///
  54. /// This is good for tasks that need to be run after the component has been dropped.
  55. pub fn spawn_forever(fut: impl Future<Output = ()> + 'static) -> Option<Task> {
  56. Runtime::with_current_scope(|cx| cx.spawn_forever(fut))
  57. }
  58. /// Informs the scheduler that this task is no longer needed and should be removed.
  59. ///
  60. /// This drops the task immediately.
  61. pub fn remove_future(id: Task) {
  62. Runtime::with(|rt| rt.remove_task(id)).expect("Runtime to exist");
  63. }
  64. /// Store a value between renders. The foundational hook for all other hooks.
  65. ///
  66. /// Accepts an `initializer` closure, which is run on the first use of the hook (typically the initial render). The return value of this closure is stored for the lifetime of the component, and a mutable reference to it is provided on every render as the return value of `use_hook`.
  67. ///
  68. /// When the component is unmounted (removed from the UI), the value is dropped. This means you can return a custom type and provide cleanup code by implementing the [`Drop`] trait
  69. ///
  70. /// # Example
  71. ///
  72. /// ```
  73. /// use dioxus_core::use_hook;
  74. ///
  75. /// // prints a greeting on the initial render
  76. /// pub fn use_hello_world() {
  77. /// use_hook(|| println!("Hello, world!"));
  78. /// }
  79. /// ```
  80. pub fn use_hook<State: Clone + 'static>(initializer: impl FnOnce() -> State) -> State {
  81. Runtime::with_current_scope(|cx| cx.use_hook(initializer)).expect("to be in a dioxus runtime")
  82. }
  83. /// Get the current render since the inception of this component
  84. ///
  85. /// This can be used as a helpful diagnostic when debugging hooks/renders, etc
  86. pub fn generation() -> usize {
  87. Runtime::with_current_scope(|cx| cx.generation()).expect("to be in a dioxus runtime")
  88. }
  89. /// Get the parent of the current scope if it exists
  90. pub fn parent_scope() -> Option<ScopeId> {
  91. Runtime::with_current_scope(|cx| cx.parent_id()).flatten()
  92. }
  93. /// Mark the current scope as dirty, causing it to re-render
  94. pub fn needs_update() {
  95. Runtime::with_current_scope(|cx| cx.needs_update());
  96. }
  97. /// Schedule an update for the current component
  98. ///
  99. /// Note: Unlike [`needs_update`], the function returned by this method will work outside of the dioxus runtime.
  100. ///
  101. /// You should prefer [`schedule_update_any`] if you need to update multiple components.
  102. pub fn schedule_update() -> Arc<dyn Fn() + Send + Sync> {
  103. Runtime::with_current_scope(|cx| cx.schedule_update()).expect("to be in a dioxus runtime")
  104. }
  105. /// Schedule an update for any component given its [`ScopeId`].
  106. ///
  107. /// A component's [`ScopeId`] can be obtained from the [`current_scope_id`] method.
  108. ///
  109. /// Note: Unlike [`needs_update`], the function returned by this method will work outside of the dioxus runtime.
  110. pub fn schedule_update_any() -> Arc<dyn Fn(ScopeId) + Send + Sync> {
  111. Runtime::with_current_scope(|cx| cx.schedule_update_any()).expect("to be in a dioxus runtime")
  112. }
  113. /// Creates a callback that will be run before the component is removed.
  114. /// This can be used to clean up side effects from the component
  115. /// (created with [`use_effect`](crate::use_effect)).
  116. ///
  117. /// Example:
  118. /// ```rust, ignore
  119. /// use dioxus::prelude::*;
  120. ///
  121. /// fn app() -> Element {
  122. /// let state = use_signal(|| true);
  123. /// rsx! {
  124. /// for _ in 0..100 {
  125. /// h1 {
  126. /// "spacer"
  127. /// }
  128. /// }
  129. /// if **state {
  130. /// rsx! {
  131. /// child_component {}
  132. /// }
  133. /// }
  134. /// button {
  135. /// onclick: move |_| {
  136. /// state.set(!*state.get());
  137. /// },
  138. /// "Unmount element"
  139. /// }
  140. /// }
  141. /// }
  142. ///
  143. /// fn child_component() -> Element {
  144. /// let original_scroll_position = use_signal(|| 0.0);
  145. /// use_effect((), move |_| {
  146. /// to_owned![original_scroll_position];
  147. /// async move {
  148. /// let window = web_sys::window().unwrap();
  149. /// let document = window.document().unwrap();
  150. /// let element = document.get_element_by_id("my_element").unwrap();
  151. /// element.scroll_into_view();
  152. /// original_scroll_position.set(window.scroll_y().unwrap());
  153. /// }
  154. /// });
  155. ///
  156. /// use_drop({
  157. /// to_owned![original_scroll_position];
  158. /// /// restore scroll to the top of the page
  159. /// move || {
  160. /// let window = web_sys::window().unwrap();
  161. /// window.scroll_with_x_and_y(*original_scroll_position.current(), 0.0);
  162. /// }
  163. /// });
  164. ///
  165. /// rsx!{
  166. /// div {
  167. /// id: "my_element",
  168. /// "hello"
  169. /// }
  170. /// }
  171. /// }
  172. /// ```
  173. pub fn use_drop<D: FnOnce() + 'static>(destroy: D) {
  174. struct LifeCycle<D: FnOnce()> {
  175. /// Wrap the closure in an option so that we can take it out on drop.
  176. ondestroy: Option<D>,
  177. }
  178. /// On drop, we want to run the closure.
  179. impl<D: FnOnce()> Drop for LifeCycle<D> {
  180. fn drop(&mut self) {
  181. if let Some(f) = self.ondestroy.take() {
  182. f();
  183. }
  184. }
  185. }
  186. // We need to impl clone for the lifecycle, but we don't want the drop handler for the closure to be called twice.
  187. impl<D: FnOnce()> Clone for LifeCycle<D> {
  188. fn clone(&self) -> Self {
  189. Self { ondestroy: None }
  190. }
  191. }
  192. use_hook(|| LifeCycle {
  193. ondestroy: Some(destroy),
  194. });
  195. }
  196. /// A hook that allows you to insert a "before render" function.
  197. ///
  198. /// This function will always be called before dioxus tries to render your component. This should be used for safely handling
  199. /// early returns
  200. pub fn use_before_render(f: impl FnMut() + 'static) {
  201. use_hook(|| before_render(f));
  202. }
  203. /// Push this function to be run after the next render
  204. ///
  205. /// This function will always be called before dioxus tries to render your component. This should be used for safely handling
  206. /// early returns
  207. pub fn use_after_render(f: impl FnMut() + 'static) {
  208. use_hook(|| after_render(f));
  209. }
  210. /// Push a function to be run before the next render
  211. /// This is a hook and will always run, so you can't unschedule it
  212. /// Will run for every progression of suspense, though this might change in the future
  213. pub fn before_render(f: impl FnMut() + 'static) {
  214. Runtime::with_current_scope(|cx| cx.push_before_render(f));
  215. }
  216. /// Push a function to be run after the render is complete, even if it didn't complete successfully
  217. pub fn after_render(f: impl FnMut() + 'static) {
  218. Runtime::with_current_scope(|cx| cx.push_after_render(f));
  219. }
  220. /// Wait for the virtualdom to finish its sync work before proceeding
  221. ///
  222. /// This is useful if you've just triggered an update and want to wait for it to finish before proceeding with valid
  223. /// DOM nodes.
  224. ///
  225. /// Effects rely on this to ensure that they only run effects after the DOM has been updated. Without flush_sync effects
  226. /// are run immediately before diffing the DOM, which causes all sorts of out-of-sync weirdness.
  227. pub async fn flush_sync() {
  228. let mut polled = false;
  229. let _task =
  230. FlushKey(Runtime::with(|rt| rt.add_to_flush_table()).expect("to be in a dioxus runtime"));
  231. // Poll without giving the waker to anyone
  232. // The runtime will manually wake this task up when it's ready
  233. poll_fn(|_| {
  234. if !polled {
  235. polled = true;
  236. futures_util::task::Poll::Pending
  237. } else {
  238. futures_util::task::Poll::Ready(())
  239. }
  240. })
  241. .await;
  242. // If the the future got polled, then we don't need to prevent it from being dropped
  243. // If we had generational indicies on tasks we could simply let the task remain in the queue and just be a no-op
  244. // when it's run
  245. std::mem::forget(_task);
  246. struct FlushKey(Task);
  247. impl Drop for FlushKey {
  248. fn drop(&mut self) {
  249. Runtime::with(|rt| rt.flush_table.borrow_mut().remove(&self.0));
  250. }
  251. }
  252. }
  253. /// Use a hook with a cleanup function
  254. pub fn use_hook_with_cleanup<T: Clone + 'static>(
  255. hook: impl FnOnce() -> T,
  256. cleanup: impl FnOnce(T) + 'static,
  257. ) -> T {
  258. let value = use_hook(hook);
  259. let _value = value.clone();
  260. use_drop(move || cleanup(_value));
  261. value
  262. }