Skip to main content

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/// > Make sure you follow the [instructions in Server-Side Rendering](https://leptos-use.rs/server_side_rendering.html).
46///
47/// On the server this function will simply be ignored.
48pub fn use_interval_fn<CbFn, N>(
49    callback: CbFn,
50    interval: N,
51) -> Pausable<impl Fn() + Clone + Send + Sync, impl Fn() + Clone + Send + Sync>
52where
53    CbFn: Fn() + Clone + 'static,
54    N: Into<Signal<u64>>,
55{
56    use_interval_fn_with_options(callback, interval, UseIntervalFnOptions::default())
57}
58
59/// Version of [`use_interval_fn`] that takes `UseIntervalFnOptions`. See [`use_interval_fn`] for how to use.
60pub fn use_interval_fn_with_options<CbFn, N>(
61    callback: CbFn,
62    interval: N,
63    options: UseIntervalFnOptions,
64) -> Pausable<impl Fn() + Clone + Send + Sync, impl Fn() + Clone + Send + Sync>
65where
66    CbFn: Fn() + Clone + 'static,
67    N: Into<Signal<u64>>,
68{
69    let UseIntervalFnOptions {
70        immediate,
71        immediate_callback,
72    } = options;
73
74    let (is_active, set_active) = signal(false);
75
76    let resume;
77    let pause;
78
79    #[cfg(feature = "ssr")]
80    {
81        resume = || ();
82        pause = || ();
83
84        let _ = options;
85    }
86
87    #[cfg(not(feature = "ssr"))]
88    {
89        let timer: Arc<SendWrapper<Cell<Option<IntervalHandle>>>> =
90            Arc::new(SendWrapper::new(Cell::new(None)));
91
92        let clean = {
93            let timer = Arc::clone(&timer);
94
95            move || {
96                if let Some(handle) = Cell::take(&timer) {
97                    handle.clear();
98                }
99            }
100        };
101
102        pause = {
103            let clean = clean.clone();
104
105            move || {
106                set_active.set(false);
107                clean();
108            }
109        };
110
111        let interval = interval.into();
112
113        resume = sendwrap_fn!(move || {
114            #[cfg(not(feature = "ssr"))]
115            {
116                let interval_value = interval.get();
117                if interval_value == 0 {
118                    return;
119                }
120
121                set_active.set(true);
122
123                let callback = {
124                    let callback = callback.clone();
125
126                    move || {
127                        #[cfg(debug_assertions)]
128                        let _z = leptos::reactive::diagnostics::SpecialNonReactiveZone::enter();
129
130                        callback();
131                    }
132                };
133
134                if immediate_callback {
135                    callback.clone()();
136                }
137                clean();
138
139                timer.set(
140                    set_interval_with_handle(
141                        callback.clone(),
142                        Duration::from_millis(interval_value),
143                    )
144                    .ok(),
145                );
146            }
147        });
148
149        if immediate {
150            resume();
151        }
152
153        {
154            #[allow(clippy::clone_on_copy)]
155            let resume = resume.clone();
156
157            let effect = Effect::watch(
158                move || interval.get(),
159                move |_, _, _| {
160                    if is_active.get() {
161                        resume();
162                    }
163                },
164                false,
165            );
166            on_cleanup(move || effect.stop());
167        }
168
169        on_cleanup({
170            let pause = SendWrapper::new(pause.clone());
171            #[allow(clippy::redundant_closure)]
172            move || pause()
173        });
174    }
175
176    Pausable {
177        is_active: is_active.into(),
178        pause,
179        resume,
180    }
181}
182
183/// Options for [`use_interval_fn_with_options`]
184#[derive(DefaultBuilder)]
185pub struct UseIntervalFnOptions {
186    /// Start the timer immediately. Defaults to `true`.
187    pub immediate: bool,
188
189    /// Execute the callback immediate after calling this function. Defaults to `false`
190    pub immediate_callback: bool,
191}
192
193impl Default for UseIntervalFnOptions {
194    fn default() -> Self {
195        Self {
196            immediate: true,
197            immediate_callback: false,
198        }
199    }
200}