react/
use_state.rs

1use std::rc::Rc;
2use wasm_bindgen::{prelude::Closure, UnwrapThrowExt};
3use wasm_bindgen::{JsCast, JsValue};
4
5use wasm_bindgen::prelude::wasm_bindgen;
6
7use super::{IntoOptionalRc, IntoRc};
8
9thread_local! {
10    static CLOSURE_FREE_WITH_USIZE: Closure<dyn FnMut(usize)> =
11        Closure::wrap(Box::new(|n| { unsafe { forgotten::try_free_with_usize(n) }; }) as Box<dyn FnMut(usize)>);
12}
13
14#[wasm_bindgen]
15extern "C" {
16    #[derive(Debug, Clone, PartialEq)]
17    type MutableRefUsizeSlice;
18    #[wasm_bindgen(structural, method, getter)]
19    fn current(this: &MutableRefUsizeSlice) -> Box<[usize]>;
20    #[wasm_bindgen(structural, method, setter)]
21    fn set_current(this: &MutableRefUsizeSlice, val: Box<[usize]>);
22
23    type MySetter;
24    #[wasm_bindgen(structural, method, js_name = "set_state")]
25    fn set_state_with(this: &MySetter, get_value_from_old: &Closure<dyn Fn(usize) -> usize>);
26}
27
28impl MutableRefUsizeSlice {
29    #[inline]
30    fn current_as_vec(&self) -> Vec<usize> {
31        self.current().into_vec()
32    }
33    #[inline]
34    fn set_current_vec(&self, vec: Vec<usize>) {
35        self.set_current(vec.into_boxed_slice())
36    }
37    #[inline]
38    fn push_into_current(&self, state: usize) {
39        let mut arr = self.current_as_vec();
40        arr.push(state);
41        self.set_current_vec(arr);
42    }
43}
44
45#[derive(Debug)]
46pub struct StateSetter<T: ?Sized> {
47    js_setter: react_sys::UseStateUsizeObjectSetter,
48    ref_state_updaters: MutableRefUsizeSlice,
49    _data: std::marker::PhantomData<T>,
50}
51
52impl<T: ?Sized> PartialEq for StateSetter<T> {
53    fn eq(&self, other: &Self) -> bool {
54        self.ref_state_updaters == other.ref_state_updaters
55    }
56}
57
58impl<T: ?Sized> Eq for StateSetter<T> {}
59
60impl<T: ?Sized> Clone for StateSetter<T> {
61    fn clone(&self) -> Self {
62        Self {
63            js_setter: self.js_setter.clone(),
64            ref_state_updaters: self.ref_state_updaters.clone(),
65            _data: std::marker::PhantomData,
66        }
67    }
68}
69
70impl<T: 'static + ?Sized> StateSetter<T> {
71    fn new(
72        js_setter: react_sys::UseStateUsizeObjectSetter,
73        ref_state_updaters: MutableRefUsizeSlice,
74    ) -> Self {
75        Self {
76            js_setter,
77            ref_state_updaters,
78            _data: std::marker::PhantomData,
79        }
80    }
81
82    pub fn set<V: IntoRc<T>>(&self, v: V) {
83        let v: Rc<T> = v.into_rc();
84        let k = forgotten::forget(v).into_shared();
85        let k = k.as_usize();
86        self.js_setter.set_state(*k)
87    }
88
89    pub fn set_from_old<R: IntoRc<T>, F: 'static + Fn(&Rc<T>) -> R>(&self, dispatch: F) {
90        self.set_optional_from_old(move |old| Some((&dispatch)(old).into_rc()))
91    }
92
93    pub fn set_optional_from_old<R: IntoOptionalRc<T>, F: 'static + Fn(&Rc<T>) -> R>(
94        &self,
95        dispatch: F,
96    ) {
97        let ref_state_updaters = self.ref_state_updaters.clone();
98        let closure = move |old_key| {
99            let old = unsafe { forgotten::try_get_with_usize::<Rc<T>>(&old_key) };
100            let old = old.unwrap_throw();
101
102            let new_state: Option<Rc<T>> = dispatch(&old).into_optional_rc();
103
104            if let Some(new_state) = new_state {
105                let new_k = forgotten::forget(new_state).into_shared();
106                let new_k = *new_k.as_usize();
107                ref_state_updaters.push_into_current(new_k);
108                new_k
109            } else {
110                old_key
111            }
112        };
113
114        let closure = Closure::wrap(Box::new(closure) as Box<dyn Fn(usize) -> usize>);
115
116        let (k, closure) = forgotten::forget_and_get(closure);
117        let k = k.into_shared();
118        let k = k.as_usize();
119
120        self.ref_state_updaters.push_into_current(*k);
121
122        let setter: &MySetter = self.js_setter.unchecked_ref();
123        setter.set_state_with(closure.as_ref());
124    }
125}
126
127pub fn use_state_value<T: 'static + ?Sized>(initial_value: Rc<T>) -> (Rc<T>, StateSetter<T>) {
128    use_state(move || Rc::clone(&initial_value))
129}
130
131pub fn use_state<T: 'static + ?Sized, F: Fn() -> Rc<T>>(
132    get_initial_value: F,
133) -> (Rc<T>, StateSetter<T>) {
134    let ref_all_persisted = use_ref_all_persisted();
135
136    let ret = react_sys::use_state_usize_with(&mut || {
137        let state = get_initial_value();
138        let k = forgotten::forget(state);
139        let k = k.into_shared();
140        let k = *k.as_usize();
141        let mut arr = ref_all_persisted.current().into_vec();
142        arr.push(k);
143        ref_all_persisted.set_current(arr.into_boxed_slice());
144        k
145    });
146
147    let state = ret.value();
148
149    use_clean_outdated_persisted(state, ref_all_persisted.clone());
150
151    let state = unsafe { forgotten::try_get_with_usize::<Rc<T>>(&state) };
152    let state = state.unwrap_throw();
153    let state = Rc::clone(state.as_ref());
154
155    let setter = ret.setter();
156
157    (state, StateSetter::new(setter, ref_all_persisted))
158}
159
160fn use_ref_all_persisted() -> MutableRefUsizeSlice {
161    let ref_all_persisted = react_sys::use_ref(&JsValue::UNDEFINED);
162    let not_initialized = ref_all_persisted.current().is_falsy();
163    let ref_all_persisted: MutableRefUsizeSlice = ref_all_persisted.unchecked_into();
164    if not_initialized {
165        ref_all_persisted.set_current_vec(Vec::<usize>::with_capacity(4));
166    }
167    ref_all_persisted
168}
169
170fn use_clean_outdated_persisted(state: usize, ref_all_persisted: MutableRefUsizeSlice) {
171    crate::use_effect!(
172        (state, ref_all_persisted) => {
173            let state = state.as_ref();
174            let state_updaters = ref_all_persisted.current();
175            if state_updaters.len()>0 {
176                for k in state_updaters.iter() {
177                    if k == state {
178                        continue;
179                    }
180                    #[cfg(debug_assertions)]
181                    {
182                        let cleaned = unsafe { forgotten::try_free_with_usize(*k) };
183                        if !cleaned {
184                            web_sys::console::error_2(
185                                &"use_state*: forgotten key invalid or already dropped. forgotten::Key = "
186                                    .into(),
187                                &(*k).into(),
188                            )
189                        }
190                    }
191
192                    #[cfg(not(debug_assertions))]
193                    unsafe {
194                        forgotten::try_free_with_usize(k)
195                    };
196                }
197                let mut state_updaters = Vec::with_capacity(4);
198                state_updaters.push(*state);
199                ref_all_persisted.set_current_vec(state_updaters);
200            }
201        }
202    );
203}
204
205#[macro_export]
206macro_rules! use_state {
207    ($e:expr) => {
208        $crate::use_state_value($crate::auto_wrap_rc!($e))
209    };
210    (() => $e:expr) => {
211        $crate::use_state(move || $crate::auto_wrap_rc!($e))
212    };
213}