leptos_reactive/trigger.rs
1use crate::{
2 diagnostics,
3 diagnostics::*,
4 node::NodeId,
5 runtime::{with_runtime, Runtime},
6 SignalGet, SignalSet, SignalUpdate,
7};
8
9/// Reactive Trigger, notifies reactive code to rerun.
10///
11/// See [`create_trigger`] for more.
12#[derive(Clone, Copy, Debug, PartialEq, Eq)]
13pub struct Trigger {
14 pub(crate) id: NodeId,
15
16 #[cfg(debug_assertions)]
17 pub(crate) defined_at: &'static std::panic::Location<'static>,
18}
19
20impl Trigger {
21 /// Creates a [`Trigger`](crate::Trigger), a kind of reactive primitive.
22 ///
23 /// A trigger is a data-less signal with the sole purpose
24 /// of notifying other reactive code of a change. This can be useful
25 /// for when using external data not stored in signals, for example.
26 ///
27 /// This is identical to [`create_trigger`].
28 ///
29 /// ```
30 /// # use leptos_reactive::*;
31 /// # let runtime = create_runtime();
32 /// use std::{cell::RefCell, fmt::Write, rc::Rc};
33 ///
34 /// let external_data = Rc::new(RefCell::new(1));
35 /// let output = Rc::new(RefCell::new(String::new()));
36 ///
37 /// let rerun_on_data = Trigger::new();
38 ///
39 /// let o = output.clone();
40 /// let e = external_data.clone();
41 /// create_effect(move |_| {
42 /// // can be `rerun_on_data()` on nightly
43 /// rerun_on_data.track();
44 /// write!(o.borrow_mut(), "{}", *e.borrow());
45 /// *e.borrow_mut() += 1;
46 /// });
47 /// # if !cfg!(feature = "ssr") {
48 /// assert_eq!(*output.borrow(), "1");
49 ///
50 /// rerun_on_data.notify(); // reruns the above effect
51 ///
52 /// assert_eq!(*output.borrow(), "12");
53 /// # }
54 /// # runtime.dispose();
55 /// ```
56 #[inline(always)]
57 #[track_caller]
58 pub fn new() -> Self {
59 create_trigger()
60 }
61
62 /// Notifies any reactive code where this trigger is tracked to rerun.
63 ///
64 /// ## Panics
65 /// Panics if there is no current reactive runtime, or if the
66 /// trigger has been disposed.
67 pub fn notify(&self) {
68 assert!(self.try_notify(), "Trigger::notify(): runtime not alive")
69 }
70
71 /// Attempts to notify any reactive code where this trigger is tracked to rerun.
72 ///
73 /// Returns `false` if there is no current reactive runtime.
74 pub fn try_notify(&self) -> bool {
75 with_runtime(|runtime| {
76 runtime.mark_dirty(self.id);
77 runtime.run_effects();
78 })
79 .is_ok()
80 }
81
82 /// Subscribes the running effect to this trigger.
83 ///
84 /// ## Panics
85 /// Panics if there is no current reactive runtime, or if the
86 /// trigger has been disposed.
87 pub fn track(&self) {
88 assert!(self.try_track(), "Trigger::track(): runtime not alive")
89 }
90
91 /// Attempts to subscribe the running effect to this trigger, returning
92 /// `false` if there is no current reactive runtime.
93 pub fn try_track(&self) -> bool {
94 let diagnostics = diagnostics!(self);
95
96 with_runtime(|runtime| {
97 runtime.update_if_necessary(self.id);
98 self.id.subscribe(runtime, diagnostics);
99 })
100 .is_ok()
101 }
102}
103
104/// Creates a [`Trigger`](crate::Trigger), a kind of reactive primitive.
105///
106/// A trigger is a data-less signal with the sole purpose
107/// of notifying other reactive code of a change. This can be useful
108/// for when using external data not stored in signals, for example.
109/// ```
110/// # use leptos_reactive::*;
111/// # let runtime = create_runtime();
112/// use std::{cell::RefCell, fmt::Write, rc::Rc};
113///
114/// let external_data = Rc::new(RefCell::new(1));
115/// let output = Rc::new(RefCell::new(String::new()));
116///
117/// let rerun_on_data = create_trigger();
118///
119/// let o = output.clone();
120/// let e = external_data.clone();
121/// create_effect(move |_| {
122/// // can be `rerun_on_data()` on nightly
123/// rerun_on_data.track();
124/// write!(o.borrow_mut(), "{}", *e.borrow());
125/// *e.borrow_mut() += 1;
126/// });
127/// # if !cfg!(feature = "ssr") {
128/// assert_eq!(*output.borrow(), "1");
129///
130/// rerun_on_data.notify(); // reruns the above effect
131///
132/// assert_eq!(*output.borrow(), "12");
133/// # }
134/// # runtime.dispose();
135/// ```
136#[cfg_attr(debug_assertions, instrument(level = "trace", skip_all,))]
137#[track_caller]
138pub fn create_trigger() -> Trigger {
139 Runtime::current().create_trigger()
140}
141
142impl Default for Trigger {
143 fn default() -> Self {
144 Self::new()
145 }
146}
147
148impl SignalGet for Trigger {
149 type Value = ();
150
151 #[cfg_attr(
152 debug_assertions,
153 instrument(
154 level = "trace",
155 name = "Trigger::get()",
156 skip_all,
157 fields(
158 id = ?self.id,
159 defined_at = %self.defined_at
160 )
161 )
162 )]
163 #[track_caller]
164 #[inline(always)]
165 fn get(&self) {
166 self.track()
167 }
168
169 #[cfg_attr(
170 debug_assertions,
171 instrument(
172 level = "trace",
173 name = "Trigger::try_get()",
174 skip_all,
175 fields(
176 id = ?self.id,
177 defined_at = %self.defined_at
178 )
179 )
180 )]
181 #[inline(always)]
182 fn try_get(&self) -> Option<()> {
183 self.try_track().then_some(())
184 }
185}
186
187impl SignalUpdate for Trigger {
188 type Value = ();
189
190 #[cfg_attr(
191 debug_assertions,
192 instrument(
193 name = "Trigger::update()",
194 level = "trace",
195 skip_all,
196 fields(
197 id = ?self.id,
198 defined_at = %self.defined_at
199 )
200 )
201 )]
202 #[inline(always)]
203 fn update(&self, f: impl FnOnce(&mut ())) {
204 self.try_update(f).expect("runtime to be alive")
205 }
206
207 #[cfg_attr(
208 debug_assertions,
209 instrument(
210 name = "Trigger::try_update()",
211 level = "trace",
212 skip_all,
213 fields(
214 id = ?self.id,
215 defined_at = %self.defined_at
216 )
217 )
218 )]
219 #[inline(always)]
220 fn try_update<O>(&self, f: impl FnOnce(&mut ()) -> O) -> Option<O> {
221 // run callback with runtime before dirtying the trigger,
222 // consistent with signals.
223 with_runtime(|runtime| {
224 let res = f(&mut ());
225
226 runtime.mark_dirty(self.id);
227 runtime.run_effects();
228
229 Some(res)
230 })
231 .ok()
232 .flatten()
233 }
234}
235
236impl SignalSet for Trigger {
237 type Value = ();
238
239 #[cfg_attr(
240 debug_assertions,
241 instrument(
242 level = "trace",
243 name = "Trigger::set()",
244 skip_all,
245 fields(
246 id = ?self.id,
247 defined_at = %self.defined_at
248 )
249 )
250 )]
251 #[inline(always)]
252 fn set(&self, _: ()) {
253 self.notify();
254 }
255
256 #[cfg_attr(
257 debug_assertions,
258 instrument(
259 level = "trace",
260 name = "Trigger::try_set()",
261 skip_all,
262 fields(
263 id = ?self.id,
264 defined_at = %self.defined_at
265 )
266 )
267 )]
268 #[inline(always)]
269 fn try_set(&self, _: ()) -> Option<()> {
270 self.try_notify().then_some(())
271 }
272}
273
274#[cfg(feature = "nightly")]
275impl FnOnce<()> for Trigger {
276 type Output = ();
277
278 #[inline(always)]
279 extern "rust-call" fn call_once(self, _args: ()) -> Self::Output {
280 self.track()
281 }
282}
283
284#[cfg(feature = "nightly")]
285impl FnMut<()> for Trigger {
286 #[inline(always)]
287 extern "rust-call" fn call_mut(&mut self, _args: ()) -> Self::Output {
288 self.track()
289 }
290}
291
292#[cfg(feature = "nightly")]
293impl Fn<()> for Trigger {
294 #[inline(always)]
295 extern "rust-call" fn call(&self, _args: ()) -> Self::Output {
296 self.track()
297 }
298}