Implement `throttle` and `debounce` as event actions
Generic client side throttling and debounce for events.
Used just like .stop_propagation and .prevent_default, so applicable to all event handler or event spec objects.
Due to how events are handled, there isn't currently a way to apply these to only a single event in the chain. If multiple event handlers are specified and any of them have debounce or throttle, then it will apply to all of the other events as well.
If the same function is throttled or debounced at the same limit/delay by multiple event triggers, these will all apply in the same domain. If different limit or delay is used, then they will be debounced or throttled separately.
Sample Code
import time
import reflex as rx
class State(rx.State):
"""The app state."""
_t_events: int = 0
_d_events: int = 0
last_t_event: str
last_d_event: str
slider_value: list[int] = 30
def handle_mouse_move_t(self):
self._t_events += 1
self.last_t_event = f"throttle moved: {self._t_events} {time.time()}"
print(self.last_t_event)
def handle_mouse_move_db(self):
self._d_events += 1
self.last_d_event = f"debounce moved: {self._d_events} {time.time()}"
print(self.last_d_event)
def handle_slider_change(self, value):
self.slider_value = value[0]
def index() -> rx.Component:
return rx.hstack(
rx.box(
rx.vstack(
rx.heading("Throttle"),
rx.box(height="50vh"),
State.last_t_event,
align="center",
padding_top="10vh",
),
on_mouse_move=lambda: State.handle_mouse_move_t.throttle(500),
height="85vh",
width="50vw",
background="linear-gradient(#e66465, #9198e5)"
),
rx.box(
rx.vstack(
rx.heading("Debounce"),
rx.box(height="50vh"),
State.last_d_event,
align="center",
padding_top="10vh",
),
on_mouse_move=State.handle_mouse_move_db.debounce(500),
height="85vh",
width="50vw",
background="linear-gradient(#9198e5, #e66465)"
),
), rx.cond(
State.is_hydrated,
rx.vstack(
rx.heading(State.slider_value),
rx.slider(
default_value=[State.slider_value],
min=0,
max=100,
on_change=State.handle_slider_change.debounce(50),
),
align="center",
)
)
app = rx.App()
app.add_page(index)
https://github.com/reflex-dev/reflex/assets/1524005/5058b6bb-370d-4ef3-b752-cfea7cf22cd5
Edit 2024-04-16: added a debounced slider example. Note it's important to wrap is the is_hydrated cond, otherwise the default_value after refreshing will be the initial value, not the last value.
closes #1482 #2872
This is a much needed feature, let me think a bit more on the final API but I like it