react/
use_effect.rs

1use std::rc::Rc;
2
3use wasm_bindgen::prelude::*;
4
5pub trait IntoOptionalCleanFn: Sized {
6    type CleanFn: 'static + FnOnce();
7    fn into_optional_clean_fn(self) -> Option<Self::CleanFn>;
8}
9
10impl<F> IntoOptionalCleanFn for F
11where
12    F: 'static + FnOnce(),
13{
14    type CleanFn = F;
15
16    #[inline]
17    fn into_optional_clean_fn(self) -> Option<F> {
18        Some(self)
19    }
20}
21
22impl<T: IntoOptionalCleanFn> IntoOptionalCleanFn for Option<T> {
23    type CleanFn = <T as IntoOptionalCleanFn>::CleanFn;
24
25    #[inline]
26    fn into_optional_clean_fn(self) -> Option<Self::CleanFn> {
27        self.and_then(IntoOptionalCleanFn::into_optional_clean_fn)
28    }
29}
30
31impl IntoOptionalCleanFn for () {
32    type CleanFn = &'static dyn Fn();
33
34    #[inline]
35    fn into_optional_clean_fn(self) -> Option<Self::CleanFn> {
36        None
37    }
38}
39
40fn effect_into_js<C: IntoOptionalCleanFn, F: 'static + FnOnce() -> C>(effect: F) -> JsValue {
41    Closure::once_into_js(move || {
42        let clean = effect().into_optional_clean_fn();
43        if let Some(clean) = clean {
44            Closure::once_into_js(move || clean())
45        } else {
46            JsValue::UNDEFINED
47        }
48    })
49}
50
51/// ```js
52/// React.useEffect(() => {
53///     effect()
54/// })
55/// ```
56pub fn use_effect_on_each_render<C: IntoOptionalCleanFn, F: 'static + FnOnce() -> C>(effect: F) {
57    let effect = effect_into_js(effect);
58    react_sys::use_effect_on_each_render(effect);
59}
60
61/// ```js
62/// React.useEffect(() => {
63///     effect()
64/// }, [])
65/// ```
66pub fn use_effect_on_mounted<C: 'static + IntoOptionalCleanFn, F: 'static + FnOnce() -> C>(
67    effect: F,
68) {
69    let ref_effect = react_sys::use_ref(&JsValue::UNDEFINED);
70    let mut effect_js = ref_effect.current();
71    if effect_js.is_falsy() {
72        effect_js = effect_into_js(effect);
73        ref_effect.set_current(effect_js.clone());
74    }
75    react_sys::use_effect(effect_js, js_sys::Array::new());
76}
77
78/// `React.useEffect` with exactly one dependency.
79/// To use multiple dependencies, see [`use_effect`].
80///
81/// ```js
82/// React.useEffect(() => {
83///     effect(dep)
84/// }, [dep])
85/// ```
86///
87///
88pub fn use_effect_one<
89    D: 'static + PartialEq,
90    C: 'static + IntoOptionalCleanFn,
91    F: 'static + FnOnce(Rc<D>) -> C,
92>(
93    effect: F,
94    dep: Rc<D>,
95) {
96    let dep_and_value = crate::use_ref_cell::<Option<(Rc<D>, (JsValue, js_sys::Array))>>(None);
97    let mut dep_and_value = dep_and_value.0.borrow_mut();
98
99    let (effect, dep_arr) = match &*dep_and_value {
100        Some(t) if &t.0 == &dep => {
101            // dep not changed
102            t.1.clone()
103        }
104        _ => {
105            // dep changed
106            let effect = {
107                let dep = Rc::clone(&dep);
108                effect_into_js(move || effect(dep))
109            };
110            let dep_arr = js_sys::Array::of1(&effect);
111            let new_v = (effect, dep_arr);
112
113            *dep_and_value = Some((dep, new_v.clone()));
114
115            new_v
116        }
117    };
118
119    react_sys::use_effect(effect, dep_arr);
120}
121
122///
123/// ## `use_effect` with dependencies.
124///
125/// The closure should not capture any local variables.
126/// If you want to use a variable without depend on it,
127/// you can `use_ref(value)` then depend on it;
128///
129///
130/// ```no_run
131/// # use wasm_bindgen::JsValue;
132/// # use react::use_effect;
133/// let state = 0;
134/// let message = "The state is ";
135/// use_effect!((
136///     // depend on `state`
137///     state,
138///     // depend on an expression and name it `message`
139///     message = message.to_string(),
140/// ) => {
141///     web_sys::console::log_2(&JsValue::from(message.as_ref()), &JsValue::from(*state));
142/// })
143/// ```
144///
145/// [`use_effect_on_mounted`]
146///
147/// ```no_run
148/// # use wasm_bindgen::JsValue;
149/// # use react::use_effect;
150/// # let do_something = || {};
151/// use_effect!(() => {
152///     do_something();
153/// })
154/// ```
155///
156/// [`use_effect_on_each_render`]
157///
158/// ```no_run
159/// # use wasm_bindgen::JsValue;
160/// # use react::use_effect;
161/// # let do_something = || {};
162/// use_effect!(on_each_render () => {
163///     do_something();
164/// })
165/// ```
166///
167#[macro_export]
168macro_rules! use_effect {
169    (on_each_render () => $e:expr) => {
170        $crate::use_effect_on_each_render(move || $e)
171    };
172    (() => $e:expr) => {
173        $crate::use_effect_on_mounted(move || $e)
174    };
175    (($( $dep:ident $(= $dep_expr:expr)? ),+ $(,)?) => $e:expr ) => {{
176        $crate::use_effect_one(
177            move |dep_tuple| {
178                $crate::__impl_let_dep_list!( { dep_tuple } $($dep)+ );
179                $e
180            },
181            {
182                let dep = (
183                    $($crate::__impl_pass_dep!( $dep $(= $dep_expr)? )),+
184                );
185                $crate::auto_wrap_rc!(dep)
186            },
187        )
188    }};
189}