1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
use crate::{debug_warn, Runtime, Scope, ScopeProperty};
use std::fmt::Debug;

/// Effects run a certain chunk of code whenever the signals they depend on change.
/// `create_effect` immediately runs the given function once, tracks its dependence
/// on any signal values read within it, and reruns the function whenever the value
/// of a dependency changes.
///
/// Effects are intended to run *side-effects* of the system, not to synchronize state
/// *within* the system. In other words: don't write to signals within effects.
/// (If you need to define a signal that depends on the value of other signals, use a
/// derived signal or [create_memo](crate::create_memo)).
///
/// The effect function is called with an argument containing whatever value it returned
/// the last time it ran. On the initial run, this is `None`.
///
/// By default, effects **do not run on the server**. This means you can call browser-specific
/// APIs within the effect function without causing issues. If you need an effect to run on
/// the server, use [create_isomorphic_effect].
/// ```
/// # use leptos_reactive::*;
/// # use log::*;
/// # create_scope(|cx| {
/// let (a, set_a) = create_signal(cx, 0);
/// let (b, set_b) = create_signal(cx, 0);
///
/// // ✅ use effects to interact between reactive state and the outside world
/// create_effect(cx, move |_| {
///   // immediately prints "Value: 0" and subscribes to `a`
///   log::debug!("Value: {}", a());
/// });
///
/// set_a(1);
/// // ✅ because it's subscribed to `a`, the effect reruns and prints "Value: 1"
///
/// // ❌ don't use effects to synchronize state within the reactive system
/// create_effect(cx, move |_| {
///   // this technically works but can cause unnecessary re-renders
///   // and easily lead to problems like infinite loops
///   set_b(a() + 1);
/// });
/// # assert_eq!(b(), 2);
/// # }).dispose();
/// ```
#[cfg(not(feature = "ssr"))]
pub fn create_effect<T>(cx: Scope, f: impl FnMut(Option<T>) -> T + 'static)
where
    T: Debug + 'static,
{
    create_isomorphic_effect(cx, f);
}
#[cfg(feature = "ssr")]
pub fn create_effect<T>(_cx: Scope, _f: impl FnMut(Option<T>) -> T + 'static)
where
    T: Debug + 'static,
{
}

/// Creates an effect; unlike effects created by [create_effect], isomorphic effects will run on
/// the server as well as the client.
/// ```
/// # use leptos_reactive::*;
/// # use log::*;
/// # create_scope(|cx| {
/// let (a, set_a) = create_signal(cx, 0);
/// let (b, set_b) = create_signal(cx, 0);
///
/// // ✅ use effects to interact between reactive state and the outside world
/// create_isomorphic_effect(cx, move |_| {
///   // immediately prints "Value: 0" and subscribes to `a`
///   log::debug!("Value: {}", a());
/// });
///
/// set_a(1);
/// // ✅ because it's subscribed to `a`, the effect reruns and prints "Value: 1"
///
/// // ❌ don't use effects to synchronize state within the reactive system
/// create_isomorphic_effect(cx, move |_| {
///   // this technically works but can cause unnecessary re-renders
///   // and easily lead to problems like infinite loops
///   set_b(a() + 1);
/// });
/// # assert_eq!(b(), 2);
/// # }).dispose();
pub fn create_isomorphic_effect<T>(cx: Scope, f: impl FnMut(Option<T>) -> T + 'static)
where
    T: Debug + 'static,
{
    let e = cx.runtime.create_effect(f);
    cx.with_scope_property(|prop| prop.push(ScopeProperty::Effect(e)))
}

#[doc(hidden)]
pub fn create_render_effect<T>(cx: Scope, f: impl FnMut(Option<T>) -> T + 'static)
where
    T: Debug + 'static,
{
    create_effect(cx, f);
}

slotmap::new_key_type! { pub struct EffectId; }

pub(crate) struct Effect<T, F>
where
    T: 'static,
    F: FnMut(Option<T>) -> T,
{
    pub(crate) f: F,
    pub(crate) value: Option<T>,
}

pub(crate) trait AnyEffect {
    fn run(&mut self, id: EffectId, runtime: &Runtime);
}

impl<T, F> AnyEffect for Effect<T, F>
where
    T: 'static,
    F: FnMut(Option<T>) -> T,
{
    fn run(&mut self, id: EffectId, runtime: &Runtime) {
        // clear previous dependencies
        id.cleanup(runtime);

        // set this as the current observer
        let prev_observer = runtime.observer.take();
        runtime.observer.set(Some(id));

        // run the effect
        let value = self.value.take();
        let new_value = (self.f)(value);
        self.value = Some(new_value);

        // restore the previous observer
        runtime.observer.set(prev_observer);
    }
}

impl EffectId {
    pub(crate) fn run<T>(&self, runtime: &Runtime) {
        let effect = {
            let effects = runtime.effects.borrow();
            effects.get(*self).cloned()
        };
        if let Some(effect) = effect {
            effect.borrow_mut().run(*self, runtime);
        } else {
            debug_warn!("[Effect] Trying to run an Effect that has been disposed. This is probably either a logic error in a component that creates and disposes of scopes, or a Resource resolving after its scope has been dropped without having been cleaned up.")
        }
    }

    pub(crate) fn cleanup(&self, runtime: &Runtime) {
        let sources = runtime.effect_sources.borrow();
        if let Some(sources) = sources.get(*self) {
            let subs = runtime.signal_subscribers.borrow();
            for source in sources.borrow().iter() {
                if let Some(source) = subs.get(*source) {
                    source.borrow_mut().remove(self);
                }
            }
        }
    }
}