use_shared_state.rs 11 KB

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