Skip to main content

react_rs_core/
effect.rs

1use crate::runtime::{ScopeId, RUNTIME};
2
3pub fn create_effect<F>(f: F)
4where
5    F: Fn() + 'static,
6{
7    let effect_id = RUNTIME.with(|rt| rt.borrow_mut().register_effect(f));
8    run_effect(effect_id);
9}
10
11pub fn on_cleanup(f: impl FnOnce() + 'static) {
12    RUNTIME.with(|rt| {
13        rt.borrow_mut().add_cleanup(f);
14    });
15}
16
17pub fn create_scope() -> ScopeId {
18    RUNTIME.with(|rt| rt.borrow_mut().create_scope())
19}
20
21pub fn dispose_scope(scope_id: ScopeId) {
22    RUNTIME.with(|rt| rt.borrow_mut().dispose_scope(scope_id));
23}
24
25pub(crate) fn run_effect(id: usize) {
26    RUNTIME.with(|rt| {
27        if rt.borrow().is_effect_disposed(id) {
28            return;
29        }
30        rt.borrow_mut().run_cleanups(id);
31        let prev = rt.borrow_mut().set_current_effect(Some(id));
32        let effect_fn = rt.borrow().clone_effect(id);
33
34        if let Some(f) = effect_fn {
35            f();
36        }
37
38        rt.borrow_mut().set_current_effect(prev);
39    });
40}
41
42pub(crate) fn flush_effects() {
43    loop {
44        let effect_id = RUNTIME.with(|rt| rt.borrow_mut().pop_pending_effect());
45        match effect_id {
46            Some(id) => run_effect(id),
47            None => break,
48        }
49    }
50}
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55    use crate::signal::create_signal;
56    use std::cell::RefCell;
57    use std::rc::Rc;
58
59    #[test]
60    fn test_effect_runs_immediately() {
61        let ran = Rc::new(RefCell::new(false));
62        let ran_clone = ran.clone();
63
64        create_effect(move || {
65            *ran_clone.borrow_mut() = true;
66        });
67
68        assert!(*ran.borrow());
69    }
70
71    #[test]
72    fn test_effect_auto_run() {
73        let (count, set_count) = create_signal(0);
74        let effect_ran = Rc::new(RefCell::new(0));
75        let effect_ran_clone = effect_ran.clone();
76
77        create_effect(move || {
78            let _ = count.get();
79            *effect_ran_clone.borrow_mut() += 1;
80        });
81
82        assert_eq!(*effect_ran.borrow(), 1);
83        set_count.set(1);
84        assert_eq!(*effect_ran.borrow(), 2);
85    }
86
87    #[test]
88    fn test_effect_only_runs_when_tracked_signal_changes() {
89        let (count, set_count) = create_signal(0);
90        let (other, _set_other) = create_signal(100);
91        let effect_ran = Rc::new(RefCell::new(0));
92        let effect_ran_clone = effect_ran.clone();
93
94        create_effect(move || {
95            let _ = count.get();
96            let _ = other.get_untracked();
97            *effect_ran_clone.borrow_mut() += 1;
98        });
99
100        assert_eq!(*effect_ran.borrow(), 1);
101
102        set_count.set(1);
103        assert_eq!(*effect_ran.borrow(), 2);
104
105        set_count.set(2);
106        assert_eq!(*effect_ran.borrow(), 3);
107    }
108
109    #[test]
110    fn test_effect_disposal() {
111        let (count, set_count) = create_signal(0);
112        let effect_ran = Rc::new(RefCell::new(0));
113        let effect_ran_clone = effect_ran.clone();
114
115        let scope = create_scope();
116        create_effect(move || {
117            let _ = count.get();
118            *effect_ran_clone.borrow_mut() += 1;
119        });
120
121        assert_eq!(*effect_ran.borrow(), 1);
122        set_count.set(1);
123        assert_eq!(*effect_ran.borrow(), 2);
124
125        dispose_scope(scope);
126
127        set_count.set(2);
128        assert_eq!(*effect_ran.borrow(), 2); // NOT re-triggered
129    }
130
131    #[test]
132    fn test_nested_scope_disposal() {
133        let (count, set_count) = create_signal(0);
134        let inner_ran = Rc::new(RefCell::new(0));
135        let inner_ran_clone = inner_ran.clone();
136
137        let outer = create_scope();
138        let _inner = create_scope();
139        create_effect(move || {
140            let _ = count.get();
141            *inner_ran_clone.borrow_mut() += 1;
142        });
143
144        assert_eq!(*inner_ran.borrow(), 1);
145        set_count.set(1);
146        assert_eq!(*inner_ran.borrow(), 2);
147
148        dispose_scope(outer); // disposing outer also disposes inner
149
150        set_count.set(2);
151        assert_eq!(*inner_ran.borrow(), 2); // NOT re-triggered
152    }
153
154    #[test]
155    fn test_on_cleanup_called_on_dispose() {
156        let cleanup_called = Rc::new(RefCell::new(false));
157        let cleanup_clone = cleanup_called.clone();
158
159        let scope = create_scope();
160        create_effect(move || {
161            let cc = cleanup_clone.clone();
162            on_cleanup(move || {
163                *cc.borrow_mut() = true;
164            });
165        });
166
167        assert!(!*cleanup_called.borrow());
168        dispose_scope(scope);
169        assert!(*cleanup_called.borrow());
170    }
171
172    #[test]
173    fn test_on_cleanup_called_on_rerun() {
174        let (count, set_count) = create_signal(0);
175        let cleanup_count = Rc::new(RefCell::new(0));
176        let cleanup_clone = cleanup_count.clone();
177
178        create_effect(move || {
179            let _ = count.get();
180            let cc = cleanup_clone.clone();
181            on_cleanup(move || {
182                *cc.borrow_mut() += 1;
183            });
184        });
185
186        assert_eq!(*cleanup_count.borrow(), 0);
187        set_count.set(1); // re-run triggers cleanup from first run
188        assert_eq!(*cleanup_count.borrow(), 1);
189        set_count.set(2);
190        assert_eq!(*cleanup_count.borrow(), 2);
191    }
192
193    #[test]
194    fn test_multiple_effects() {
195        let (count, set_count) = create_signal(0);
196        let effect1_ran = Rc::new(RefCell::new(0));
197        let effect2_ran = Rc::new(RefCell::new(0));
198
199        let e1 = effect1_ran.clone();
200        let c1 = count.clone();
201        create_effect(move || {
202            let _ = c1.get();
203            *e1.borrow_mut() += 1;
204        });
205
206        let e2 = effect2_ran.clone();
207        create_effect(move || {
208            let _ = count.get();
209            *e2.borrow_mut() += 1;
210        });
211
212        assert_eq!(*effect1_ran.borrow(), 1);
213        assert_eq!(*effect2_ran.borrow(), 1);
214
215        set_count.set(1);
216
217        assert_eq!(*effect1_ran.borrow(), 2);
218        assert_eq!(*effect2_ran.borrow(), 2);
219    }
220}