hooks.rs 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. //! Useful, foundational hooks that 3rd parties can implement.
  2. //! Currently implemented:
  3. //! - [x] use_ref
  4. //! - [x] use_state
  5. //! - [ ] use_reducer
  6. //! - [ ] use_effect
  7. pub use use_ref_def::use_ref;
  8. pub use use_state_def::use_state;
  9. mod use_state_def {
  10. use crate::innerlude::*;
  11. use std::{cell::RefCell, ops::DerefMut, rc::Rc};
  12. struct UseState<T: 'static> {
  13. new_val: Rc<RefCell<Option<T>>>,
  14. current_val: T,
  15. caller: Box<dyn Fn(T) + 'static>,
  16. }
  17. /// Store state between component renders!
  18. /// When called, this hook retrives a stored value and provides a setter to update that value.
  19. /// When the setter is called, the component is re-ran with the new value.
  20. ///
  21. /// This is behaves almost exactly the same way as React's "use_state".
  22. ///
  23. /// Usage:
  24. /// ```ignore
  25. /// static Example: FC<()> = |ctx| {
  26. /// let (counter, set_counter) = use_state(ctx, || 0);
  27. /// let increment = |_| set_couter(counter + 1);
  28. /// let decrement = |_| set_couter(counter + 1);
  29. ///
  30. /// html! {
  31. /// <div>
  32. /// <h1>"Counter: {counter}" </h1>
  33. /// <button onclick={increment}> "Increment" </button>
  34. /// <button onclick={decrement}> "Decrement" </button>
  35. /// </div>
  36. /// }
  37. /// }
  38. /// ```
  39. pub fn use_state<'a, 'c, T: 'static, F: FnOnce() -> T>(
  40. ctx: &'c Context<'a>,
  41. initial_state_fn: F,
  42. ) -> (&'a T, &'a impl Fn(T)) {
  43. ctx.use_hook(
  44. move || UseState {
  45. new_val: Rc::new(RefCell::new(None)),
  46. current_val: initial_state_fn(),
  47. caller: Box::new(|_| println!("setter called!")),
  48. },
  49. move |hook| {
  50. log::debug!("Use_state set called");
  51. let inner = hook.new_val.clone();
  52. let scheduled_update = ctx.schedule_update();
  53. // get ownership of the new val and replace the current with the new
  54. // -> as_ref -> borrow_mut -> deref_mut -> take
  55. // -> rc -> &RefCell -> RefMut -> &Option<T> -> T
  56. if let Some(new_val) = hook.new_val.as_ref().borrow_mut().deref_mut().take() {
  57. hook.current_val = new_val;
  58. }
  59. // todo: swap out the caller with a subscription call and an internal update
  60. hook.caller = Box::new(move |new_val| {
  61. // update the setter with the new value
  62. let mut new_inner = inner.as_ref().borrow_mut();
  63. *new_inner = Some(new_val);
  64. // Ensure the component gets updated
  65. scheduled_update();
  66. });
  67. // box gets derefed into a ref which is then taken as ref with the hook
  68. (&hook.current_val, &hook.caller)
  69. },
  70. |_| {},
  71. )
  72. }
  73. }
  74. mod use_ref_def {
  75. use crate::innerlude::*;
  76. use std::{cell::RefCell, ops::DerefMut};
  77. pub struct UseRef<T: 'static> {
  78. _current: RefCell<T>,
  79. }
  80. impl<T: 'static> UseRef<T> {
  81. fn new(val: T) -> Self {
  82. Self {
  83. _current: RefCell::new(val),
  84. }
  85. }
  86. pub fn modify(&self, modifier: impl FnOnce(&mut T)) {
  87. let mut val = self._current.borrow_mut();
  88. let val_as_ref = val.deref_mut();
  89. modifier(val_as_ref);
  90. }
  91. pub fn current(&self) -> std::cell::Ref<'_, T> {
  92. self._current.borrow()
  93. }
  94. }
  95. /// Store a mutable value between renders!
  96. /// To read the value, borrow the ref.
  97. /// To change it, use modify.
  98. /// Modifications to this value do not cause updates to the component
  99. /// Attach to inner context reference, so context can be consumed
  100. pub fn use_ref<'a, T: 'static>(
  101. ctx: &'_ Context<'a>,
  102. initial_state_fn: impl FnOnce() -> T + 'static,
  103. ) -> &'a UseRef<T> {
  104. ctx.use_hook(|| UseRef::new(initial_state_fn()), |state| &*state, |_| {})
  105. }
  106. }
  107. mod use_reducer_def {
  108. use crate::innerlude::*;
  109. use std::{cell::RefCell, ops::DerefMut, rc::Rc};
  110. struct UseReducer<T: 'static, R: 'static> {
  111. new_val: Rc<RefCell<Option<T>>>,
  112. current_val: T,
  113. caller: Box<dyn Fn(R) + 'static>,
  114. }
  115. /// Store state between component renders!
  116. /// When called, this hook retrives a stored value and provides a setter to update that value.
  117. /// When the setter is called, the component is re-ran with the new value.
  118. ///
  119. /// This is behaves almost exactly the same way as React's "use_state".
  120. ///
  121. pub fn use_reducer<'a, 'c, State: 'static, Action: 'static>(
  122. ctx: &'c Context<'a>,
  123. initial_state_fn: impl FnOnce() -> State,
  124. reducer: impl Fn(&mut State, Action),
  125. ) -> (&'a State, &'a impl Fn(Action)) {
  126. ctx.use_hook(
  127. move || UseReducer {
  128. new_val: Rc::new(RefCell::new(None)),
  129. current_val: initial_state_fn(),
  130. caller: Box::new(|_| println!("setter called!")),
  131. },
  132. move |hook| {
  133. let inner = hook.new_val.clone();
  134. let scheduled_update = ctx.schedule_update();
  135. // get ownership of the new val and replace the current with the new
  136. // -> as_ref -> borrow_mut -> deref_mut -> take
  137. // -> rc -> &RefCell -> RefMut -> &Option<T> -> T
  138. if let Some(new_val) = hook.new_val.as_ref().borrow_mut().deref_mut().take() {
  139. hook.current_val = new_val;
  140. }
  141. // todo: swap out the caller with a subscription call and an internal update
  142. hook.caller = Box::new(move |new_val| {
  143. // update the setter with the new value
  144. // let mut new_inner = inner.as_ref().borrow_mut();
  145. // *new_inner = Some(new_val);
  146. // Ensure the component gets updated
  147. scheduled_update();
  148. });
  149. // box gets derefed into a ref which is then taken as ref with the hook
  150. (&hook.current_val, &hook.caller)
  151. },
  152. |_| {},
  153. )
  154. }
  155. // #[cfg(test)]
  156. mod tests {
  157. use super::*;
  158. use crate::prelude::*;
  159. use bumpalo::Bump;
  160. enum Actions {
  161. Incr,
  162. Decr,
  163. }
  164. #[allow(unused)]
  165. static Example: FC<()> = |ctx, props| {
  166. let (count, reduce) = use_reducer(
  167. &ctx,
  168. || 0,
  169. |count, action| match action {
  170. Actions::Incr => *count += 1,
  171. Actions::Decr => *count -= 1,
  172. },
  173. );
  174. ctx.render(rsx! {
  175. div {
  176. h1 {"Count: {count}"}
  177. button {
  178. "Increment"
  179. onclick: move |_| reduce(Actions::Incr)
  180. }
  181. button {
  182. "Decrement"
  183. onclick: move |_| reduce(Actions::Decr)
  184. }
  185. }
  186. })
  187. };
  188. }
  189. }