1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
use std::borrow::BorrowMut;

use crate::construction::{hook::HookBuilder, types::HookReturn};

use super::{use_ref, Reference};

struct StoredEffect<Args, OnDrop>
where
    OnDrop: FnOnce() + 'static,
    Args: PartialEq + Clone + 'static,
{
    args: Args,
    on_drop: Option<OnDrop>,
}

impl<Args, OnDrop> Drop for StoredEffect<Args, OnDrop>
where
    OnDrop: FnOnce() + 'static,
    Args: PartialEq + Clone + 'static,
{
    fn drop(&mut self) {
        if let Some(od) = self.on_drop.take() {
            od();
        }
    }
}

/** Runs the function with the arg as argument when the arg changes.

Takes a single-argument function and the arg.

The arg must be [PartialEq] + [Clone] + `'static`, because we need to store and compare it.

The effect runs as the component renders. Updating states inside `use_effect` will queue another render.

If you want something to run after the component completed rendering, consider using [crate::executor::run_later].

This takes a function rather than a closure, so every dependency must be passed through `args`.
For React devs, this is equivalent to `react-hooks/exhaustive-deps` being enforced.

Example using this to change the page title when some data change:

```
let (cc, _) = cc.hook(use_effect, (
    |deps: (String, u32)| {
        let title = format!("Profile - {}, age {}", deps.0, deps.1);
        web_sys::window().unwrap().document().unwrap().set_title(&title);
    }, (name, number)
));

```

 */
pub fn use_effect<Args, OnDrop>(
    cc: HookBuilder,
    (func, args): (fn(Args) -> OnDrop, Args),
) -> impl HookReturn<()>
where
    OnDrop: FnOnce() + 'static,
    Args: PartialEq + Clone + 'static,
{
    use_effect_relaxed(cc, (func, args))
}

/** Like [use_effect], but takes a closure instead of a function.

This mean you don't have to pass every dependency through `args`.
For React devs, this is equivalent to `react-hooks/exhaustive-deps` not being enforced.
 */
pub fn use_effect_relaxed<Args, OnDrop, Eff>(
    cc: HookBuilder,
    (func, args): (Eff, Args),
) -> impl HookReturn<()>
where
    OnDrop: FnOnce() + 'static,
    Args: PartialEq + Clone + 'static,
    Eff: FnOnce(Args) -> OnDrop,
{
    let cc = cc.init();
    let (cc, store): (_, Reference<Option<StoredEffect<Args, OnDrop>>>) = cc.hook(use_ref, ());
    let should_run = store
        .visit_mut_with(|opt| match opt {
            Some(stored) => {
                if stored.args != args {
                    stored.args = args;
                    true
                } else {
                    false
                }
            }
            opt_none => {
                *opt_none = Some(StoredEffect {
                    args,
                    on_drop: None,
                });
                true
            }
        })
        .unwrap();
    if should_run {
        store
            .visit_mut_with(|opt| {
                let stored = opt.as_mut().unwrap().borrow_mut();
                if let Some(od) = stored.on_drop.take() {
                    od();
                }
                stored.on_drop = Some(func(stored.args.clone()));
            })
            .unwrap();
    }
    (cc, ())
}