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/// ## SendWrapped Return
32///
33/// The returned closures `start` and `stop` are sendwrapped functions. They can
34/// only be called from the same thread that called `use_timeout_fn`.
35///
36/// ## Server-Side Rendering
37///
38/// > Make sure you follow the [instructions in Server-Side Rendering](https://leptos-use.rs/server_side_rendering.html).
39///
40/// On the server the callback will never be run. The returned functions are all no-ops and
41/// `is_pending` will always be `false`.
42pub fn use_timeout_fn<CbFn, Arg, D>(
43    callback: CbFn,
44    delay: D,
45) -> UseTimeoutFnReturn<impl Fn(Arg) + Clone + Send + Sync, Arg, impl Fn() + Clone + Send + Sync>
46where
47    CbFn: Fn(Arg) + Clone + 'static,
48    Arg: 'static,
49    D: Into<Signal<f64>>,
50{
51    let delay = delay.into();
52
53    let (is_pending, set_pending) = signal(false);
54
55    let start;
56    let stop;
57
58    #[cfg(not(feature = "ssr"))]
59    {
60        use crate::sendwrap_fn;
61        use leptos::leptos_dom::helpers::TimeoutHandle;
62        use std::sync::{Arc, Mutex};
63        use std::time::Duration;
64
65        let timer = Arc::new(Mutex::new(None::<TimeoutHandle>));
66
67        let clear = {
68            let timer = Arc::clone(&timer);
69
70            move || {
71                let timer = timer.lock().unwrap();
72                if let Some(timer) = *timer {
73                    timer.clear();
74                }
75            }
76        };
77
78        stop = {
79            let clear = clear.clone();
80
81            sendwrap_fn!(move || {
82                set_pending.set(false);
83                clear();
84            })
85        };
86
87        start = {
88            let timer = Arc::clone(&timer);
89            let callback = callback.clone();
90
91            sendwrap_fn!(move |arg: Arg| {
92                set_pending.set(true);
93
94                let handle = set_timeout_with_handle(
95                    {
96                        let timer = Arc::clone(&timer);
97                        let callback = callback.clone();
98
99                        move || {
100                            set_pending.set(false);
101                            *timer.lock().unwrap() = None;
102
103                            #[cfg(debug_assertions)]
104                            let _z = leptos::reactive::diagnostics::SpecialNonReactiveZone::enter();
105
106                            callback(arg);
107                        }
108                    },
109                    Duration::from_millis(delay.get_untracked() as u64),
110                )
111                .ok();
112
113                *timer.lock().unwrap() = handle;
114            })
115        };
116
117        on_cleanup(clear);
118    }
119
120    #[cfg(feature = "ssr")]
121    {
122        let _ = set_pending;
123        let _ = callback;
124        let _ = delay;
125
126        start = move |_: Arg| ();
127        stop = move || ();
128    }
129
130    UseTimeoutFnReturn {
131        is_pending: is_pending.into(),
132        start,
133        stop,
134        _marker: PhantomData,
135    }
136}
137
138/// Return type of [`use_timeout_fn`].
139pub struct UseTimeoutFnReturn<StartFn, StartArg, StopFn>
140where
141    StartFn: Fn(StartArg) + Clone + Send + Sync,
142    StopFn: Fn() + Clone + Send + Sync,
143{
144    /// Whether the timeout is pending. When the `callback` is called this is set to `false`.
145    pub is_pending: Signal<bool>,
146
147    /// Start the timeout. The `callback` will be called after `delay` milliseconds.
148    pub start: StartFn,
149
150    /// Stop the timeout. If the timeout was still pending the `callback` is not called.
151    pub stop: StopFn,
152
153    _marker: PhantomData<StartArg>,
154}