hooks.rs 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  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. let inner = hook.new_val.clone();
  51. let scheduled_update = ctx.schedule_update();
  52. // get ownership of the new val and replace the current with the new
  53. // -> as_ref -> borrow_mut -> deref_mut -> take
  54. // -> rc -> &RefCell -> RefMut -> &Option<T> -> T
  55. if let Some(new_val) = hook.new_val.as_ref().borrow_mut().deref_mut().take() {
  56. hook.current_val = new_val;
  57. }
  58. // todo: swap out the caller with a subscription call and an internal update
  59. hook.caller = Box::new(move |new_val| {
  60. // update the setter with the new value
  61. let mut new_inner = inner.as_ref().borrow_mut();
  62. *new_inner = Some(new_val);
  63. // Ensure the component gets updated
  64. scheduled_update();
  65. });
  66. // box gets derefed into a ref which is then taken as ref with the hook
  67. (&hook.current_val, &hook.caller)
  68. },
  69. |_| {},
  70. )
  71. }
  72. }
  73. mod use_ref_def {
  74. use crate::innerlude::*;
  75. use std::{cell::RefCell, ops::DerefMut};
  76. pub struct UseRef<T: 'static> {
  77. _current: RefCell<T>,
  78. }
  79. impl<T: 'static> UseRef<T> {
  80. fn new(val: T) -> Self {
  81. Self {
  82. _current: RefCell::new(val),
  83. }
  84. }
  85. pub fn modify(&self, modifier: impl FnOnce(&mut T)) {
  86. let mut val = self._current.borrow_mut();
  87. let val_as_ref = val.deref_mut();
  88. modifier(val_as_ref);
  89. }
  90. pub fn current(&self) -> std::cell::Ref<'_, T> {
  91. self._current.borrow()
  92. }
  93. }
  94. /// Store a mutable value between renders!
  95. /// To read the value, borrow the ref.
  96. /// To change it, use modify.
  97. /// Modifications to this value do not cause updates to the component
  98. /// Attach to inner context reference, so context can be consumed
  99. pub fn use_ref<'a, T: 'static>(
  100. ctx: &'_ Context<'a>,
  101. initial_state_fn: impl FnOnce() -> T + 'static,
  102. ) -> &'a UseRef<T> {
  103. ctx.use_hook(|| UseRef::new(initial_state_fn()), |state| &*state, |_| {})
  104. }
  105. }