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.set_if_changed(new_value);
38    });
39
40    Memo { read }
41}
42
43#[cfg(test)]
44mod tests {
45    use super::*;
46    use crate::signal::create_signal;
47    use std::cell::RefCell;
48    use std::rc::Rc;
49
50    #[test]
51    fn test_memo_basic() {
52        let (count, set_count) = create_signal(2);
53        let doubled = create_memo(move || count.get() * 2);
54
55        assert_eq!(doubled.get(), 4);
56
57        set_count.set(5);
58        assert_eq!(doubled.get(), 10);
59    }
60
61    #[test]
62    fn test_memo_caching() {
63        let (count, set_count) = create_signal(1);
64        let compute_count = Rc::new(RefCell::new(0));
65        let compute_count_clone = compute_count.clone();
66
67        let doubled = create_memo(move || {
68            *compute_count_clone.borrow_mut() += 1;
69            count.get() * 2
70        });
71
72        assert_eq!(doubled.get(), 2);
73        assert_eq!(doubled.get(), 2);
74        let initial_count = *compute_count.borrow();
75
76        set_count.set(2);
77        assert_eq!(doubled.get(), 4);
78        assert_eq!(*compute_count.borrow(), initial_count + 1);
79    }
80
81    #[test]
82    fn test_memo_only_updates_on_change() {
83        let (count, set_count) = create_signal(1);
84        let compute_count = Rc::new(RefCell::new(0));
85        let compute_count_clone = compute_count.clone();
86
87        let is_even = create_memo(move || {
88            *compute_count_clone.borrow_mut() += 1;
89            count.get() % 2 == 0
90        });
91
92        assert!(!is_even.get());
93        let count_after_init = *compute_count.borrow();
94
95        set_count.set(3);
96        assert!(!is_even.get());
97        assert_eq!(*compute_count.borrow(), count_after_init + 1);
98
99        set_count.set(4);
100        assert!(is_even.get());
101        assert_eq!(*compute_count.borrow(), count_after_init + 2);
102    }
103
104    #[test]
105    fn test_memo_chain() {
106        let (count, set_count) = create_signal(1);
107
108        let doubled = {
109            let count = count.clone();
110            create_memo(move || count.get() * 2)
111        };
112
113        let quadrupled = {
114            let doubled = doubled.clone();
115            create_memo(move || doubled.get() * 2)
116        };
117
118        assert_eq!(count.get(), 1);
119        assert_eq!(doubled.get(), 2);
120        assert_eq!(quadrupled.get(), 4);
121
122        set_count.set(3);
123
124        assert_eq!(count.get(), 3);
125        assert_eq!(doubled.get(), 6);
126        assert_eq!(quadrupled.get(), 12);
127    }
128
129    #[test]
130    fn test_memo_does_not_notify_when_unchanged() {
131        let (count, set_count) = create_signal(1);
132        let is_even = create_memo(move || count.get() % 2 == 0);
133
134        let downstream_runs = Rc::new(RefCell::new(0usize));
135        let downstream_clone = downstream_runs.clone();
136        crate::effect::create_effect(move || {
137            let _ = is_even.get();
138            *downstream_clone.borrow_mut() += 1;
139        });
140
141        assert_eq!(*downstream_runs.borrow(), 1);
142
143        set_count.set(3); // 3 % 2 != 0, is_even stays false
144        assert_eq!(*downstream_runs.borrow(), 1);
145
146        set_count.set(4); // 4 % 2 == 0, is_even changes to true
147        assert_eq!(*downstream_runs.borrow(), 2);
148    }
149}