Skip to main content

leptos_use/
use_timeout_fn.rs

1use leptos::prelude::*;
2use std::marker::PhantomData;
3
4/// Wrapper for `setTimeout` with controls.
5///
6/// ## Demo
7///
8/// [Link to Demo](https://github.com/Synphonyte/leptos-use/tree/main/examples/use_timeout_fn)
9///
10/// ## Usage
11///
12/// ```
13/// # use leptos::prelude::*;
14/// # use leptos_use::{use_timeout_fn, UseTimeoutFnReturn};
15/// #
16/// # #[component]
17/// # fn Demo() -> impl IntoView {
18/// let UseTimeoutFnReturn { start, stop, is_pending, .. } = use_timeout_fn(
19///     |i: i32| {
20///         // do sth
21///     },
22///     3000.0
23/// );
24///
25/// start(3);
26/// #
27/// # view! { }
28/// # }
29/// ```
30///
31/// ## Rescheduling
32///
33/// Calling `start` while a previous timer is still pending cancels that pending
34/// timer and schedules a fresh one. Only the most recent `start` call's
35/// callback will fire. This matches VueUse's `useTimeoutFn` semantics.
36///
37/// ## SendWrapped Return
38///
39/// The returned closures `start` and `stop` are sendwrapped functions. They can
40/// only be called from the same thread that called `use_timeout_fn`.
41///
42/// ## Server-Side Rendering
43///
44/// > Make sure you follow the [instructions in Server-Side Rendering](https://leptos-use.rs/server_side_rendering.html).
45///
46/// On the server the callback will never be run. The returned functions are all no-ops and
47/// `is_pending` will always be `false`.
48pub fn use_timeout_fn<CbFn, Arg, D>(
49    callback: CbFn,
50    delay: D,
51) -> UseTimeoutFnReturn<impl Fn(Arg) + Clone + Send + Sync, Arg, impl Fn() + Clone + Send + Sync>
52where
53    CbFn: Fn(Arg) + Clone + 'static,
54    Arg: 'static,
55    D: Into<Signal<f64>>,
56{
57    let delay = delay.into();
58
59    let (is_pending, set_pending) = signal(false);
60
61    let start;
62    let stop;
63
64    #[cfg(not(feature = "ssr"))]
65    {
66        use crate::sendwrap_fn;
67        use leptos::leptos_dom::helpers::TimeoutHandle;
68        use std::sync::{Arc, Mutex};
69        use std::time::Duration;
70
71        let timer = Arc::new(Mutex::new(None::<TimeoutHandle>));
72
73        let clear = {
74            let timer = Arc::clone(&timer);
75
76            move || {
77                let timer = timer.lock().unwrap();
78                if let Some(timer) = *timer {
79                    timer.clear();
80                }
81            }
82        };
83
84        stop = {
85            let clear = clear.clone();
86
87            sendwrap_fn!(move || {
88                set_pending.set(false);
89                clear();
90            })
91        };
92
93        start = {
94            let timer = Arc::clone(&timer);
95            let callback = callback.clone();
96            let clear = clear.clone();
97
98            sendwrap_fn!(move |arg: Arg| {
99                clear();
100                set_pending.set(true);
101
102                let handle = set_timeout_with_handle(
103                    {
104                        let timer = Arc::clone(&timer);
105                        let callback = callback.clone();
106
107                        move || {
108                            set_pending.set(false);
109                            *timer.lock().unwrap() = None;
110
111                            #[cfg(debug_assertions)]
112                            let _z = leptos::reactive::diagnostics::SpecialNonReactiveZone::enter();
113
114                            callback(arg);
115                        }
116                    },
117                    Duration::from_millis(delay.get_untracked() as u64),
118                )
119                .ok();
120
121                *timer.lock().unwrap() = handle;
122            })
123        };
124
125        on_cleanup(clear);
126    }
127
128    #[cfg(feature = "ssr")]
129    {
130        let _ = set_pending;
131        let _ = callback;
132        let _ = delay;
133
134        start = move |_: Arg| ();
135        stop = move || ();
136    }
137
138    UseTimeoutFnReturn {
139        is_pending: is_pending.into(),
140        start,
141        stop,
142        _marker: PhantomData,
143    }
144}
145
146/// Return type of [`use_timeout_fn`].
147pub struct UseTimeoutFnReturn<StartFn, StartArg, StopFn>
148where
149    StartFn: Fn(StartArg) + Clone + Send + Sync,
150    StopFn: Fn() + Clone + Send + Sync,
151{
152    /// Whether the timeout is pending. When the `callback` is called this is set to `false`.
153    pub is_pending: Signal<bool>,
154
155    /// Start the timeout. The `callback` will be called after `delay` milliseconds.
156    /// If a previous timer is still pending, it is cancelled and replaced.
157    pub start: StartFn,
158
159    /// Stop the timeout. If the timeout was still pending the `callback` is not called.
160    pub stop: StopFn,
161
162    _marker: PhantomData<StartArg>,
163}