use_shared_state.rs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375
  1. use self::error::{UseSharedStateError, UseSharedStateResult};
  2. use dioxus_core::{ScopeId, ScopeState};
  3. use std::{collections::HashSet, rc::Rc, sync::Arc};
  4. #[cfg(debug_assertions)]
  5. pub use dioxus_debug_cell::{
  6. error::{BorrowError, BorrowMutError},
  7. Ref, RefCell, RefMut,
  8. };
  9. #[cfg(not(debug_assertions))]
  10. pub use std::cell::{BorrowError, BorrowMutError, Ref, RefCell, RefMut};
  11. #[macro_export]
  12. macro_rules! debug_location {
  13. () => {{
  14. #[cfg(debug_assertions)]
  15. {
  16. std::panic::Location::caller()
  17. }
  18. #[cfg(not(debug_assertions))]
  19. {
  20. ()
  21. }
  22. }};
  23. }
  24. pub mod error {
  25. fn locations_display(locations: &[&'static std::panic::Location<'static>]) -> String {
  26. locations
  27. .iter()
  28. .map(|location| format!(" - {location}"))
  29. .collect::<Vec<_>>()
  30. .join("\n")
  31. }
  32. #[derive(thiserror::Error, Debug)]
  33. pub enum UseSharedStateError {
  34. #[cfg_attr(
  35. debug_assertions,
  36. error(
  37. "[{0}] {1} is already borrowed at, so it cannot be borrowed mutably. Previous borrows:\n[{2}]\n\n",
  38. .source.attempted_at,
  39. .type_name,
  40. locations_display(&.source.already_borrowed_at)
  41. )
  42. )]
  43. #[cfg_attr(
  44. not(debug_assertions),
  45. error("{type_name} is already borrowed, so it cannot be borrowed mutably. (More detail available in debug mode)")
  46. )]
  47. AlreadyBorrowed {
  48. source: super::BorrowMutError,
  49. type_name: &'static str,
  50. },
  51. #[cfg_attr(
  52. debug_assertions,
  53. error(
  54. "[{0}] {1} is already borrowed mutably at [{2}], so it cannot be borrowed anymore.",
  55. .source.attempted_at,
  56. .type_name,
  57. locations_display(&.source.already_borrowed_at)
  58. )
  59. )]
  60. #[cfg_attr(
  61. not(debug_assertions),
  62. error("{type_name} is already borrowed mutably, so it cannot be borrowed anymore. (More detail available in debug mode)")
  63. )]
  64. AlreadyBorrowedMutably {
  65. source: super::BorrowError,
  66. type_name: &'static str,
  67. },
  68. }
  69. pub type UseSharedStateResult<T> = Result<T, UseSharedStateError>;
  70. }
  71. type ProvidedState<T> = Rc<RefCell<ProvidedStateInner<T>>>;
  72. // Tracks all the subscribers to a shared State
  73. pub(crate) struct ProvidedStateInner<T> {
  74. value: T,
  75. notify_any: Arc<dyn Fn(ScopeId)>,
  76. consumers: HashSet<ScopeId>,
  77. gen: usize,
  78. }
  79. impl<T> ProvidedStateInner<T> {
  80. pub(crate) fn notify_consumers(&mut self) {
  81. self.gen += 1;
  82. for consumer in self.consumers.iter() {
  83. (self.notify_any)(*consumer);
  84. }
  85. }
  86. }
  87. /// This hook provides some relatively light ergonomics around shared state.
  88. ///
  89. /// It is not a substitute for a proper state management system, but it is capable enough to provide use_state - type
  90. /// ergonomics in a pinch, with zero cost.
  91. ///
  92. /// # Example
  93. ///
  94. /// ```rust
  95. /// # use dioxus::prelude::*;
  96. /// #
  97. /// # fn app(cx: Scope) -> Element {
  98. /// # render! {
  99. /// # Parent{}
  100. /// # }
  101. /// # }
  102. ///
  103. /// #[derive(Clone, Copy)]
  104. /// enum Theme {
  105. /// Light,
  106. /// Dark,
  107. /// }
  108. ///
  109. /// // Provider
  110. /// fn Parent<'a>(cx: Scope<'a>) -> Element<'a> {
  111. /// use_shared_state_provider(cx, || Theme::Dark);
  112. /// let theme = use_shared_state::<Theme>(cx).unwrap();
  113. ///
  114. /// render! {
  115. /// button{
  116. /// onclick: move |_| {
  117. /// let current_theme = *theme.read();
  118. /// *theme.write() = match current_theme {
  119. /// Theme::Dark => Theme::Light,
  120. /// Theme::Light => Theme::Dark,
  121. /// };
  122. /// },
  123. /// "Change theme"
  124. /// }
  125. /// Child{}
  126. /// }
  127. /// }
  128. ///
  129. /// // Consumer
  130. /// fn Child<'a>(cx: Scope<'a>) -> Element<'a> {
  131. /// let theme = use_shared_state::<Theme>(cx).unwrap();
  132. /// let current_theme = *theme.read();
  133. ///
  134. /// render! {
  135. /// match current_theme {
  136. /// Theme::Dark => {
  137. /// "Dark mode"
  138. /// }
  139. /// Theme::Light => {
  140. /// "Light mode"
  141. /// }
  142. /// }
  143. /// }
  144. /// }
  145. /// ```
  146. ///
  147. /// # How it works
  148. ///
  149. /// Any time a component calls `write`, every consumer of the state will be notified - excluding the provider.
  150. ///
  151. /// Right now, there is not a distinction between read-only and write-only, so every consumer will be notified.
  152. #[must_use]
  153. pub fn use_shared_state<T: 'static>(cx: &ScopeState) -> Option<&UseSharedState<T>> {
  154. let state_owner: &mut Option<UseSharedStateOwner<T>> = &mut *cx.use_hook(move || {
  155. let scope_id = cx.scope_id();
  156. let root = cx.consume_context::<ProvidedState<T>>()?;
  157. root.borrow_mut().consumers.insert(scope_id);
  158. let state = UseSharedState::new(root);
  159. let owner = UseSharedStateOwner { state, scope_id };
  160. Some(owner)
  161. });
  162. state_owner.as_mut().map(|s| {
  163. s.state.gen = s.state.inner.borrow().gen;
  164. &s.state
  165. })
  166. }
  167. /// This wrapper detects when the hook is dropped and will unsubscribe when the component is unmounted
  168. struct UseSharedStateOwner<T> {
  169. state: UseSharedState<T>,
  170. scope_id: ScopeId,
  171. }
  172. impl<T> Drop for UseSharedStateOwner<T> {
  173. fn drop(&mut self) {
  174. // we need to unsubscribe when our component is unmounted
  175. let mut root = self.state.inner.borrow_mut();
  176. root.consumers.remove(&self.scope_id);
  177. }
  178. }
  179. /// State that is shared between components through the context system
  180. pub struct UseSharedState<T> {
  181. pub(crate) inner: Rc<RefCell<ProvidedStateInner<T>>>,
  182. gen: usize,
  183. }
  184. impl<T> UseSharedState<T> {
  185. fn new(inner: Rc<RefCell<ProvidedStateInner<T>>>) -> Self {
  186. let gen = inner.borrow().gen;
  187. Self { inner, gen }
  188. }
  189. /// Notify all consumers of the state that it has changed. (This is called automatically when you call "write")
  190. pub fn notify_consumers(&self) {
  191. self.inner.borrow_mut().notify_consumers();
  192. }
  193. /// Try reading the shared state
  194. #[cfg_attr(debug_assertions, track_caller)]
  195. #[cfg_attr(debug_assertions, inline(never))]
  196. pub fn try_read(&self) -> UseSharedStateResult<Ref<'_, T>> {
  197. match self.inner.try_borrow() {
  198. Ok(value) => Ok(Ref::map(value, |inner| &inner.value)),
  199. Err(source) => Err(UseSharedStateError::AlreadyBorrowedMutably {
  200. source,
  201. type_name: std::any::type_name::<Self>(),
  202. }),
  203. }
  204. }
  205. /// Read the shared value
  206. #[cfg_attr(debug_assertions, track_caller)]
  207. #[cfg_attr(debug_assertions, inline(never))]
  208. pub fn read(&self) -> Ref<'_, T> {
  209. match self.try_read() {
  210. Ok(value) => value,
  211. Err(message) => panic!(
  212. "Reading the shared state failed: {}\n({:?})",
  213. message, message
  214. ),
  215. }
  216. }
  217. /// Try writing the shared state
  218. #[cfg_attr(debug_assertions, track_caller)]
  219. #[cfg_attr(debug_assertions, inline(never))]
  220. pub fn try_write(&self) -> UseSharedStateResult<RefMut<'_, T>> {
  221. match self.inner.try_borrow_mut() {
  222. Ok(mut value) => {
  223. value.notify_consumers();
  224. Ok(RefMut::map(value, |inner| &mut inner.value))
  225. }
  226. Err(source) => Err(UseSharedStateError::AlreadyBorrowed {
  227. source,
  228. type_name: std::any::type_name::<Self>(),
  229. }),
  230. }
  231. }
  232. /// Calling "write" will force the component to re-render
  233. ///
  234. ///
  235. // TODO: We prevent unncessary notifications only in the hook, but we should figure out some more global lock
  236. #[cfg_attr(debug_assertions, track_caller)]
  237. #[cfg_attr(debug_assertions, inline(never))]
  238. pub fn write(&self) -> RefMut<'_, T> {
  239. match self.try_write() {
  240. Ok(value) => value,
  241. Err(message) => panic!(
  242. "Writing to shared state failed: {}\n({:?})",
  243. message, message
  244. ),
  245. }
  246. }
  247. /// Tries writing the value without forcing a re-render
  248. #[cfg_attr(debug_assertions, track_caller)]
  249. #[cfg_attr(debug_assertions, inline(never))]
  250. pub fn try_write_silent(&self) -> UseSharedStateResult<RefMut<'_, T>> {
  251. match self.inner.try_borrow_mut() {
  252. Ok(value) => Ok(RefMut::map(value, |inner| &mut inner.value)),
  253. Err(source) => Err(UseSharedStateError::AlreadyBorrowed {
  254. source,
  255. type_name: std::any::type_name::<Self>(),
  256. }),
  257. }
  258. }
  259. /// Writes the value without forcing a re-render
  260. #[cfg_attr(debug_assertions, track_caller)]
  261. #[cfg_attr(debug_assertions, inline(never))]
  262. pub fn write_silent(&self) -> RefMut<'_, T> {
  263. match self.try_write_silent() {
  264. Ok(value) => value,
  265. Err(message) => panic!(
  266. "Writing to shared state silently failed: {}\n({:?})",
  267. message, message
  268. ),
  269. }
  270. }
  271. /// Take a reference to the inner value temporarily and produce a new value
  272. #[cfg_attr(debug_assertions, track_caller)]
  273. #[cfg_attr(debug_assertions, inline(never))]
  274. pub fn with<O>(&self, immutable_callback: impl FnOnce(&T) -> O) -> O {
  275. immutable_callback(&*self.read())
  276. }
  277. /// Take a mutable reference to the inner value temporarily and produce a new value
  278. #[cfg_attr(debug_assertions, track_caller)]
  279. #[cfg_attr(debug_assertions, inline(never))]
  280. pub fn with_mut<O>(&self, mutable_callback: impl FnOnce(&mut T) -> O) -> O {
  281. mutable_callback(&mut *self.write())
  282. }
  283. }
  284. impl<T> Clone for UseSharedState<T> {
  285. fn clone(&self) -> Self {
  286. Self {
  287. inner: self.inner.clone(),
  288. gen: self.gen,
  289. }
  290. }
  291. }
  292. impl<T> PartialEq for UseSharedState<T> {
  293. fn eq(&self, other: &Self) -> bool {
  294. self.gen == other.gen
  295. }
  296. }
  297. /// Provide some state for components down the hierarchy to consume without having to drill props. See [`use_shared_state`] to consume the state
  298. ///
  299. ///
  300. /// # Example
  301. ///
  302. /// ```rust
  303. /// # use dioxus::prelude::*;
  304. /// #
  305. /// # fn app(cx: Scope) -> Element {
  306. /// # render! {
  307. /// # Parent{}
  308. /// # }
  309. /// # }
  310. ///
  311. /// #[derive(Clone, Copy)]
  312. /// enum Theme {
  313. /// Light,
  314. /// Dark,
  315. /// }
  316. ///
  317. /// // Provider
  318. /// fn Parent<'a>(cx: Scope<'a>) -> Element<'a> {
  319. /// use_shared_state_provider(cx, || Theme::Dark);
  320. /// let theme = use_shared_state::<Theme>(cx).unwrap();
  321. ///
  322. /// render! {
  323. /// button{
  324. /// onclick: move |_| {
  325. /// let current_theme = *theme.read();
  326. /// *theme.write() = match current_theme {
  327. /// Theme::Dark => Theme::Light,
  328. /// Theme::Light => Theme::Dark,
  329. /// };
  330. /// },
  331. /// "Change theme"
  332. /// }
  333. /// // Children components that consume the state...
  334. /// }
  335. /// }
  336. /// ```
  337. pub fn use_shared_state_provider<T: 'static>(cx: &ScopeState, f: impl FnOnce() -> T) {
  338. cx.use_hook(|| {
  339. let state: ProvidedState<T> = Rc::new(RefCell::new(ProvidedStateInner {
  340. value: f(),
  341. notify_any: cx.schedule_update_any(),
  342. consumers: HashSet::new(),
  343. gen: 0,
  344. }));
  345. cx.provide_context(state);
  346. });
  347. }