Skip to content

Instantly share code, notes, and snippets.

@davidatsurge
Last active January 16, 2023 22:59
Show Gist options
  • Save davidatsurge/cf5cc237d7e40c9903f218635509ba7c to your computer and use it in GitHub Desktop.
Save davidatsurge/cf5cc237d7e40c9903f218635509ba7c to your computer and use it in GitHub Desktop.
A `debounce` for `sycamore-rs` (using crates`gloo_timers` and `pin_project`). Look at `main.rs` below for one would use it.
use std::cell::RefCell;
use futures_util::stream::{AbortHandle, Abortable};
use gloo_timers::future::TimeoutFuture;
use sycamore::{
futures::spawn_local_scoped,
reactive::{create_ref, Scope},
};
pub struct Debounced<'a, F>
where
F: FnMut() -> (),
{
cx: Scope<'a>,
/// We need the `RefCell` in order to debounce `FnMut`s because we'd need a `& mut` reference
/// to the `F` to do so, and when we allocate on sycamore's `Scope`, we cannot get a `&
/// mut` reference.
f: &'a RefCell<F>,
/// Not `std::time::Duration` because that allows u128 values for milli
/// seconds, but `TimeoutFuture` only allows u32.
duration_millis: u32,
abort_handle: &'a AbortHandle,
}
impl<'a, F> Debounced<'a, F>
where
F: FnMut() -> () + 'a,
{
pub fn new(cx: Scope<'a>, f: F, duration_millis: u32) -> Self {
// Abort handle that doesn't do anything. (We could use an `Option<&'a AbortHandle>` and
// set it to `None` when we don't have an active timeout, but this is simpler.)
let (abort_handle, _) = AbortHandle::new_pair();
Self {
cx,
f: create_ref(cx, RefCell::new(f)),
duration_millis,
abort_handle: create_ref(cx, abort_handle),
}
}
pub fn call(&mut self) {
self.abort_handle.abort();
let duration_millis = self.duration_millis;
let f = self.f;
let future = async move {
TimeoutFuture::new(duration_millis).await;
(f.borrow_mut())();
};
let (abort_handle, abort_registration) = AbortHandle::new_pair();
let future = Abortable::new(future, abort_registration);
spawn_local_scoped(
self.cx,
// We need to create another future because `spawn_local_scoped` takes futures that
// resolve to `()`, but this resolves to a `Result`.
#[allow(unused_must_use)]
async {
future.await;
},
);
self.abort_handle = create_ref(self.cx, abort_handle);
}
}
pub trait DebouncedTrait<'a>
where
Self: Fn() -> () + Sized + 'a,
{
fn debounced(self, cx: Scope<'a>, duration_millis: u32) -> Debounced<Self> {
Debounced::new(cx, self, duration_millis)
}
}
impl<'a, F> DebouncedTrait<'a> for F where F: Fn() -> () + Sized + 'a {}
/// This file is here to show how one would use the debounce implementation.
use super::debounce::DebouncedTrait;
use sycamore::builder::prelude::*;
fn main() {
sycamore::render(|cx| {
let closure = || {
println!("hi!");
};
// NOTE: Sycamore signal dependencies that are accessed in `closure` above will NOT be automatically tracked
// if `debounced_closure` is called in an effect because the closure will be executed in a spawned future,
// which means Sycamore will/can not track the dependencies accessed in there.
// A work around is to manually call `.track()` inside the effect that the debounced closure is called in
// for every dependency accessed in `closure`/`debounced_closure`.
let mut debounced_closure = closure.debounced(cx, 1000);
debounced_closure.call();
div().view(cx)
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment