Skip to content

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

Open
BABTUNA wants to merge 1 commit intoreflex-dev:mainfrom
BABTUNA:fix-6204-cond-event-trigger
Open

fix(event): handle mixed rx.cond event/function returns in event lambdas#6354
BABTUNA wants to merge 1 commit intoreflex-dev:mainfrom
BABTUNA:fix-6204-cond-event-trigger

Conversation

@BABTUNA
Copy link
Copy Markdown

@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.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 21, 2026

Greptile Summary

This PR fixes call_event_fn to accept rx.cond(...) expressions whose branches return heterogeneous event-like types (e.g. a FunctionVar on one branch and an EventSpec on the other). A new inner helper _dispatch_mixed_event_var wraps the runtime-ambiguous Var into a JS arrow function that branches on typeof to either call the frontend function directly or queue it through addEvents.

Confidence Score: 5/5

Safe to merge — the fix is correct and all remaining findings are P2 style/quality suggestions that do not block the primary use case.

No P0 or P1 findings. The over-broad type guard and missing event_actions propagation are both P2: the former is harmless in practice, and the latter is consistent with pre-existing FunctionVar behavior rather than a regression.

packages/reflex-base/src/reflex_base/event/init.py — the new type guard condition and hard-coded event_actions warrant a second look, but neither blocks merge.

Important Files Changed

Filename Overview
packages/reflex-base/src/reflex_base/event/init.py Adds _dispatch_mixed_event_var helper and a broadened type-guard in call_event_fn to wrap rx.cond-produced union-type vars into a runtime-dispatching JS arrow function; minor concerns around over-broad type check and hard-coded empty event_actions.
tests/units/test_event.py Adds test_event_chain_create_lambda_allows_conditional_mixed_function_and_event covering the new rx.cond mixed-type lambda path; assertions are appropriate and reuse the existing make_timeout_logger helper.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["call_event_fn(fn, arg_spec)"] --> B["Invoke fn with parsed args"]
    B --> C["Normalize to list: out = [...]"]
    C --> D["For each e in out"]
    D --> E{"isinstance(e, EventHandler)?"}
    E -- Yes --> F["call_event_handler(e, ...)"]
    E -- No --> G{"isinstance(e, EventChain)?"}
    F --> G
    G -- Yes --> H["e = Var.create(e)"]
    G -- No --> I{"isinstance(e, Var) and\nnot EventVar/FunctionVar and\ntypehint_issubclass(type, EventSpec|Callable)?"}
    H --> I
    I -- Yes --> J["_dispatch_mixed_event_var(e)\n→ JS arrow fn with typeof branch"]
    I -- No --> K{"isinstance(e, EventSpec|FunctionVar|EventVar)?"}
    J --> K
    K -- No --> L["Raise EventHandlerValueError"]
    K -- Yes --> M["events.append(e)"]
    M --> D
    J --> N["JS: (...args) => {\n  const __event_or_fn = ...;\n  if (typeof __event_or_fn === 'function')\n    return __event_or_fn(...args);\n  return addEvents([__event_or_fn], args, {});\n}"]
Loading

Reviews (1): Last reviewed commit: "Handle mixed rx.cond event/function retu..." | Re-trigger Greptile

Comment on lines +2099 to +2104
if (
isinstance(e, Var)
and not isinstance(e, (EventVar, FunctionVar))
and typehint_issubclass(e._var_type, EventSpec | Callable)
):
e = _dispatch_mixed_event_var(e)
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 Over-broad type guard can match non-union types

The condition typehint_issubclass(e._var_type, EventSpec | Callable) returns True for any Var whose _var_type is a subclass of either EventSpec or Callable alone — not just for union types containing both. A plain Var[EventSpec] or Var[Callable] would both be caught here and wrapped unnecessarily by _dispatch_mixed_event_var. While harmless in practice, it silently widens the handling surface beyond the stated intent of "mixed" conditional types. Consider adding a union-origin guard:

if (
    isinstance(e, Var)
    and not isinstance(e, (EventVar, FunctionVar))
    and get_origin(e._var_type) in (Union, types.UnionType)
    and typehint_issubclass(e._var_type, EventSpec | Callable)
):
    e = _dispatch_mixed_event_var(e)

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.

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!

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.

1 participant