wasm_react/hooks/
use_state.rs

1use super::{use_ref, RefContainer};
2use crate::react_bindings;
3use js_sys::Function;
4use std::cell::Ref;
5use wasm_bindgen::{JsValue, UnwrapThrowExt};
6
7/// Allows access to the underlying state data persisted with [`use_state()`].
8#[derive(Debug)]
9pub struct State<T> {
10  ref_container: RefContainer<Option<T>>,
11  update: Function,
12}
13
14impl<T: 'static> State<T> {
15  /// Returns a reference to the value of the state.
16  pub fn value(&self) -> Ref<'_, T> {
17    Ref::map(self.ref_container.current(), |x| {
18      x.as_ref().expect_throw("no state value available")
19    })
20  }
21
22  /// Sets the state to the return value of the given mutator closure and
23  /// rerenders the component.
24  ///
25  /// # Panics
26  ///
27  /// Panics if the value is currently borrowed.
28  pub fn set(&mut self, mutator: impl FnOnce(T) -> T) {
29    let value = self.ref_container.current_mut().take();
30    let new_value = value.map(|value| mutator(value));
31
32    self.ref_container.set_current(new_value);
33    self
34      .update
35      .call0(&JsValue::NULL)
36      .expect_throw("unable to call state update");
37  }
38}
39
40impl<T> Clone for State<T> {
41  fn clone(&self) -> Self {
42    Self {
43      ref_container: self.ref_container.clone(),
44      update: self.update.clone(),
45    }
46  }
47}
48
49/// Persist stateful data of the component.
50///
51/// Unlike the [`use_ref()`] hook, updating the state will automatically trigger
52/// a rerender of the component.
53///
54/// Unlike its React counterpart, setting the state will mutate the underlying
55/// data immediately.
56///
57/// # Example
58///
59/// ```
60/// # use wasm_react::{*, hooks::*};
61/// #
62/// # struct State { greet: &'static str }
63/// # struct C;
64/// # impl C {
65/// fn render(&self) -> VNode {
66///   let state = use_state(|| State { greet: "Hello!" });
67///
68///   use_effect({
69///     clones!(mut state);
70///
71///     move || {
72///       state.set(|mut state| {
73///         state.greet = "Welcome!";
74///         state
75///       });
76///     }
77///   }, Deps::some(( /* … */ )));
78///
79///   let vnode = h!(div).build(state.value().greet);
80///   vnode
81/// }
82/// # }
83/// ```
84pub fn use_state<T: 'static>(init: impl FnOnce() -> T) -> State<T> {
85  let mut ref_container = use_ref(None);
86
87  if ref_container.current().is_none() {
88    ref_container.set_current(Some(init()));
89  }
90
91  let update = react_bindings::use_rust_state();
92
93  State {
94    ref_container,
95    update,
96  }
97}