freya_core/lifecycle/
effect.rs

1use std::{
2    cell::RefCell,
3    rc::Rc,
4};
5
6use crate::{
7    prelude::{
8        State,
9        spawn,
10        use_hook,
11        use_reactive,
12    },
13    reactive_context::ReactiveContext,
14};
15
16pub struct Effect;
17
18impl Effect {
19    pub fn create(mut callback: impl FnMut() + 'static) {
20        let (rx, rc) = ReactiveContext::new_for_task();
21        spawn(async move {
22            loop {
23                ReactiveContext::run(rc.clone(), &mut callback);
24                rx.notified().await;
25            }
26        });
27    }
28
29    // TODO: This should probably not be sync but instead Freya should prioritize effect tasks
30    pub fn create_sync_with_gen(mut callback: impl FnMut(usize) + 'static) {
31        let (rx, rc) = ReactiveContext::new_for_task();
32        ReactiveContext::run(rc.clone(), || callback(0));
33        spawn(async move {
34            let mut current_gen = 1;
35            loop {
36                rx.notified().await;
37                ReactiveContext::run(rc.clone(), || callback(current_gen));
38                current_gen += 1;
39            }
40        });
41    }
42
43    pub fn create_sync(mut callback: impl FnMut() + 'static) {
44        let (rx, rc) = ReactiveContext::new_for_task();
45        ReactiveContext::run(rc.clone(), &mut callback);
46        spawn(async move {
47            loop {
48                rx.notified().await;
49                ReactiveContext::run(rc.clone(), &mut callback);
50            }
51        });
52    }
53
54    pub fn create_after(callback: impl FnMut() + 'static) {
55        let (rx, rc) = ReactiveContext::new_for_task();
56        let callback = Rc::new(RefCell::new(callback));
57        spawn(async move {
58            loop {
59                let callback = callback.clone();
60                let rc = rc.clone();
61                spawn(async move {
62                    ReactiveContext::run(rc, &mut *callback.borrow_mut());
63                });
64                rx.notified().await;
65            }
66        });
67    }
68
69    pub fn create_value<T: 'static>(mut callback: impl FnMut() -> T + 'static) -> State<T> {
70        let (rx, rc) = ReactiveContext::new_for_task();
71        let mut state = State::create(ReactiveContext::run(rc.clone(), &mut callback));
72        spawn(async move {
73            let mut current_gen = 0;
74            loop {
75                if current_gen > 0 {
76                    state.set(ReactiveContext::run(rc.clone(), &mut callback));
77                }
78                rx.notified().await;
79                current_gen += 1;
80            }
81        });
82        state
83    }
84}
85
86/// Registers a callback that will run every time a [State] which was [.read()](State::read) inside, changes.
87/// ```rust, no_run
88/// # use freya::prelude::*;
89/// let state = use_state(|| 0);
90///
91/// use_side_effect(move || {
92///     // The moment `.read()` is called this side effect callback gets subscribed to it
93///     let value = *state.read();
94///     println!("{value}");
95/// });
96/// ```
97pub fn use_side_effect(callback: impl FnMut() + 'static) {
98    use_hook(|| Effect::create(callback));
99}
100
101pub fn use_after_side_effect(callback: impl FnMut() + 'static) {
102    use_hook(|| Effect::create_after(callback));
103}
104
105pub fn use_side_effect_value<T: 'static>(callback: impl FnMut() -> T + 'static) -> State<T> {
106    use_hook(|| Effect::create_value(callback))
107}
108
109pub fn use_side_effect_with_deps<D: 'static + Clone + PartialEq>(
110    deps: &D,
111    mut callback: impl FnMut(&D) + 'static,
112) {
113    let deps = use_reactive(deps);
114    use_hook(move || {
115        Effect::create(move || {
116            let deps = deps.read();
117            callback(&deps)
118        })
119    });
120}