Last active
January 16, 2023 22:59
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// 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