From ea089688aa215ffd1a8c79f8490715e1e0485a39 Mon Sep 17 00:00:00 2001 From: Jet Li Date: Tue, 29 Mar 2022 00:00:00 +0800 Subject: [PATCH] Add use_throttle (#4) * Add use_throttle --- README.md | 1 + crates/yew-hooks/Cargo.toml | 2 +- crates/yew-hooks/src/hooks/mod.rs | 2 + crates/yew-hooks/src/hooks/use_throttle.rs | 118 ++++++++++++++++++ examples/yew-app/src/routes/home.rs | 1 + examples/yew-app/src/routes/hooks/mod.rs | 2 + .../yew-app/src/routes/hooks/use_throttle.rs | 40 ++++++ examples/yew-app/src/routes/mod.rs | 3 + 8 files changed, 168 insertions(+), 1 deletion(-) create mode 100644 crates/yew-hooks/src/hooks/use_throttle.rs create mode 100644 examples/yew-app/src/routes/hooks/use_throttle.rs diff --git a/README.md b/README.md index 49ae30f..b9f1bce 100644 --- a/README.md +++ b/README.md @@ -95,6 +95,7 @@ fn counter() -> Html { - `use_session_storage` - manages a value in `sessionStorage`. - `use_before_unload` - shows browser alert when user try to reload or close the page. - `use_debounce` - debounces a function. +- `use_throttle` - throttles a function. ### Lifecycles diff --git a/crates/yew-hooks/Cargo.toml b/crates/yew-hooks/Cargo.toml index 2df2eb9..809b1aa 100644 --- a/crates/yew-hooks/Cargo.toml +++ b/crates/yew-hooks/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "yew-hooks" -version = "0.1.48" +version = "0.1.49" edition = "2018" authors = ["Jet Li "] categories = ["gui", "wasm", "web-programming"] diff --git a/crates/yew-hooks/src/hooks/mod.rs b/crates/yew-hooks/src/hooks/mod.rs index 21a4b5b..74c67da 100644 --- a/crates/yew-hooks/src/hooks/mod.rs +++ b/crates/yew-hooks/src/hooks/mod.rs @@ -37,6 +37,7 @@ mod use_set; mod use_size; mod use_state_ptr_eq; mod use_swipe; +mod use_throttle; mod use_timeout; mod use_title; mod use_toggle; @@ -85,6 +86,7 @@ pub use use_set::*; pub use use_size::*; pub use use_state_ptr_eq::*; pub use use_swipe::*; +pub use use_throttle::*; pub use use_timeout::*; pub use use_title::*; pub use use_toggle::*; diff --git a/crates/yew-hooks/src/hooks/use_throttle.rs b/crates/yew-hooks/src/hooks/use_throttle.rs new file mode 100644 index 0000000..6fbabbd --- /dev/null +++ b/crates/yew-hooks/src/hooks/use_throttle.rs @@ -0,0 +1,118 @@ +use std::rc::Rc; + +use yew::prelude::*; + +use super::{use_mut_latest, use_timeout}; + +/// State handle for the [`use_throttle`] hook. +pub struct UseThrottleHandle { + run: Rc, + cancel: Rc, +} + +impl UseThrottleHandle { + /// Run the throttle. + pub fn run(&self) { + (&self.run)() + } + + /// Cancel the throttle. + pub fn cancel(&self) { + (&self.cancel)() + } +} + +impl Clone for UseThrottleHandle { + fn clone(&self) -> Self { + Self { + run: self.run.clone(), + cancel: self.cancel.clone(), + } + } +} + +/// A hook that throttles invoking a function, the function is only executed once every `millis`. +/// +/// # Example +/// +/// ```rust +/// # use yew::prelude::*; +/// # +/// use yew_hooks::use_throttle; +/// +/// #[function_component(Throttle)] +/// fn throttle() -> Html { +/// let state = use_state(|| 0); +/// +/// let throttle = { +/// let state = state.clone(); +/// use_throttle( +/// move || { +/// state.set(*state + 1); +/// }, +/// 2000, +/// ) +/// }; +/// +/// let onclick = { +/// let throttle = throttle.clone(); +/// Callback::from(move |_| throttle.run()) +/// }; +/// +/// let oncancel = { Callback::from(move |_| throttle.cancel()) }; +/// +/// html! { +///
+///
+///
+/// +/// +///

+/// { "State: " } {*state} +///

+///
+///
+///
+/// } +/// } +/// ``` +pub fn use_throttle(callback: Callback, millis: u32) -> UseThrottleHandle +where + Callback: FnMut() + 'static, +{ + let throttled = use_mut_ref(|| false); + let callback_ref = use_mut_latest(callback); + let timeout = { + let throttled = throttled.clone(); + use_timeout( + move || { + *throttled.borrow_mut() = false; + }, + millis, + ) + }; + + let run = { + let throttled = throttled.clone(); + let timeout = timeout.clone(); + Rc::new(move || { + let throttled_value = *throttled.borrow(); + if !throttled_value { + let callback_ref = callback_ref.current(); + let callback = &mut *callback_ref.borrow_mut(); + callback(); + *throttled.borrow_mut() = true; + timeout.reset(); + } + }) + }; + + let cancel = { + Rc::new(move || { + timeout.cancel(); + *throttled.borrow_mut() = false; + }) + }; + + UseThrottleHandle { run, cancel } +} diff --git a/examples/yew-app/src/routes/home.rs b/examples/yew-app/src/routes/home.rs index 1c9ce53..e8a925e 100644 --- a/examples/yew-app/src/routes/home.rs +++ b/examples/yew-app/src/routes/home.rs @@ -41,6 +41,7 @@ pub fn home() -> Html {
  • to={AppRoute::UseSessionStorage} classes="app-link" >{ "use_session_storage" }> { " - manages a value in sessionStorage." }
  • to={AppRoute::UseBeforeUnload} classes="app-link" >{ "use_before_unload" }> { " - shows browser alert when user try to reload or close the page." }
  • to={AppRoute::UseDebounce} classes="app-link" >{ "use_debounce" }> { " - debounces a function." }
  • +
  • to={AppRoute::UseThrottle} classes="app-link" >{ "use_throttle" }> { " - throttles a function." }
  • { "Lifecycles" }

    diff --git a/examples/yew-app/src/routes/hooks/mod.rs b/examples/yew-app/src/routes/hooks/mod.rs index 210b579..6c224c1 100644 --- a/examples/yew-app/src/routes/hooks/mod.rs +++ b/examples/yew-app/src/routes/hooks/mod.rs @@ -39,6 +39,7 @@ mod use_set; mod use_size; mod use_state_ptr_eq; mod use_swipe; +mod use_throttle; mod use_timeout; mod use_title; mod use_toggle; @@ -89,6 +90,7 @@ pub use use_set::*; pub use use_size::*; pub use use_state_ptr_eq::*; pub use use_swipe::*; +pub use use_throttle::*; pub use use_timeout::*; pub use use_title::*; pub use use_toggle::*; diff --git a/examples/yew-app/src/routes/hooks/use_throttle.rs b/examples/yew-app/src/routes/hooks/use_throttle.rs new file mode 100644 index 0000000..797773e --- /dev/null +++ b/examples/yew-app/src/routes/hooks/use_throttle.rs @@ -0,0 +1,40 @@ +use yew::prelude::*; + +use yew_hooks::use_throttle; + +/// `use_throttle` demo +#[function_component(UseThrottle)] +pub fn throttle() -> Html { + let state = use_state(|| 0); + + let throttle = { + let state = state.clone(); + use_throttle( + move || { + state.set(*state + 1); + }, + 2000, + ) + }; + + let onclick = { + let throttle = throttle.clone(); + Callback::from(move |_| throttle.run()) + }; + + let oncancel = { Callback::from(move |_| throttle.cancel()) }; + + html! { +
    +
    +
    + + +

    + { "State: " } {*state} +

    +
    +
    +
    + } +} diff --git a/examples/yew-app/src/routes/mod.rs b/examples/yew-app/src/routes/mod.rs index 85444b6..332ee53 100644 --- a/examples/yew-app/src/routes/mod.rs +++ b/examples/yew-app/src/routes/mod.rs @@ -112,6 +112,8 @@ pub enum AppRoute { UseDebounce, #[at("/use_debounce_state")] UseDebounceState, + #[at("/use_throttle")] + UseThrottle, #[not_found] #[at("/page-not-found")] PageNotFound, @@ -173,6 +175,7 @@ pub fn switch(routes: &AppRoute) -> Html { AppRoute::UseDefault => html! { }, AppRoute::UseDebounce => html! { }, AppRoute::UseDebounceState => html! { }, + AppRoute::UseThrottle => html! { }, AppRoute::PageNotFound => html! { }, } }