leptos_use/
use_interval_fn.rs

1#![cfg_attr(feature = "ssr", allow(unused_variables, unused_imports))]
2
3use crate::sendwrap_fn;
4use crate::utils::Pausable;
5use default_struct_builder::DefaultBuilder;
6use leptos::leptos_dom::helpers::IntervalHandle;
7use leptos::prelude::*;
8use send_wrapper::SendWrapper;
9use std::cell::Cell;
10use std::sync::Arc;
11use std::time::Duration;
12
13/// Wrapper for `set_interval` with controls.
14///
15/// ## Demo
16///
17/// [Link to Demo](https://github.com/Synphonyte/leptos-use/tree/main/examples/use_interval_fn)
18///
19/// ## Usage
20///
21/// ```
22/// # use leptos::prelude::*;
23/// # use leptos_use::use_interval_fn;
24/// # use leptos_use::utils::Pausable;
25/// #
26/// # #[component]
27/// # fn Demo() -> impl IntoView {
28/// let Pausable { pause, resume, is_active } = use_interval_fn(
29///     || {
30///         // do something
31///     },
32///     1000,
33/// );
34/// # view! { }
35/// # }
36/// ```
37///
38/// ## SendWrapped Return
39///
40/// The returned closures `pause` and `resume` are sendwrapped functions. They can
41/// only be called from the same thread that called `use_interval_fn`.
42///
43/// ## Server-Side Rendering
44///
45/// On the server this function will simply be ignored.
46pub fn use_interval_fn<CbFn, N>(
47    callback: CbFn,
48    interval: N,
49) -> Pausable<impl Fn() + Clone + Send + Sync, impl Fn() + Clone + Send + Sync>
50where
51    CbFn: Fn() + Clone + 'static,
52    N: Into<Signal<u64>>,
53{
54    use_interval_fn_with_options(callback, interval, UseIntervalFnOptions::default())
55}
56
57/// Version of [`use_interval_fn`] that takes `UseIntervalFnOptions`. See [`use_interval_fn`] for how to use.
58pub fn use_interval_fn_with_options<CbFn, N>(
59    callback: CbFn,
60    interval: N,
61    options: UseIntervalFnOptions,
62) -> Pausable<impl Fn() + Clone + Send + Sync, impl Fn() + Clone + Send + Sync>
63where
64    CbFn: Fn() + Clone + 'static,
65    N: Into<Signal<u64>>,
66{
67    let UseIntervalFnOptions {
68        immediate,
69        immediate_callback,
70    } = options;
71
72    let timer: Arc<SendWrapper<Cell<Option<IntervalHandle>>>> =
73        Arc::new(SendWrapper::new(Cell::new(None)));
74
75    let (is_active, set_active) = signal(false);
76
77    let clean = {
78        let timer = Arc::clone(&timer);
79
80        move || {
81            if let Some(handle) = Cell::take(&timer) {
82                handle.clear();
83            }
84        }
85    };
86
87    let pause = {
88        let clean = clean.clone();
89
90        move || {
91            set_active.set(false);
92            clean();
93        }
94    };
95
96    let interval = interval.into();
97
98    let resume = sendwrap_fn!(move || {
99        #[cfg(not(feature = "ssr"))]
100        {
101            let interval_value = interval.get();
102            if interval_value == 0 {
103                return;
104            }
105
106            set_active.set(true);
107
108            let callback = {
109                let callback = callback.clone();
110
111                move || {
112                    #[cfg(debug_assertions)]
113                    let _z = leptos::reactive::diagnostics::SpecialNonReactiveZone::enter();
114
115                    callback();
116                }
117            };
118
119            if immediate_callback {
120                callback.clone()();
121            }
122            clean();
123
124            timer.set(
125                set_interval_with_handle(callback.clone(), Duration::from_millis(interval_value))
126                    .ok(),
127            );
128        }
129    });
130
131    if immediate {
132        resume();
133    }
134
135    {
136        #[allow(clippy::clone_on_copy)]
137        let resume = resume.clone();
138
139        let effect = Effect::watch(
140            move || interval.get(),
141            move |_, _, _| {
142                if is_active.get() {
143                    resume();
144                }
145            },
146            false,
147        );
148        on_cleanup(move || effect.stop());
149    }
150
151    on_cleanup({
152        let pause = SendWrapper::new(pause.clone());
153        #[allow(clippy::redundant_closure)]
154        move || pause()
155    });
156
157    Pausable {
158        is_active: is_active.into(),
159        pause,
160        resume,
161    }
162}
163
164/// Options for [`use_interval_fn_with_options`]
165#[derive(DefaultBuilder)]
166pub struct UseIntervalFnOptions {
167    /// Start the timer immediately. Defaults to `true`.
168    pub immediate: bool,
169
170    /// Execute the callback immediate after calling this function. Defaults to `false`
171    pub immediate_callback: bool,
172}
173
174impl Default for UseIntervalFnOptions {
175    fn default() -> Self {
176        Self {
177            immediate: true,
178            immediate_callback: false,
179        }
180    }
181}