maple_core/
reactive.rs

1//! Reactive primitives.
2
3mod effect;
4mod signal;
5mod signal_vec;
6
7pub use effect::*;
8pub use signal::*;
9pub use signal_vec::*;
10
11/// Creates a new reactive root. Generally, you won't need this method as it is called automatically in [`render`](crate::render()).
12///
13/// # Example
14/// ```
15/// use maple_core::prelude::*;
16///
17/// let trigger = Signal::new(());
18/// let counter = Signal::new(0);
19///
20/// let owner = create_root(cloned!((trigger, counter) => move || {
21///     create_effect(move || {
22///         trigger.get(); // subscribe to trigger
23///         counter.set(*counter.get_untracked() + 1);
24///     });
25/// }));
26///
27/// assert_eq!(*counter.get(), 1);
28///
29/// trigger.set(());
30/// assert_eq!(*counter.get(), 2);
31///
32/// drop(owner);
33/// trigger.set(());
34/// assert_eq!(*counter.get(), 2); // should not be updated because owner was dropped
35/// ```
36#[must_use = "create_root returns the owner of the effects created inside this scope"]
37pub fn create_root<'a>(callback: impl FnOnce() + 'a) -> Owner {
38    /// Internal implementation: use dynamic dispatch to reduce code bloat.
39    fn internal<'a>(callback: Box<dyn FnOnce() + 'a>) -> Owner {
40        OWNER.with(|owner| {
41            let outer_owner = owner.replace(Some(Owner::new()));
42            callback();
43
44            owner
45                .replace(outer_owner)
46                .expect("Owner should be valid inside the reactive root")
47        })
48    }
49
50    internal(Box::new(callback))
51}
52
53#[cfg(test)]
54mod tests {
55    use std::cell::RefCell;
56    use std::rc::Rc;
57
58    use super::*;
59
60    #[test]
61    fn drop_owner_inside_effect() {
62        let owner = Rc::new(RefCell::new(None));
63
64        *owner.borrow_mut() = Some(create_root({
65            let owner = Rc::clone(&owner);
66            move || {
67                let owner = owner.take();
68                drop(owner)
69            }
70        }));
71    }
72}