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}