Skip to content

fix(event): handle mixed rx.cond event/function returns in event lambdas#6354

Merged
masenf merged 6 commits into
reflex-dev:mainfrom
BABTUNA:fix-6204-cond-event-trigger
May 8, 2026
Merged

fix(event): handle mixed rx.cond event/function returns in event lambdas#6354
masenf merged 6 commits into
reflex-dev:mainfrom
BABTUNA:fix-6204-cond-event-trigger

Conversation

@BABTUNA
Copy link
Copy Markdown
Contributor

@BABTUNA BABTUNA commented Apr 21, 2026

All Submissions:

  • Have you followed the guidelines stated in [CONTRIBUTING.md](https://
    github.com/reflex-dev/reflex/blob/main/CONTRIBUTING.md) file?
  • Have you checked to ensure there aren't any other open Pull
    Requests
    for the desired
    changed?

Type of change

  • Bug fix (non-breaking change which fixes an issue)

New Feature Submission:

  • Does your submission pass the tests?
  • Have you linted your code locally prior to submission?

Changes To Core Features:

  • Have you added an explanation of what your changes do and why you'd
    like us to include them?
  • Have you written new tests for your core changes, as applicable?
  • Have you successfully ran tests with your changes locally?

Description

This fixes event-lambda handling for rx.cond(...) expressions that return
mixed event-like values (frontend function branch vs backend event branch).

Before this change, call_event_fn rejected this pattern because the
lambda return was a Var (CustomVarOperation) rather than one of:

  • EventSpec
  • EventHandler
  • EventVar
  • FunctionVar
    That made valid mixed conditional handlers fail during event-chain
    creation.

handles issue #6204

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 21, 2026

Greptile Summary

This PR fixes call_event_fn to handle rx.cond expressions that return a union of EventSpec and FunctionVar (frontend callable) branches, which previously caused an EventHandlerValueError at chain-creation time. It also fixes a pre-existing leading-comma bug in format_args_function_operation when a rest parameter is used with no positional arguments.

  • event/__init__.py: Adds a _dispatch_mixed_event_var helper that wraps a union-typed Var in a JS IIFE using a typeof check to route execution at runtime — calling the branch directly if it's a function, or queuing it via addEvents if it's an event object. A union-origin guard (get_origin(...) in (Union, types.UnionType)) ensures plain Callable or EventSpec vars are not caught by the new path.
  • vars/function.py: Fixes format_args_function_operation to build the argument list from parts before joining, eliminating a spurious leading comma when no positional args precede a rest parameter.
  • Tests: New unit tests cover the happy-path mixed cond, the rejection of non-union callables, and both rest-arg rendering cases; a new integration test exercises both branches end-to-end.

Confidence Score: 5/5

Safe to merge; the fix is well-scoped and all previously-raised review concerns have been addressed.

The union-origin guard prevents over-broad matching, the JS dispatcher is structurally correct (confirmed by unit test assertions on rendered output), the leading-comma fix in format_args_function_operation is a mechanical improvement with direct test coverage, and no existing behaviour is altered for the non-union code paths.

The integration test in tests/integration/test_event_chain.py contains a minor poll condition that may resolve on intermediate state mid-typing, but this does not affect the correctness of the feature itself.

Important Files Changed

Filename Overview
packages/reflex-base/src/reflex_base/event/init.py Adds _dispatch_mixed_event_var helper and union-origin type guard to handle rx.cond lambdas that return a mix of EventSpec and FunctionVar; guard now correctly requires a Union/UnionType origin as requested in prior review
packages/reflex-base/src/reflex_base/vars/function.py Fixes a leading-comma bug in format_args_function_operation when a rest arg is used without any positional args
tests/units/test_event.py Adds two unit tests: happy-path mixed cond and rejection of non-union callable Var
tests/units/test_var.py Adds two targeted tests for ArgsFunctionOperation covering rest-only and rest+positional argument rendering
tests/integration/test_event_chain.py Adds end-to-end integration test; poll_for_content(marker, exp_not_equal='') may resolve on intermediate state 'f' before 'fn' is fully applied

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["call_event_fn(fn, arg_spec)"] --> B["Invoke fn with parsed args"]
    B --> C["Normalize out to list"]
    C --> D["For each e in out"]
    D --> E{isinstance EventHandler?}
    E -- yes --> F["call_event_handler"]
    F --> G
    E -- no --> G{isinstance EventChain?}
    G -- yes --> H["Var.create(e)"]
    H --> I
    G -- no --> I{"Var, not EventVar/FunctionVar\nUnion origin\ntypehint_issubclass EventSpec|Callable?"}
    I -- yes --> J["_dispatch_mixed_event_var(e)"]
    J --> K
    I -- no --> K{"EventSpec, FunctionVar, or EventVar?"}
    K -- yes --> L["events.append(e)"]
    K -- no --> M["raise EventHandlerValueError"]
    subgraph "_dispatch_mixed_event_var emits"
        N["const __event_or_fn = cond_expr"]
        O{"typeof === 'function'?"}
        N --> O
        O -- true --> P["__event_or_fn(...args)"]
        O -- false --> Q["addEvents([__event_or_fn], args, {})"]
    end
    J -.->|generates| N
Loading

Reviews (2): Last reviewed commit: "Merge remote-tracking branch 'origin/mai..." | Re-trigger Greptile

Comment thread packages/reflex-base/src/reflex_base/event/__init__.py
f'if (typeof {event_like_name} === "function") {{'
f"return {event_like_name}(...args);"
"}"
f"return {CompileVars.ADD_EVENTS}([{event_like_name}], args, ({{ }}));"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Chain-level event_actions not propagated to conditional backend events

_dispatch_mixed_event_var hard-codes ({ }) (an empty object) as the event_actions argument to addEvents. If this dispatcher ends up in an EventChain carrying chain-level event_actions (e.g. preventDefault: True), those actions will be silently dropped for any conditional branch that resolves to a backend event. The existing EventVarinvocation.call(...) path threads those actions correctly, but the new FunctionVar wrapper bypasses that path. This is consistent with pre-existing FunctionVar behavior (not a regression), but worth noting for future callers.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bit out of the scope of this PR imo

f'if (typeof {event_like_name} === "function") {{'
f"return {event_like_name}(...args);"
"}"
f"return {CompileVars.ADD_EVENTS}([{event_like_name}], args, ({{ }}));"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Extraneous spaces in empty object literal

({{ }}) produces ({ }) — a parenthesised empty object with two interior spaces — in the emitted JavaScript. This is valid but inconsistent with idiomatic {}. Consider {{}} instead.

Suggested change
f"return {CompileVars.ADD_EVENTS}([{event_like_name}], args, ({{ }}));"
f"return {CompileVars.ADD_EVENTS}([{event_like_name}], args, ({{}}));"

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ight will fix

@BABTUNA BABTUNA requested a review from a team as a code owner April 29, 2026 02:12
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Apr 29, 2026

Merging this PR will not alter performance

✅ 24 untouched benchmarks
⏩ 2 skipped benchmarks1


Comparing BABTUNA:fix-6204-cond-event-trigger (96ca4a8) with main (1eed933)

Open in CodSpeed

Footnotes

  1. 2 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@BABTUNA
Copy link
Copy Markdown
Contributor Author

BABTUNA commented Apr 29, 2026

@masenf plz review when you get a chance 🙏🙏🙏

Copy link
Copy Markdown
Collaborator

@masenf masenf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since this behavior is very dependent on how the generated code actually works, we need to see an integration test (or extend an existing integration test) that exercises this behavior end to end.

Comment on lines +2069 to +2087
return Var(
_js_expr=(
"(...args) => {"
f"const {event_like_name} = {event_like_var!s};"
f'if (typeof {event_like_name} === "function") {{'
f"return {event_like_name}(...args);"
"}"
f"return {CompileVars.ADD_EVENTS}([{event_like_name}], args, ({{}}));"
"}"
),
_var_type=Callable,
_var_data=VarData.merge(
event_like_var._get_all_var_data(),
VarData(
imports=Imports.EVENTS,
hooks={Hooks.EVENTS: None},
),
),
).to(FunctionVar)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

prefer to use ArgsFunctionOperations.create helper with rest and explicit_return arguments.

using a structured Var is helpful because it allows the compiler to optimize the form down the road without changing user-facing code and it tends to be less brittle w.r.t composition, carry var data, etc.

masenf added 4 commits May 7, 2026 16:21
Build the runtime dispatcher with ArgsFunctionOperation.create + a
ternary_operation Var instead of a hand-rolled JS string so the form
composes naturally and var data propagates through the helper.

Extends the EventChain integration test app with a mixed-cond button
that exercises both branches end-to-end: the FunctionVar branch writes
a DOM data-attribute via a frontend-only function, and the EventSpec
branch dispatches a backend handler.
When args_names is empty and rest is set, the formatter produced
"(, ...args) => ..." — invalid JS. Build the parameter list as a list
joined with ", " instead so an empty positional list does not introduce
a stray separator.
@masenf
Copy link
Copy Markdown
Collaborator

masenf commented May 8, 2026

@greptile-apps re-review

@BABTUNA
Copy link
Copy Markdown
Contributor Author

BABTUNA commented May 8, 2026

oh shoot sorry @masenf for being a little late on the fixes! Lemme go ahead and update my other branches to clear up some of the backlog

@masenf masenf merged commit 414af15 into reflex-dev:main May 8, 2026
69 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants