Skip to content

Commit df626d0

Browse files
committed
fix: remove EventChain fast path to preserve per-spec event actions
The fast path grouped backend-only EventSpecs into a single addEvents call, which lost per-spec event actions like individual debounce values. Each EventSpec now renders its own addEvents call, and chain-level actions use applyEventActions consistently.
1 parent 1d7e57e commit df626d0

3 files changed

Lines changed: 41 additions & 27 deletions

File tree

reflex/event.py

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2115,22 +2115,6 @@ def create(
21152115
raise ValueError(msg)
21162116
assert invocation is not None
21172117

2118-
if not any(isinstance(event, FunctionVar) for event in value.events):
2119-
return cls(
2120-
_js_expr="",
2121-
_var_type=EventChain,
2122-
_var_data=_var_data,
2123-
_args=FunctionArgs(arg_def),
2124-
_return_expr=invocation.call(
2125-
LiteralVar.create([
2126-
LiteralVar.create(event) for event in value.events
2127-
]),
2128-
arg_def_expr,
2129-
value.event_actions,
2130-
),
2131-
_var_value=value,
2132-
)
2133-
21342118
call_args = arg_vars if sig.parameters else (Var(_js_expr="...args"),)
21352119
statements = [
21362120
(

tests/units/test_event.py

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -760,23 +760,52 @@ def do_a_thing(self):
760760
assert isinstance(button.event_triggers["on_click"], EventChain)
761761

762762

763-
def test_event_chain_codegen_uses_fast_path_for_backend_only_events():
764-
"""Backend-only chains should render through a single addEvents call."""
763+
def test_event_chain_codegen_preserves_backend_event_actions_per_spec():
764+
"""Backend-only chains should keep per-spec event actions separate."""
765765

766766
class FastPathState(BaseState):
767767
@event
768-
def do_a_thing(self):
768+
def do_a_thing(self, value: str):
769769
pass
770770

771771
chain = EventChain.create(
772-
[FastPathState.do_a_thing, FastPathState.do_a_thing],
772+
[
773+
FastPathState.do_a_thing("first x 1000").debounce(1000),
774+
FastPathState.do_a_thing("second x 200").debounce(200),
775+
],
773776
args_spec=lambda: (),
774-
event_actions={"preventDefault": True},
775777
)
776778
rendered = str(LiteralVar.create(chain))
777779

778780
assert "applyEventActions(" not in rendered
779-
assert rendered.count("addEvents(") == 1
781+
assert rendered.count("addEvents(") == 2
782+
assert '["debounce"] : 1000' in rendered
783+
assert '["debounce"] : 200' in rendered
784+
assert rendered.index("first x 1000") < rendered.index("second x 200")
785+
786+
787+
def test_event_chain_codegen_keeps_chain_event_actions_for_backend_only_events():
788+
"""Chain-level actions should still wrap backend-only event chains."""
789+
790+
class FastPathState(BaseState):
791+
@event
792+
def do_a_thing(self, value: str):
793+
pass
794+
795+
chain = EventChain.create(
796+
[
797+
FastPathState.do_a_thing("first x 1000").debounce(1000),
798+
FastPathState.do_a_thing("second x 200").debounce(200),
799+
],
800+
args_spec=lambda: (),
801+
event_actions={"preventDefault": True},
802+
)
803+
rendered = str(LiteralVar.create(chain))
804+
805+
assert "applyEventActions(" in rendered
806+
assert rendered.count("addEvents(") == 2
807+
assert '["debounce"] : 1000' in rendered
808+
assert '["debounce"] : 200' in rendered
780809
assert '["preventDefault"] : true' in rendered
781810

782811

tests/units/utils/test_format.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def make_timeout_logger():
5353

5454

5555
def test_format_prop_event_chain_pure_eventspec_grouped():
56-
"""Pure EventSpec chains should stay grouped in one addEvents call."""
56+
"""Pure EventSpec chains should preserve order with separate addEvents calls."""
5757
chain = EventChain(
5858
events=[
5959
EventSpec(handler=EventHandler(fn=mock_event)),
@@ -63,8 +63,9 @@ def test_format_prop_event_chain_pure_eventspec_grouped():
6363
)
6464

6565
assert format.format_prop(LiteralVar.create(chain)) == (
66-
'((_e) => (addEvents([(ReflexEvent("mock_event", ({ }), ({ }))), '
67-
'(ReflexEvent("mock_event_two", ({ }), ({ })))], [_e], ({ }))))'
66+
'((_e) => {(addEvents([(ReflexEvent("mock_event", ({ }), ({ })))], '
67+
'[_e], ({ })));(addEvents([(ReflexEvent("mock_event_two", ({ }), '
68+
"({ })))], [_e], ({ })));})"
6869
)
6970

7071

@@ -510,7 +511,7 @@ def test_format_match(
510511
args_spec=no_args_event_spec,
511512
event_actions={"stopPropagation": True},
512513
),
513-
'((...args) => (addEvents([(ReflexEvent("mock_event", ({ }), ({ })))], args, ({ ["stopPropagation"] : true }))))',
514+
'((...args) => (applyEventActions((() => {(addEvents([(ReflexEvent("mock_event", ({ }), ({ })))], args, ({ })));}), ({ ["stopPropagation"] : true }), ...args)))',
514515
),
515516
(
516517
EventChain(
@@ -530,7 +531,7 @@ def test_format_match(
530531
args_spec=no_args_event_spec,
531532
event_actions={"preventDefault": True},
532533
),
533-
'((...args) => (addEvents([(ReflexEvent("mock_event", ({ }), ({ })))], args, ({ ["preventDefault"] : true }))))',
534+
'((...args) => (applyEventActions((() => {(addEvents([(ReflexEvent("mock_event", ({ }), ({ })))], args, ({ })));}), ({ ["preventDefault"] : true }), ...args)))',
534535
),
535536
({"a": "red", "b": "blue"}, '({ ["a"] : "red", ["b"] : "blue" })'),
536537
(Var(_js_expr="var", _var_type=int).guess_type(), "var"),

0 commit comments

Comments
 (0)