1
0

use_shared_state.rs 4.6 KB

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