sycamore_reactive/
effects.rs
1use std::cell::RefCell;
4use std::rc::Rc;
5
6use crate::create_memo;
7
8#[cfg_attr(debug_assertions, track_caller)]
30pub fn create_effect(f: impl FnMut() + 'static) {
31 create_memo(f);
32}
33
34#[cfg_attr(debug_assertions, track_caller)]
61pub fn create_effect_initial<T: 'static>(
62 initial: impl FnOnce() -> (Box<dyn FnMut() + 'static>, T) + 'static,
63) -> T {
64 let ret = Rc::new(RefCell::new(None));
65 let mut initial = Some(initial);
66 let mut effect = None;
67
68 create_effect({
69 let ret = Rc::clone(&ret);
70 move || {
71 if let Some(initial) = initial.take() {
72 let (new_f, value) = initial();
73 effect = Some(new_f);
74 *ret.borrow_mut() = Some(value);
75 } else {
76 effect.as_mut().unwrap()()
77 }
78 }
79 });
80
81 ret.take().unwrap()
82}
83
84#[cfg(test)]
85mod tests {
86 use crate::*;
87
88 #[test]
89 fn effect() {
90 let _ = create_root(|| {
91 let state = create_signal(0);
92
93 let double = create_signal(-1);
94
95 create_effect(move || {
96 double.set(state.get() * 2);
97 });
98 assert_eq!(double.get(), 0); state.set(1);
101 assert_eq!(double.get(), 2);
102 state.set(2);
103 assert_eq!(double.get(), 4);
104 });
105 }
106
107 #[test]
108 fn effect_with_explicit_dependencies() {
109 let _ = create_root(|| {
110 let state = create_signal(0);
111
112 let double = create_signal(-1);
113
114 create_effect(on(state, move || {
115 double.set(state.get() * 2);
116 }));
117 assert_eq!(double.get(), 0); state.set(1);
120 assert_eq!(double.get(), 2);
121 state.set(2);
122 assert_eq!(double.get(), 4);
123 });
124 }
125
126 #[test]
127 fn effect_cannot_create_infinite_loop() {
128 let _ = create_root(|| {
129 let state = create_signal(0);
130 create_effect(move || {
131 state.track();
132 state.set(0);
133 });
134 state.set(0);
135 });
136 }
137
138 #[test]
139 fn effect_should_only_subscribe_once_to_same_signal() {
140 let _ = create_root(|| {
141 let state = create_signal(0);
142
143 let counter = create_signal(0);
144 create_effect(move || {
145 counter.set(counter.get_untracked() + 1);
146
147 state.track();
149 state.track();
150 });
151
152 assert_eq!(counter.get(), 1);
153
154 state.set(1);
155 assert_eq!(counter.get(), 2);
156 });
157 }
158
159 #[test]
160 fn effect_should_recreate_dependencies_each_time() {
161 let _ = create_root(|| {
162 let condition = create_signal(true);
163
164 let state1 = create_signal(0);
165 let state2 = create_signal(1);
166
167 let counter = create_signal(0);
168 create_effect(move || {
169 counter.set(counter.get_untracked() + 1);
170
171 if condition.get() {
172 state1.track();
173 } else {
174 state2.track();
175 }
176 });
177
178 assert_eq!(counter.get(), 1);
179
180 state1.set(1);
181 assert_eq!(counter.get(), 2);
182
183 state2.set(1);
184 assert_eq!(counter.get(), 2); condition.set(false);
187 assert_eq!(counter.get(), 3);
188
189 state1.set(2);
190 assert_eq!(counter.get(), 3); state2.set(2);
193 assert_eq!(counter.get(), 4); });
195 }
196
197 #[test]
198 fn outer_effects_run_first() {
199 let _ = create_root(|| {
200 let trigger = create_signal(());
201
202 let outer_counter = create_signal(0);
203 let inner_counter = create_signal(0);
204
205 create_effect(move || {
206 trigger.track();
207 outer_counter.set(outer_counter.get_untracked() + 1);
208
209 create_effect(move || {
210 trigger.track();
211 inner_counter.set(inner_counter.get_untracked() + 1);
212 });
213 });
214
215 assert_eq!(outer_counter.get(), 1);
216 assert_eq!(inner_counter.get(), 1);
217
218 trigger.set(());
219
220 assert_eq!(outer_counter.get(), 2);
221 assert_eq!(inner_counter.get(), 2);
222 });
223 }
224
225 #[test]
226 fn destroy_effects_on_scope_dispose() {
227 let _ = create_root(|| {
228 let counter = create_signal(0);
229
230 let trigger = create_signal(());
231
232 let child_scope = create_child_scope(move || {
233 create_effect(move || {
234 trigger.track();
235 counter.set(counter.get_untracked() + 1);
236 });
237 });
238
239 assert_eq!(counter.get(), 1);
240
241 trigger.set(());
242 assert_eq!(counter.get(), 2);
243
244 child_scope.dispose();
245 trigger.set(());
246 assert_eq!(counter.get(), 2); });
248 }
249
250 #[test]
251 fn effect_scoped_subscribing_to_own_signal() {
252 let _ = create_root(|| {
253 let trigger = create_signal(());
254 create_effect(move || {
255 trigger.track();
256 let signal = create_signal(());
257 signal.track();
259 });
260 trigger.set(());
261 });
262 }
263}