reactive_signals/signals/
mod.rs

1#[cfg(test)]
2mod tests;
3
4pub(crate) mod kinds;
5mod signal_accessors;
6mod signal_id;
7mod signal_inner;
8mod signal_new;
9pub mod types;
10mod updater;
11
12use std::marker::PhantomData;
13
14use crate::runtimes::Runtime;
15pub(crate) use signal_id::SignalId;
16pub(crate) use signal_inner::{SignalInner, SignalValue};
17pub(crate) use types::*;
18
19#[doc(hidden)]
20pub use kinds::*;
21
22/// A [Signal] is a reactive value or a function that produces a value,
23/// with subscribers that are automatically notified when the value changes.
24///
25/// When it is a function, the function automatically subscribes to all the other
26/// signals it is using and automatically re-runs when any of those signals change.
27///
28/// If the value implements [PartialEq] then the subscribers are notified only if
29/// the value changed.
30///
31/// A [Signal] is created in a reactive [Scope](crate::Scope) using the [signal!](crate::signal!) macro.
32/// It can only be deleted by discarding that [Scope](crate::Scope).
33///
34/// ## Accessors
35///
36/// Only data signals can be manually changed. Func signals that only runs on `server` or `client`
37/// always return optional values which are some only when runninig on their side.
38///
39/// | Value implements | Data signal          | Func signal | Func signal with<br>`server` or `client` |
40/// | ---              | --                   | ---         | ---                                      |
41/// | -                | .set, .update, .with | .with       | .opt_with                                |
42/// | [Clone]          | .cloned              | .cloned     | .opt_cloned                              |
43/// | [Copy]           | .get                 | .get        | .opt_get                                 |
44///
45///
46/// ## Example
47///
48/// ```rust
49/// use reactive_signals::{runtimes::ClientRuntime, signal};
50///
51/// // signals are created in scopes
52/// let sx = ClientRuntime::new_root_scope();
53///
54/// // a simple data value
55/// let count = signal!(sx, 5);
56///
57/// // a simple string value
58/// let name = signal!(sx, "kiwi");
59///
60/// // is_plural will update when count changes
61/// let is_plural = signal!(sx, move || count.get() != 1);
62///
63/// // we'll keep a history of all changes
64/// let history = signal!(sx, Vec::<String>::new());
65///
66/// let text = signal!(sx, move || {
67///     let ending = if is_plural.get() { "s" } else { "" };
68///     let txt = format!("{} {}{ending}", count.get(), name.get());
69///     // using .update we can add the text to the vec without cloning the vec
70///     history.update(|hist| hist.push(txt.clone()));
71///     txt
72/// });
73///
74/// assert_eq!(text.cloned(), "5 kiwis");
75///
76/// // when setting to same value the subscribers are not notified.
77/// name.set("kiwi");
78/// assert_eq!(history.with(|h| h.join(", ")), "5 kiwis");
79///
80/// // when changing the count the name and is_plural are updated automatically.
81/// count.set(1);
82/// assert_eq!(text.cloned(), "1 kiwi");
83///
84/// // you can update the name
85/// name.update(|t| *t = "fig");
86/// assert_eq!(text.cloned(), "1 fig");
87///
88/// // 1 kiwi is repated because when changing count, is_plural changes as well
89/// // triggering a second update of the text. This will be detected in
90/// // future versions and only notified once.
91/// assert_eq!(
92///     history.with(|h| h.join(", ")),
93///     "5 kiwis, 1 kiwi, 1 kiwi, 1 fig"
94/// );
95///
96/// with_signal_arg(count);
97///
98/// // when declaring functions some additional imports are necessary
99/// use reactive_signals::{runtimes::Runtime, Signal, types::*};
100///
101/// fn with_signal_arg<RT: Runtime>(count: Signal<EqData<i32>, RT>) {
102/// }
103///
104/// ```
105pub struct Signal<T: SignalType, RT: Runtime> {
106    id: SignalId<RT>,
107    ty: PhantomData<T>,
108}
109
110impl<T: SignalType, RT: Runtime> Clone for Signal<T, RT> {
111    fn clone(&self) -> Self {
112        Self {
113            id: self.id,
114            ty: self.ty,
115        }
116    }
117}
118
119impl<T: SignalType, RT: Runtime> Copy for Signal<T, RT> {}
120
121#[test]
122fn test_example() {
123    use crate::{runtimes::ClientRuntime, signal};
124
125    // signals are created in scopes
126    let sx = ClientRuntime::new_root_scope();
127
128    // a simple data value
129    let count = signal!(sx, 5);
130
131    // a simple string value
132    let name = signal!(sx, "kiwi");
133
134    // is_plural will update when count changes
135    let is_plural = signal!(sx, move || count.get() != 1);
136
137    // we'll keep a history of all changes
138    let history = signal!(sx, Vec::<String>::new());
139
140    let text = signal!(sx, move || {
141        let ending = if is_plural.get() { "s" } else { "" };
142        let txt = format!("{} {}{ending}", count.get(), name.get());
143        // using .update we can add the text to the vec without cloning the vec
144        history.update(|hist| hist.push(txt.clone()));
145        txt
146    });
147
148    assert_eq!(text.cloned(), "5 kiwis");
149
150    // when setting to same value the subscribers are not notified.
151    name.set("kiwi");
152    assert_eq!(history.with(|h| h.join(", ")), "5 kiwis");
153
154    // when changing the count the name and is_plural are updated automatically.
155    count.set(1);
156    assert_eq!(text.cloned(), "1 kiwi");
157
158    // you can update the name
159    name.update(|t| *t = "fig");
160    assert_eq!(text.cloned(), "1 fig");
161
162    // 1 kiwi is repated because when changing count, is_plural changes as well
163    // triggering a second update of the text. This will be detected in
164    // future versions and only notified once.
165    assert_eq!(
166        history.with(|h| h.join(", ")),
167        "5 kiwis, 1 kiwi, 1 kiwi, 1 fig"
168    );
169}