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
51pub 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
61pub 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
78pub 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 t.1.clone()
103 }
104 _ => {
105 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#[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}