Skip to main content

repose_core/
effects.rs

1use std::cell::RefCell;
2use std::rc::Rc;
3
4#[derive(Clone)]
5pub struct Dispose(Rc<RefCell<Option<Box<dyn FnOnce()>>>>);
6
7impl Dispose {
8    pub fn new(f: impl FnOnce() + 'static) -> Self {
9        Self(Rc::new(RefCell::new(Some(Box::new(f)))))
10    }
11
12    /// Runs at most once (safe to call multiple times).
13    pub fn run(&self) {
14        if let Some(f) = self.0.borrow_mut().take() {
15            f()
16        }
17    }
18}
19
20/// Runs `f()` immediately and returns its `Dispose`, registering cleanup on
21/// the current scope when one exists. Like `scoped_effect`, this runs on every
22/// call - for mount-once semantics use `scoped_effect_once` or
23/// `disposable_effect`.
24pub fn effect<F>(f: F) -> Dispose
25where
26    F: FnOnce() -> Dispose + 'static,
27{
28    let d = f();
29
30    if let Some(scope) = crate::scope::current_scope() {
31        let d2 = d.clone();
32        scope.add_disposer(move || d2.run());
33    } else {
34        debug_assert!(
35            false,
36            "effect called without a current Scope; cleanup cannot be tracked"
37        );
38        log::error!("effect called without a current Scope; cleanup untracked");
39    }
40
41    d
42}
43
44/// Mount-once effect: runs `f` only the first time this call site composes.
45/// Later recompositions return the original `Dispose` without re-running setup.
46#[track_caller]
47pub fn effect_once(f: impl FnOnce() -> Dispose + 'static) -> Dispose {
48    let loc = std::panic::Location::caller();
49    let key = format!("effect:{}:{}:{}", loc.file(), loc.line(), loc.column());
50    let slot = crate::remember_with_key(key, || RefCell::new(None::<Dispose>));
51    if let Some(d) = slot.borrow().as_ref() {
52        return d.clone();
53    }
54    let d = effect(f);
55    *slot.borrow_mut() = Some(d.clone());
56    d
57}
58/// Helper to register cleanup inside effect.
59pub fn on_unmount(f: impl FnOnce() + 'static) -> Dispose {
60    Dispose::new(f)
61}