use_shared_state.rs 4.8 KB

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