leptos-use/src/use_timeout_fn.rs

151 lines
3.6 KiB
Rust
Raw Normal View History

2024-05-07 12:41:44 +01:00
use leptos::prelude::*;
use std::marker::PhantomData;
/// Wrapper for `setTimeout` with controls.
///
/// ## Demo
///
/// [Link to Demo](https://github.com/Synphonyte/leptos-use/tree/main/examples/use_timeout_fn)
///
/// ## Usage
///
/// ```
2024-05-07 12:41:44 +01:00
/// # use leptos::prelude::*;
/// # use leptos_use::{use_timeout_fn, UseTimeoutFnReturn};
/// #
/// # #[component]
/// # fn Demo() -> impl IntoView {
/// let UseTimeoutFnReturn { start, stop, is_pending, .. } = use_timeout_fn(
/// |i: i32| {
/// // do sth
/// },
/// 3000.0
/// );
///
/// start(3);
/// #
/// # view! { }
/// # }
/// ```
2024-07-31 01:15:20 +01:00
///
/// ## Server-Side Rendering
///
/// On the server the callback will never be run. The returned functions are all no-ops and
/// `is_pending` will always be `false`.
pub fn use_timeout_fn<CbFn, Arg, D>(
callback: CbFn,
delay: D,
) -> UseTimeoutFnReturn<impl Fn(Arg) + Clone, Arg, impl Fn() + Clone>
where
CbFn: Fn(Arg) + Clone + 'static,
Arg: 'static,
D: Into<MaybeSignal<f64>>,
{
let delay = delay.into();
2024-05-07 12:41:44 +01:00
let (is_pending, set_pending) = signal(false);
2024-07-31 01:15:20 +01:00
let start;
let stop;
#[cfg(not(feature = "ssr"))]
{
use leptos::leptos_dom::helpers::TimeoutHandle;
2024-08-22 17:08:21 +01:00
use leptos::prelude::diagnostics::SpecialNonReactiveZone;
2024-07-31 01:15:20 +01:00
use std::cell::Cell;
2024-08-22 17:08:21 +01:00
use std::sync::{Arc, Mutex};
2024-07-31 01:15:20 +01:00
use std::time::Duration;
2024-07-31 01:15:20 +01:00
let delay = delay.into();
let timer = Arc::new(Mutex::new(None::<TimeoutHandle>));
2024-07-31 01:15:20 +01:00
let clear = {
let timer = Arc::clone(&timer);
2024-07-31 01:15:20 +01:00
move || {
let timer = timer.lock().unwrap();
if let Some(timer) = *timer {
2024-07-31 01:15:20 +01:00
timer.clear();
}
}
2024-07-31 01:15:20 +01:00
};
2024-07-31 01:15:20 +01:00
stop = {
let clear = clear.clone();
2024-07-31 01:15:20 +01:00
move || {
set_pending.set(false);
clear();
}
};
2024-07-31 01:15:20 +01:00
start = {
let timer = Arc::clone(&timer);
2024-07-31 01:15:20 +01:00
let callback = callback.clone();
2024-07-31 01:15:20 +01:00
move |arg: Arg| {
set_pending.set(true);
2024-07-31 01:15:20 +01:00
let handle = set_timeout_with_handle(
{
let timer = Arc::clone(&timer);
2024-07-31 01:15:20 +01:00
let callback = callback.clone();
2024-07-31 01:15:20 +01:00
move || {
set_pending.set(false);
*timer.lock().unwrap() = None;
2024-07-31 01:15:20 +01:00
#[cfg(debug_assertions)]
let _z = SpecialNonReactiveZone::enter();
2024-07-31 01:15:20 +01:00
callback(arg);
}
},
Duration::from_millis(delay.get_untracked() as u64),
)
.ok();
*timer.lock().unwrap() = handle;
2024-07-31 01:15:20 +01:00
}
};
2024-07-31 01:15:20 +01:00
on_cleanup(clear);
}
2024-07-31 01:15:20 +01:00
#[cfg(feature = "ssr")]
{
let _ = set_pending;
let _ = callback;
let _ = delay;
2024-07-31 01:15:20 +01:00
start = move |_: Arg| ();
stop = move || ();
}
UseTimeoutFnReturn {
is_pending: is_pending.into(),
start,
stop,
_marker: PhantomData,
}
}
/// Return type of [`use_timeout_fn`].
pub struct UseTimeoutFnReturn<StartFn, StartArg, StopFn>
where
StartFn: Fn(StartArg) + Clone,
StopFn: Fn() + Clone,
{
/// Whether the timeout is pending. When the `callback` is called this is set to `false`.
pub is_pending: Signal<bool>,
/// Start the timeout. The `callback` will be called after `delay` milliseconds.
pub start: StartFn,
/// Stop the timeout. If the timeout was still pending the `callback` is not called.
pub stop: StopFn,
_marker: PhantomData<StartArg>,
}