Skip to main content

react_rs_elements/
reactive.rs

1use std::cell::RefCell;
2use std::rc::Rc;
3
4use react_rs_core::signal::ReadSignal;
5
6pub trait SignalExt<T> {
7    fn map<U, F>(&self, f: F) -> MappedSignal<T, U, F>
8    where
9        F: Fn(&T) -> U + 'static;
10}
11
12impl<T: Clone + 'static> SignalExt<T> for ReadSignal<T> {
13    fn map<U, F>(&self, f: F) -> MappedSignal<T, U, F>
14    where
15        F: Fn(&T) -> U + 'static,
16    {
17        MappedSignal {
18            signal: self.clone(),
19            mapper: f,
20        }
21    }
22}
23
24pub struct MappedSignal<T, U, F>
25where
26    F: Fn(&T) -> U,
27{
28    signal: ReadSignal<T>,
29    mapper: F,
30}
31
32impl<T: Clone, U, F> MappedSignal<T, U, F>
33where
34    F: Fn(&T) -> U,
35{
36    pub fn get(&self) -> U {
37        self.signal.with(|v| (self.mapper)(v))
38    }
39}
40
41pub enum ReactiveValue<T> {
42    Static(T),
43    Dynamic(Rc<RefCell<T>>),
44}
45
46impl<T: Clone> Clone for ReactiveValue<T> {
47    fn clone(&self) -> Self {
48        match self {
49            ReactiveValue::Static(v) => ReactiveValue::Static(v.clone()),
50            ReactiveValue::Dynamic(v) => ReactiveValue::Dynamic(v.clone()),
51        }
52    }
53}
54
55impl<T: Clone> ReactiveValue<T> {
56    pub fn get(&self) -> T {
57        match self {
58            ReactiveValue::Static(v) => v.clone(),
59            ReactiveValue::Dynamic(v) => v.borrow().clone(),
60        }
61    }
62}
63
64pub trait IntoReactiveString {
65    fn into_reactive_string(self) -> ReactiveValue<String>;
66}
67
68impl IntoReactiveString for &str {
69    fn into_reactive_string(self) -> ReactiveValue<String> {
70        ReactiveValue::Static(self.to_string())
71    }
72}
73
74impl IntoReactiveString for String {
75    fn into_reactive_string(self) -> ReactiveValue<String> {
76        ReactiveValue::Static(self)
77    }
78}
79
80impl<T, F> IntoReactiveString for MappedSignal<T, String, F>
81where
82    T: Clone + 'static,
83    F: Fn(&T) -> String + 'static,
84{
85    fn into_reactive_string(self) -> ReactiveValue<String> {
86        use react_rs_core::effect::create_effect;
87
88        let value = Rc::new(RefCell::new(self.get()));
89        let value_clone = value.clone();
90
91        create_effect(move || {
92            let new_value = self.get();
93            *value_clone.borrow_mut() = new_value;
94        });
95
96        ReactiveValue::Dynamic(value)
97    }
98}
99
100pub trait IntoReactiveBool {
101    fn into_reactive_bool(self) -> ReactiveValue<bool>;
102}
103
104impl IntoReactiveBool for bool {
105    fn into_reactive_bool(self) -> ReactiveValue<bool> {
106        ReactiveValue::Static(self)
107    }
108}
109
110impl<T, F> IntoReactiveBool for MappedSignal<T, bool, F>
111where
112    T: Clone + 'static,
113    F: Fn(&T) -> bool + 'static,
114{
115    fn into_reactive_bool(self) -> ReactiveValue<bool> {
116        use react_rs_core::effect::create_effect;
117
118        let value = Rc::new(RefCell::new(self.get()));
119        let value_clone = value.clone();
120
121        create_effect(move || {
122            let new_value = self.get();
123            *value_clone.borrow_mut() = new_value;
124        });
125
126        ReactiveValue::Dynamic(value)
127    }
128}
129
130#[cfg(test)]
131mod tests {
132    use super::*;
133    use react_rs_core::signal::create_signal;
134
135    #[test]
136    fn test_signal_map() {
137        let (count, set_count) = create_signal(5);
138        let doubled = count.map(|n| n * 2);
139
140        assert_eq!(doubled.get(), 10);
141
142        set_count.set(10);
143        assert_eq!(doubled.get(), 20);
144    }
145
146    #[test]
147    fn test_mapped_signal_to_string() {
148        let (count, set_count) = create_signal(0);
149        let text = count.map(|n| format!("Count: {}", n));
150
151        assert_eq!(text.get(), "Count: 0");
152
153        set_count.set(42);
154        assert_eq!(text.get(), "Count: 42");
155    }
156
157    #[test]
158    fn test_reactive_value_static() {
159        let value: ReactiveValue<String> = "hello".into_reactive_string();
160        assert_eq!(value.get(), "hello");
161    }
162
163    #[test]
164    fn test_reactive_value_dynamic() {
165        let (count, set_count) = create_signal(0);
166        let text = count.map(|n| format!("{}", n));
167        let reactive: ReactiveValue<String> = text.into_reactive_string();
168
169        assert_eq!(reactive.get(), "0");
170
171        set_count.set(5);
172        assert_eq!(reactive.get(), "5");
173    }
174}