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> ReactiveValue<T> {
47    pub fn get(&self) -> T {
48        match self {
49            ReactiveValue::Static(v) => v.clone(),
50            ReactiveValue::Dynamic(v) => v.borrow().clone(),
51        }
52    }
53}
54
55pub trait IntoReactiveString {
56    fn into_reactive_string(self) -> ReactiveValue<String>;
57}
58
59impl IntoReactiveString for &str {
60    fn into_reactive_string(self) -> ReactiveValue<String> {
61        ReactiveValue::Static(self.to_string())
62    }
63}
64
65impl IntoReactiveString for String {
66    fn into_reactive_string(self) -> ReactiveValue<String> {
67        ReactiveValue::Static(self)
68    }
69}
70
71impl<T, F> IntoReactiveString for MappedSignal<T, String, F>
72where
73    T: Clone + 'static,
74    F: Fn(&T) -> String + 'static,
75{
76    fn into_reactive_string(self) -> ReactiveValue<String> {
77        use react_rs_core::effect::create_effect;
78
79        let value = Rc::new(RefCell::new(self.get()));
80        let value_clone = value.clone();
81
82        create_effect(move || {
83            let new_value = self.get();
84            *value_clone.borrow_mut() = new_value;
85        });
86
87        ReactiveValue::Dynamic(value)
88    }
89}
90
91pub trait IntoReactiveBool {
92    fn into_reactive_bool(self) -> ReactiveValue<bool>;
93}
94
95impl IntoReactiveBool for bool {
96    fn into_reactive_bool(self) -> ReactiveValue<bool> {
97        ReactiveValue::Static(self)
98    }
99}
100
101impl<T, F> IntoReactiveBool for MappedSignal<T, bool, F>
102where
103    T: Clone + 'static,
104    F: Fn(&T) -> bool + 'static,
105{
106    fn into_reactive_bool(self) -> ReactiveValue<bool> {
107        use react_rs_core::effect::create_effect;
108
109        let value = Rc::new(RefCell::new(self.get()));
110        let value_clone = value.clone();
111
112        create_effect(move || {
113            let new_value = self.get();
114            *value_clone.borrow_mut() = new_value;
115        });
116
117        ReactiveValue::Dynamic(value)
118    }
119}
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124    use react_rs_core::signal::create_signal;
125
126    #[test]
127    fn test_signal_map() {
128        let (count, set_count) = create_signal(5);
129        let doubled = count.map(|n| n * 2);
130
131        assert_eq!(doubled.get(), 10);
132
133        set_count.set(10);
134        assert_eq!(doubled.get(), 20);
135    }
136
137    #[test]
138    fn test_mapped_signal_to_string() {
139        let (count, set_count) = create_signal(0);
140        let text = count.map(|n| format!("Count: {}", n));
141
142        assert_eq!(text.get(), "Count: 0");
143
144        set_count.set(42);
145        assert_eq!(text.get(), "Count: 42");
146    }
147
148    #[test]
149    fn test_reactive_value_static() {
150        let value: ReactiveValue<String> = "hello".into_reactive_string();
151        assert_eq!(value.get(), "hello");
152    }
153
154    #[test]
155    fn test_reactive_value_dynamic() {
156        let (count, set_count) = create_signal(0);
157        let text = count.map(|n| format!("{}", n));
158        let reactive: ReactiveValue<String> = text.into_reactive_string();
159
160        assert_eq!(reactive.get(), "0");
161
162        set_count.set(5);
163        assert_eq!(reactive.get(), "5");
164    }
165}