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