-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Description
Background: Event Types in Reflex
There are three kinds of events that can be bound to a component's event trigger (e.g. on_click):
-
EventHandler — a reference to an
@rx.eventdecorated function defined in anrx.Statesubclass. When bound to a trigger, this generates JS code that adds the event (mapping any trigger arguments) to the frontend queue to be sent to the server over the websocket and processed on the backend. -
Special frontend events — built-in events like
_call_script,_console_log, etc. These also generate JS code that adds the event to the frontend event queue, but they are handled in a special frontend path without going to the backend. -
EventChainVar — an escape hatch where a
Var(representing some expression in the compiled JS) is passed directly as an event trigger. It gets executed inline on the frontend, bypassing all Reflex event loop machinery.
Types 1 and 2 can be "chained" by passing them together in a list to an event trigger prop. Type 3 is special and currently can only be passed by itself.
Problem
Currently, it's not possible to mix normal state event handlers with custom FunctionVar event handlers in an event chain:
import reflex as rx
class State(rx.State):
@rx.event
def do_a_thing(self):
print("Doing a thing!")
log_after_timeout = rx.vars.FunctionStringVar.create(
"(...args) => { setTimeout(() => console.log('Timeout reached!', args), 1000); }"
).to(rx.EventChain)
def index():
return rx.button(
"Do both",
on_click=[
State.do_a_thing, # EventHandler — needs addEvents()
log_after_timeout, # FunctionVar — should be called directly
]
)This fails because LiteralEventChainVar.create() tries to pass everything through a single addEvents([ ... ], args, actions) call. FunctionVar items don't serialize as ReflexEvent(...) objects, so they can't go through addEvents.
The current workaround is rx.call_function(log_after_timeout), but this is a cludge — it still generates code that routes through addEvents.
Goal
Make EventChain generate a JS function body that handles each item appropriately: addEvents() for EventHandler/EventSpec items, and direct function calls for FunctionVar items. The generated code should be a single function that executes each step in sequence.
Currently there are type checks that require a Var to be an EventChainVar, but this should be relaxed to allow any FunctionVar to be used directly in the chain.
Example Expected Output
For the example above, the generated JS should look something like:
(_event) => {
addEvents([ReflexEvent("state.do_a_thing", {})], _event, {});
((...args) => { setTimeout(() => console.log('Timeout reached!', args), 1000); })(_event);
}Rather than trying to pack both into a single addEvents([...]) array.
Acceptance Criteria
-
LiteralEventChainVar.create()results in eachEventHandler/EventSpecevent having a separateaddEvents()(EventChain.invocation) call and invokesFunctionVar/EventVarevents each as direct function calls - The generated JS function body is a sequence of statements (not a single expression), using
explicit_returnor a block body - Mixed chains work:
on_click=[State.handler, some_function_var, State.other_handler]produces 3 separate invocations (addEvents, direct call, addEvents) - Pure EventHandler/EventSpec chains (the common case) should produce separate
addEvents()calls per event — this allows different events to have different actions in the same chain. - Pure FunctionVar chains produce direct calls without
addEvents - Event actions (preventDefault, stopPropagation, etc) are applied correctly regardless of mix
- Unit tests covering: pure EventSpec chain, pure FunctionVar chain, mixed chain, mixed chain with event actions
Key Files
| File | Purpose |
|---|---|
reflex/event.py:2041-2113 |
LiteralEventChainVar.create() — this is the main code to modify. Currently builds a single invocation.call(events_array, args, actions) |
reflex/event.py:449-555 |
EventChain.create() — constructs the EventChain from mixed input types |
reflex/vars/function.py:348-403 |
ArgsFunctionOperation — used to build the JS arrow function. May need explicit_return=True or a block-body variant |
reflex/vars/function.py:410-459 |
ArgsFunctionOperationBuilder — builder pattern variant, base of LiteralEventChainVar |
reflex/constants/compiler.py |
CompileVars.ADD_EVENTS, Hooks.EVENTS, Imports.EVENTS |
reflex/utils/format.py |
format_prop() — where EventChain is converted to a Var for rendering |
Notes
EventChain.eventsis typed asSequence[EventSpec | EventVar | EventCallback]— you'll need to distinguish which items are "addEvents-compatible" (EventSpec/EventHandler/EventVar) vs "direct call" (FunctionVar)- The
ArgsFunctionOperationsupportsexplicit_return=Truewhich generates{ return expr }blocks — you may need a multi-statement block body instead - Look at how
rx.call_functioncurrently wraps FunctionVar to understand the existing workaround