usestate.rs 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. use dioxus_core::prelude::*;
  2. use std::{
  3. cell::{Cell, Ref, RefCell, RefMut},
  4. fmt::{Debug, Display},
  5. rc::Rc,
  6. };
  7. /// Store state between component renders!
  8. ///
  9. /// ## Dioxus equivalent of useState, designed for Rust
  10. ///
  11. /// The Dioxus version of `useState` for state management inside components. It allows you to ergonomically store and
  12. /// modify state between component renders. When the state is updated, the component will re-render.
  13. ///
  14. /// Dioxus' use_state basically wraps a RefCell with helper methods and integrates it with the VirtualDOM update system.
  15. ///
  16. /// [`use_state`] exposes a few helper methods to modify the underlying state:
  17. /// - `.set(new)` allows you to override the "work in progress" value with a new value
  18. /// - `.get_mut()` allows you to modify the WIP value
  19. /// - `.get_wip()` allows you to access the WIP value
  20. /// - `.deref()` provides the previous value (often done implicitly, though a manual dereference with `*` might be required)
  21. ///
  22. /// Additionally, a ton of std::ops traits are implemented for the `UseState` wrapper, meaning any mutative type operations
  23. /// will automatically be called on the WIP value.
  24. ///
  25. /// ## Combinators
  26. ///
  27. /// On top of the methods to set/get state, `use_state` also supports fancy combinators to extend its functionality:
  28. /// - `.classic()` and `.split()` convert the hook into the classic React-style hook
  29. /// ```rust
  30. /// let (state, set_state) = use_state(&cx, || 10).split()
  31. /// ```
  32. /// Usage:
  33. ///
  34. /// ```ignore
  35. /// const Example: Component = |cx| {
  36. /// let counter = use_state(&cx, || 0);
  37. ///
  38. /// cx.render(rsx! {
  39. /// div {
  40. /// h1 { "Counter: {counter}" }
  41. /// button { onclick: move |_| counter.set(**counter + 1), "Increment" }
  42. /// button { onclick: move |_| counter.set(**counter - 1), "Decrement" }
  43. /// }
  44. /// ))
  45. /// }
  46. /// ```
  47. pub fn use_state<'a, T: 'static>(
  48. cx: &'a ScopeState,
  49. initial_state_fn: impl FnOnce() -> T,
  50. ) -> &'a UseState<T> {
  51. let hook = cx.use_hook(move |_| UseState {
  52. current_val: Rc::new(initial_state_fn()),
  53. update_callback: cx.schedule_update(),
  54. wip: Rc::new(RefCell::new(None)),
  55. update_scheuled: Cell::new(false),
  56. });
  57. hook.update_scheuled.set(false);
  58. let mut new_val = hook.wip.borrow_mut();
  59. if new_val.is_some() {
  60. // if there's only one reference (weak or otherwise), we can just swap the values
  61. if let Some(val) = Rc::get_mut(&mut hook.current_val) {
  62. *val = new_val.take().unwrap();
  63. } else {
  64. hook.current_val = Rc::new(new_val.take().unwrap());
  65. }
  66. }
  67. hook
  68. }
  69. pub struct UseState<T: 'static> {
  70. pub(crate) current_val: Rc<T>,
  71. pub(crate) wip: Rc<RefCell<Option<T>>>,
  72. pub(crate) update_callback: Rc<dyn Fn()>,
  73. pub(crate) update_scheuled: Cell<bool>,
  74. }
  75. impl<T: Debug> Debug for UseState<T> {
  76. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  77. write!(f, "{:?}", self.current_val)
  78. }
  79. }
  80. impl<T: 'static> UseState<T> {
  81. /// Tell the Dioxus Scheduler that we need to be processed
  82. pub fn needs_update(&self) {
  83. if !self.update_scheuled.get() {
  84. self.update_scheuled.set(true);
  85. (self.update_callback)();
  86. }
  87. }
  88. pub fn set(&self, new_val: T) {
  89. *self.wip.borrow_mut() = Some(new_val);
  90. self.needs_update();
  91. }
  92. pub fn get(&self) -> &T {
  93. &self.current_val
  94. }
  95. pub fn get_rc(&self) -> &Rc<T> {
  96. &self.current_val
  97. }
  98. /// Get the current status of the work-in-progress data
  99. pub fn get_wip(&self) -> Ref<Option<T>> {
  100. self.wip.borrow()
  101. }
  102. /// Get the current status of the work-in-progress data
  103. pub fn get_wip_mut(&self) -> RefMut<Option<T>> {
  104. self.wip.borrow_mut()
  105. }
  106. pub fn split(&self) -> (&T, Rc<dyn Fn(T)>) {
  107. (&self.current_val, self.setter())
  108. }
  109. pub fn setter(&self) -> Rc<dyn Fn(T)> {
  110. let slot = self.wip.clone();
  111. let callback = self.update_callback.clone();
  112. Rc::new(move |new| {
  113. callback();
  114. *slot.borrow_mut() = Some(new)
  115. })
  116. }
  117. pub fn wtih(&self, f: impl FnOnce(&mut T)) {
  118. let mut val = self.wip.borrow_mut();
  119. if let Some(inner) = val.as_mut() {
  120. f(inner);
  121. }
  122. }
  123. pub fn for_async(&self) -> UseState<T> {
  124. let UseState {
  125. current_val,
  126. wip,
  127. update_callback,
  128. update_scheuled,
  129. } = self;
  130. UseState {
  131. current_val: current_val.clone(),
  132. wip: wip.clone(),
  133. update_callback: update_callback.clone(),
  134. update_scheuled: update_scheuled.clone(),
  135. }
  136. }
  137. }
  138. impl<T: 'static + ToOwned<Owned = T>> UseState<T> {
  139. /// Gain mutable access to the new value via [`RefMut`].
  140. ///
  141. /// If `modify` is called, then the component will re-render.
  142. ///
  143. /// This method is only available when the value is a `ToOwned` type.
  144. ///
  145. /// Mutable access is derived by calling "ToOwned" (IE cloning) on the current value.
  146. ///
  147. /// To get a reference to the current value, use `.get()`
  148. pub fn modify(&self) -> RefMut<T> {
  149. // make sure we get processed
  150. self.needs_update();
  151. // Bring out the new value, cloning if it we need to
  152. // "get_mut" is locked behind "ToOwned" to make it explicit that cloning occurs to use this
  153. RefMut::map(self.wip.borrow_mut(), |slot| {
  154. if slot.is_none() {
  155. *slot = Some(self.current_val.as_ref().to_owned());
  156. }
  157. slot.as_mut().unwrap()
  158. })
  159. }
  160. pub fn inner(self) -> T {
  161. self.current_val.as_ref().to_owned()
  162. }
  163. }
  164. impl<'a, T> std::ops::Deref for UseState<T> {
  165. type Target = T;
  166. fn deref(&self) -> &Self::Target {
  167. self.get()
  168. }
  169. }
  170. // enable displaty for the handle
  171. impl<'a, T: 'static + Display> std::fmt::Display for UseState<T> {
  172. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  173. write!(f, "{}", self.current_val)
  174. }
  175. }
  176. impl<'a, V, T: PartialEq<V>> PartialEq<V> for UseState<T> {
  177. fn eq(&self, other: &V) -> bool {
  178. self.get() == other
  179. }
  180. }
  181. impl<'a, O, T: std::ops::Not<Output = O> + Copy> std::ops::Not for UseState<T> {
  182. type Output = O;
  183. fn not(self) -> Self::Output {
  184. !*self.get()
  185. }
  186. }