1
0

use_shared_state.rs 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. use dioxus_core::{prelude::Context, ScopeId};
  2. use std::{
  3. cell::{Cell, Ref, RefCell, RefMut},
  4. collections::HashSet,
  5. rc::Rc,
  6. };
  7. type ProvidedState<T> = RefCell<ProvidedStateInner<T>>;
  8. // Tracks all the subscribers to a shared State
  9. pub(crate) struct ProvidedStateInner<T> {
  10. value: Rc<RefCell<T>>,
  11. notify_any: Rc<dyn Fn(ScopeId)>,
  12. consumers: HashSet<ScopeId>,
  13. }
  14. impl<T> ProvidedStateInner<T> {
  15. pub(crate) fn notify_consumers(&mut self) {
  16. for consumer in self.consumers.iter() {
  17. (self.notify_any)(*consumer);
  18. }
  19. }
  20. }
  21. /// This hook provides some relatively light ergonomics around shared state.
  22. ///
  23. /// It is not a substitute for a proper state management system, but it is capable enough to provide use_state - type
  24. /// ergonimics in a pinch, with zero cost.
  25. ///
  26. /// # Example
  27. ///
  28. /// ## Provider
  29. ///
  30. /// ```rust
  31. ///
  32. ///
  33. /// ```
  34. ///
  35. /// ## Consumer
  36. ///
  37. /// ```rust
  38. ///
  39. ///
  40. /// ```
  41. ///
  42. /// # How it works
  43. ///
  44. /// Any time a component calls `write`, every consumer of the state will be notified - excluding the provider.
  45. ///
  46. /// Right now, there is not a distinction between read-only and write-only, so every consumer will be notified.
  47. ///
  48. ///
  49. ///
  50. pub fn use_shared_state<'a, T: 'static>(cx: Context<'a>) -> Option<UseSharedState<'a, T>> {
  51. cx.use_hook(
  52. |_| {
  53. let scope_id = cx.scope_id();
  54. let root = cx.consume_state::<ProvidedState<T>>();
  55. if let Some(root) = root.as_ref() {
  56. root.borrow_mut().consumers.insert(scope_id);
  57. }
  58. let value = root.as_ref().map(|f| f.borrow().value.clone());
  59. SharedStateInner {
  60. root,
  61. value,
  62. scope_id,
  63. needs_notification: Cell::new(false),
  64. }
  65. },
  66. |f| {
  67. //
  68. f.needs_notification.set(false);
  69. match (&f.value, &f.root) {
  70. (Some(value), Some(root)) => Some(UseSharedState {
  71. cx,
  72. value,
  73. root,
  74. needs_notification: &f.needs_notification,
  75. }),
  76. _ => None,
  77. }
  78. },
  79. |f| {
  80. // we need to unsubscribe when our component is unounted
  81. if let Some(root) = &f.root {
  82. let mut root = root.borrow_mut();
  83. root.consumers.remove(&f.scope_id);
  84. }
  85. },
  86. )
  87. }
  88. struct SharedStateInner<T: 'static> {
  89. root: Option<Rc<ProvidedState<T>>>,
  90. value: Option<Rc<RefCell<T>>>,
  91. scope_id: ScopeId,
  92. needs_notification: Cell<bool>,
  93. }
  94. pub struct UseSharedState<'a, T: 'static> {
  95. pub(crate) cx: Context<'a>,
  96. pub(crate) value: &'a Rc<RefCell<T>>,
  97. pub(crate) root: &'a Rc<RefCell<ProvidedStateInner<T>>>,
  98. pub(crate) needs_notification: &'a Cell<bool>,
  99. }
  100. impl<'a, T: 'static> UseSharedState<'a, T> {
  101. pub fn read(&self) -> Ref<'_, T> {
  102. self.value.borrow()
  103. }
  104. pub fn notify_consumers(self) {
  105. // if !self.needs_notification.get() {
  106. self.root.borrow_mut().notify_consumers();
  107. // self.needs_notification.set(true);
  108. // }
  109. }
  110. pub fn read_write(&self) -> (Ref<'_, T>, &Self) {
  111. (self.read(), self)
  112. }
  113. /// Calling "write" will force the component to re-render
  114. ///
  115. ///
  116. /// TODO: We prevent unncessary notifications only in the hook, but we should figure out some more global lock
  117. pub fn write(&self) -> RefMut<'_, T> {
  118. self.cx.needs_update();
  119. self.notify_consumers();
  120. self.value.borrow_mut()
  121. }
  122. /// Allows the ability to write the value without forcing a re-render
  123. pub fn write_silent(&self) -> RefMut<'_, T> {
  124. self.value.borrow_mut()
  125. }
  126. }
  127. impl<T> Copy for UseSharedState<'_, T> {}
  128. impl<'a, T> Clone for UseSharedState<'a, T>
  129. where
  130. T: 'static,
  131. {
  132. fn clone(&self) -> Self {
  133. UseSharedState {
  134. cx: self.cx,
  135. value: self.value,
  136. root: self.root,
  137. needs_notification: self.needs_notification,
  138. }
  139. }
  140. }
  141. /// Provide some state for components down the hierarchy to consume without having to drill props.
  142. ///
  143. ///
  144. ///
  145. ///
  146. ///
  147. ///
  148. ///
  149. pub fn use_provide_state<'a, T: 'static>(cx: Context<'a>, f: impl FnOnce() -> T) {
  150. cx.use_hook(
  151. |_| {
  152. let state: ProvidedState<T> = RefCell::new(ProvidedStateInner {
  153. value: Rc::new(RefCell::new(f())),
  154. notify_any: cx.schedule_update_any(),
  155. consumers: HashSet::new(),
  156. });
  157. cx.provide_state(state)
  158. },
  159. |inner| {},
  160. |_| {},
  161. )
  162. }