leptos_use/
watch_with_options.rs

1use crate::filter_builder_methods;
2use crate::utils::{create_filter_wrapper, DebounceOptions, FilterOptions, ThrottleOptions};
3use default_struct_builder::DefaultBuilder;
4use leptos::prelude::*;
5use std::cell::RefCell;
6use std::rc::Rc;
7
8/// A version of `leptos::watch` but with additional options.
9///
10/// ## Immediate
11///
12/// This is the same as for `leptos::watch`. But you don't have to specify it.
13/// By default its set to `false`.
14/// If `immediate` is `true`, the `callback` will run immediately (this is also true if throttled/debounced).
15/// If it's `false`, the `callback` will run only after
16/// the first change is detected of any signal that is accessed in `deps`.
17///
18/// ```
19/// # use leptos::prelude::*;
20/// # use leptos::logging::log;
21/// # use leptos_use::{watch_with_options, WatchOptions};
22/// #
23/// # pub fn Demo() -> impl IntoView {
24/// let (num, set_num) = signal(0);
25///
26/// watch_with_options(
27///     move || num.get(),
28///     move |num, _, _| {
29///         log!("Number {}", num);
30///     },
31///     WatchOptions::default().immediate(true),
32/// ); // > "Number 0"
33///
34/// set_num.set(1); // > "Number 1"
35/// #    view! { }
36/// # }
37/// ```
38///
39/// ## Filters
40///
41/// The callback can be throttled or debounced. Please see [`fn@crate::watch_throttled`]
42/// and [`fn@crate::watch_debounced`] for details.
43///
44/// ```
45/// # use leptos::prelude::*;
46/// # use leptos::logging::log;
47/// # use leptos_use::{watch_with_options, WatchOptions};
48/// #
49/// # pub fn Demo() -> impl IntoView {
50/// # let (num, set_num) = signal(0);
51/// #
52/// watch_with_options(
53///     move || num.get(),
54///     move |num, _, _| {
55///         log!("Number {}", num);
56///     },
57///     WatchOptions::default().throttle(100.0), // there's also `throttle_with_options`
58/// );
59/// #    view! { }
60/// # }
61/// ```
62///
63/// ```
64/// # use leptos::prelude::*;
65/// # use leptos::logging::log;
66/// # use leptos_use::{watch_with_options, WatchOptions};
67/// #
68/// # pub fn Demo() -> impl IntoView {
69/// # let (num, set_num) = signal(0);
70/// #
71/// watch_with_options(
72///     move || num.get(),
73///     move |num, _, _| {
74///         log!("number {}", num);
75///     },
76///     WatchOptions::default().debounce(100.0), // there's also `debounce_with_options`
77/// );
78/// #    view! { }
79/// # }
80/// ```
81///
82/// ## Server-Side Rendering
83///
84/// > Make sure you follow the [instructions in Server-Side Rendering](https://leptos-use.rs/server_side_rendering.html).
85///
86/// On the server this works just fine except if you throttle or debounce in which case the callback
87/// will never be called except if you set `immediate` to `true` in which case the callback will be
88/// called exactly once when `watch()` is executed.
89///
90/// ## See also
91///
92/// * [`fn@crate::watch_throttled`]
93/// * [`fn@crate::watch_debounced`]
94pub fn watch_with_options<W, T, DFn, CFn>(
95    deps: DFn,
96    callback: CFn,
97    options: WatchOptions,
98) -> impl Fn() + Clone + Send + Sync
99where
100    DFn: Fn() -> W + 'static,
101    CFn: Fn(&W, Option<&W>, Option<T>) -> T + Clone + 'static,
102    W: Clone + 'static,
103    T: Clone + 'static,
104{
105    let cur_deps_value: Rc<RefCell<Option<W>>> = Rc::new(RefCell::new(None));
106    let prev_deps_value: Rc<RefCell<Option<W>>> = Rc::new(RefCell::new(None));
107    let prev_callback_value: Rc<RefCell<Option<T>>> = Rc::new(RefCell::new(None));
108
109    let wrapped_callback = {
110        let cur_deps_value = Rc::clone(&cur_deps_value);
111        let prev_deps_value = Rc::clone(&prev_deps_value);
112        let prev_callback_val = Rc::clone(&prev_callback_value);
113
114        move || {
115            #[cfg(debug_assertions)]
116            let _z = leptos::reactive::diagnostics::SpecialNonReactiveZone::enter();
117
118            let ret = callback(
119                cur_deps_value
120                    .borrow()
121                    .as_ref()
122                    .expect("this will not be called before there is deps value"),
123                prev_deps_value.borrow().as_ref(),
124                prev_callback_val.take(),
125            );
126
127            ret
128        }
129    };
130
131    let filtered_callback =
132        create_filter_wrapper(options.filter.filter_fn(), wrapped_callback.clone());
133
134    let effect = Effect::watch(
135        deps,
136        move |deps_value, previous_deps_value, did_run_before| {
137            cur_deps_value.replace(Some(deps_value.clone()));
138            prev_deps_value.replace(previous_deps_value.cloned());
139
140            let callback_value = if options.immediate && did_run_before.is_none() {
141                Some(wrapped_callback())
142            } else {
143                filtered_callback().lock().unwrap().take()
144            };
145
146            prev_callback_value.replace(callback_value);
147        },
148        options.immediate,
149    );
150
151    move || effect.stop()
152
153    // create_effect(move |did_run_before| {
154    //     if !is_active.get() {
155    //         return;
156    //     }
157    //
158    //     let deps_value = deps();
159    //
160    //     if !options.immediate && did_run_before.is_none() {
161    //         prev_deps_value.replace(Some(deps_value));
162    //         return;
163    //     }
164    //
165    //     cur_deps_value.replace(Some(deps_value.clone()));
166    //
167    //
168    //     prev_deps_value.replace(Some(deps_value));
169    // });
170    //
171    //
172}
173
174/// Options for `watch_with_options`
175#[derive(DefaultBuilder, Default)]
176pub struct WatchOptions {
177    /// If `immediate` is true, the `callback` will run immediately.
178    /// If it's `false, the `callback` will run only after
179    /// the first change is detected of any signal that is accessed in `deps`.
180    /// Defaults to `false`.
181    immediate: bool,
182
183    /// Allows to debounce or throttle the callback. Defaults to no filter.
184    filter: FilterOptions,
185}
186
187impl WatchOptions {
188    filter_builder_methods!(
189        /// the watch callback
190        filter
191    );
192}