Decentralized Event Handlers
Overview
Decentralized event handlers allow you to define event handlers outside of state classes, providing more flexible code organization. This feature was introduced in Reflex v0.7.10 and enables a more modular approach to event handling.
With decentralized event handlers, you can:
- Organize event handlers by feature rather than by state class
- Separate UI logic from state management
- Create more maintainable and scalable applications
Basic Usage
To create a decentralized event handler, use the @rx.event decorator on a function that takes a state instance as its first parameter:
Count: 0
In this example:
- We define a
MyStateclass with acountvariable - We create a decentralized event handler
incrementthat takes aMyStateinstance as its first parameter - We use the event handler in buttons, passing different amounts to increment by
Compared to Traditional Event Handlers
Here's a comparison between traditional event handlers defined within state classes and decentralized event handlers:
# Traditional event handler within a state class
class TraditionalState(rx.State):
count: int = 0
@rx.event
def increment(self, amount: int = 1):
self.count += amount
# Usage in components
rx.button("Increment", on_click=TraditionalState.increment(5))
# Decentralized event handler outside the state class
class DecentralizedState(rx.State):
count: int = 0
@rx.event
def increment(state: DecentralizedState, amount: int = 1):
state.count += amount
# Usage in components
rx.button("Increment", on_click=increment(5))ExpandCollapse
Key differences:
- Traditional event handlers use
selfto reference the state instance - Decentralized event handlers explicitly take a state instance as the first parameter
- Both approaches use the same syntax for triggering events in components
- Both can be decorated with
@rx.eventrespectively
Best Practices
When to Use Decentralized Event Handlers
Decentralized event handlers are particularly useful in these scenarios:
- Large applications with many event handlers that benefit from better organization
- Feature-based organization where you want to group related event handlers together
- Separation of concerns when you want to keep state definitions clean and focused
Type Annotations
Always use proper type annotations for your state parameter and any additional parameters:
@rx.event
def update_user(state: UserState, name: str, age: int):
state.name = name
state.age = ageNaming Conventions
Follow these naming conventions for clarity:
- Use descriptive names that indicate the action being performed
- Use the state class name as the type annotation for the first parameter
- Name the state parameter consistently across your codebase (e.g., always use
stateor the first letter of the state class)
Organization
Consider these approaches for organizing decentralized event handlers:
- Group related event handlers in the same file
- Place event handlers near the state classes they modify
- For larger applications, create a dedicated
eventsdirectory with files organized by feature
# Example organization in a larger application
# events/user_events.py
@rx.event
def update_user(state: UserState, name: str, age: int):
state.name = name
state.age = age
@rx.event
def delete_user(state: UserState):
state.name = ""
state.age = 0Combining with Other Event Features
Decentralized event handlers work seamlessly with other Reflex event features:
# Background event
@rx.event(background=True)
async def long_running_task(state: AppState):
# Long-running task implementation
pass
# Event chaining
@rx.event
def process_form(state: FormState, data: dict):
# Process form data
return validate_data # Chain to another eventStateless Event Handlers
A function decorated with @rx.event whose first parameter is not a state is a
stateless event handler. It is registered like any other handler, so it can be
used as an event trigger and awaited from other handlers, but it runs without a
state and takes no state lock of its own. Triggered from the frontend, it runs
at once and the client's other events do not wait behind it; awaited from a
handler, it runs inline under whatever lock that handler holds.
class PortfolioState(rx.State):
price: float = 0.0
@rx.event
def show_price(self, price: float):
self.price = price
@rx.event
async def refresh_quote(symbol: str):
# No state lock is held while the request is in flight.
yield PortfolioState.show_price(await price_service.quote(symbol))
def quote_button():
return rx.button("Refresh", on_click=refresh_quote("ACME"))Events a stateless handler yields or returns run against the state of the
client that triggered it, so refresh_quote fetches the quote with no lock
held and then hands the result to PortfolioState like any chained event.
Without the decorator, refresh_quote would be a plain function that cannot be
used as a trigger. Awaiting a stateless handler from another handler runs it
inline, so call external services from a
background task or from a stateless trigger
like this one, never from inside async with self.