Skip to main content

react_rs_core/
memo.rs

1use crate::effect::create_effect;
2use crate::signal::{create_signal, ReadSignal};
3
4pub struct Memo<T> {
5    read: ReadSignal<T>,
6}
7
8impl<T: Clone> Memo<T> {
9    pub fn get(&self) -> T {
10        self.read.get()
11    }
12
13    pub fn with<R>(&self, f: impl FnOnce(&T) -> R) -> R {
14        self.read.with(f)
15    }
16}
17
18impl<T> Clone for Memo<T> {
19    fn clone(&self) -> Self {
20        Self {
21            read: self.read.clone(),
22        }
23    }
24}
25
26/// Creates a derived computation that caches its value and updates when dependencies change.
27pub fn create_memo<T, F>(f: F) -> Memo<T>
28where
29    F: Fn() -> T + 'static,
30    T: PartialEq + Clone + 'static,
31{
32    let initial = f();
33    let (read, write) = create_signal(initial);
34
35    create_effect(move || {
36        let new_value = f();
37        write.update(|current| {
38            if *current != new_value {
39                *current = new_value;
40            }
41        });
42    });
43
44    Memo { read }
45}
46
47#[cfg(test)]
48mod tests {
49    use super::*;
50    use crate::signal::create_signal;
51    use std::cell::RefCell;
52    use std::rc::Rc;
53
54    #[test]
55    fn test_memo_basic() {
56        let (count, set_count) = create_signal(2);
57        let doubled = create_memo(move || count.get() * 2);
58
59        assert_eq!(doubled.get(), 4);
60
61        set_count.set(5);
62        assert_eq!(doubled.get(), 10);
63    }
64
65    #[test]
66    fn test_memo_caching() {
67        let (count, set_count) = create_signal(1);
68        let compute_count = Rc::new(RefCell::new(0));
69        let compute_count_clone = compute_count.clone();
70
71        let doubled = create_memo(move || {
72            *compute_count_clone.borrow_mut() += 1;
73            count.get() * 2
74        });
75
76        assert_eq!(doubled.get(), 2);
77        assert_eq!(doubled.get(), 2);
78        let initial_count = *compute_count.borrow();
79
80        set_count.set(2);
81        assert_eq!(doubled.get(), 4);
82        assert_eq!(*compute_count.borrow(), initial_count + 1);
83    }
84
85    #[test]
86    fn test_memo_only_updates_on_change() {
87        let (count, set_count) = create_signal(1);
88        let compute_count = Rc::new(RefCell::new(0));
89        let compute_count_clone = compute_count.clone();
90
91        let is_even = create_memo(move || {
92            *compute_count_clone.borrow_mut() += 1;
93            count.get() % 2 == 0
94        });
95
96        assert!(!is_even.get());
97        let count_after_init = *compute_count.borrow();
98
99        set_count.set(3);
100        assert!(!is_even.get());
101        assert_eq!(*compute_count.borrow(), count_after_init + 1);
102
103        set_count.set(4);
104        assert!(is_even.get());
105        assert_eq!(*compute_count.borrow(), count_after_init + 2);
106    }
107
108    #[test]
109    fn test_memo_chain() {
110        let (count, set_count) = create_signal(1);
111
112        let doubled = {
113            let count = count.clone();
114            create_memo(move || count.get() * 2)
115        };
116
117        let quadrupled = {
118            let doubled = doubled.clone();
119            create_memo(move || doubled.get() * 2)
120        };
121
122        assert_eq!(count.get(), 1);
123        assert_eq!(doubled.get(), 2);
124        assert_eq!(quadrupled.get(), 4);
125
126        set_count.set(3);
127
128        assert_eq!(count.get(), 3);
129        assert_eq!(doubled.get(), 6);
130        assert_eq!(quadrupled.get(), 12);
131    }
132}