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}