Fix uncalled rx.toast hint and dropped-result warning lookup
Status
The original change in this PR landed on main through the squash merge of the stacked #7821 (554eb10a6). The branch now merges main, so the diff holds only two follow-up fixes to that code:
- Uncalled
rx.toasthint. For an uncalledrx.toastorrx.toast.<type>, the "Did you mean to call it" hint named the internalToaster.send_toast(...)/Toaster.toast_*(...)methods, and the tests asserted that text._creator_referencenow names a creatorrx.<name>when reflex exports it under that name, such asrx.redirect(...). For any other creator defined by reflex, whose name is internal, the hint is "Did you mean to call it?" with no name. An app's own creator keeps its qualified name, such as_go_home(...). - Dropped-result warning lookup.
_warn_dropped_resultindexedapp._registration_context.event_handlers[name], which raisesKeyErrorin the done callback when the name is not in the app's registry. It now uses.get(name)and falls back to the event name. In a running app, the processor resolves the handler from the same registry, so this is a robustness fix only.
Test plan
test_returned_mistaken_event_raises_type_error: 3 toast cases now expect "Did you mean to call it?" / "Did you mean to call them?"; a new case returns an app's own uncalled creator and expects "Did you mean to call it: _go_home(...)?".- New
test_dropped_result_warning_names_an_unregistered_eventintests/units/test_app.py. It andtest_on_event_warns_once_in_dev_when_a_frontend_result_is_droppedassert the full warning message. - All 4 fail against
main's source and pass with the fix. The 30 related targeted tests pass;ruff,pyrightand pre-commit are clean.
News fragments: news/+dropped-result-warning-lookup.bugfix.md, packages/reflex-base/news/+uncalled-toast-hint.bugfix.md.
Original description (landed via #7821)
Fixes FINDING-015 from the reflex 0.11.0a1 pre-release testing (artifacts on branch claude/vibrant-pasteur-3921g3, prerelease-testing/runs/2026-11-11-v0.11.0a1/events_await/ and verify_evt/).
Defect
Since handlers may return a value to the backend code awaiting them (#7673), every non-event return value became the handler's result. That silently swallowed the mistakes 0.10.1 reported with TypeError: Your handler ... must only return/yield: None, Events or other EventHandlers referenced by their class ...: returning self.other_handler (meant type(self).other_handler), an uncalled event creator such as rx.redirect, or a list holding one. On 0.11.0a1 the chained handler never runs and nothing is logged or toasted.
Fix
The rule stays narrow on purpose, so handlers keep returning arbitrary values to their awaiting callers: only values that can never be a meaningful result raise.
base_state_processor.py: after_is_event_likerejects a value,_is_invalid_resultflags (as before) an unawaited coroutine, plus- a handler function bound to its state or to a background task's state proxy (
self.other_handler), recognised as a bound method whose__self__.event_handlers[name].fn isthe function; and the stand-in a background handler binds to, now marked_reflex_bound_background_task; - a callable annotated to return an
EventSpec(everyrx.*event creator incl.rx.toast; reflex does not otherwise mark creators — an unannotated helper is treated as a result); - a list/tuple holding either.
These go through the existing invalid-yield path: the delta is flushed, then the 0.10.1TypeErrornaming the handler is raised (backend exception handler / error toast), for clicked, awaited and background handlers alike. The check only runs after_is_event_likefailed and starts withcallable(), so handlers returningNoneor events never reach it.
- a handler function bound to its state or to a background task's state proxy (
reflex/app.pyEventNamespace.on_event: in dev mode only, when a handler triggered from the frontend returns a non-Nonevalue (other than[]), warn once per handler that the value is dropped and only awaiting backend code receives it.on_eventis the one place that owns the discarded future, so the check is exact; the dev flag is read once, so prod pays one attribute check per event.- Docs: one rules bullet and one alert sentence in
docs/events/chaining_events.md.
Interaction with #7761
This PR is expected to merge before #7761 ("Stream event handler yield and return values to the frontend caller"). When rebasing #7761 onto it:
_is_resultcan becomenot _is_invalid_result(value)after itsNone/event checks (replacingcallable(value) or iscoroutine(value) or _holds_events_or_coroutines(value), which would reject legitimate callable results).- The
on_eventdev warning must be skipped for events that carry a requestid(their value is streamed to the frontend), and the docs/news wording "is dropped" should be narrowed accordingly.
Test plan
tests/units/reflex_base/event/processor/test_base_state_processor.py:test_returned_mistaken_event_raises_type_error(14 cases: sync / async / sync-generator / background handlers returningself.target;self.bg_target; uncalledrx.redirect,rx.toast,rx.toast.success;[self.target];(rx.call_script,); sync and async yields ofself.targetandrx.redirect; awaiting inline a handler that returnsself.target) — each also asserts the target never ran.test_returned_callable_that_is_not_an_event_resolves_future(4 cases: private helper,len,functools.partial, aMock).test_returned_event_reference_still_chains(3 cases).
tests/units/test_app.py:test_on_event_warns_once_in_dev_when_a_frontend_result_is_dropped(6 cases),test_on_event_does_not_watch_frontend_results_in_prod;test_on_event_uses_frontend_traceparent's fakeenqueuenow returns a future.- Failing before on unfixed
main: 11 of the 14 mistaken-value cases (Failed: DID NOT RAISE TypeErrorfor returns_bound_handler, async_returns_bound_handler, generator_returns_bound_handler, returns_bound_background_handler, background_returns_bound_handler, returns_uncalled_redirect, returns_uncalled_toast, returns_uncalled_toast_success, returns_list_holding_bound_handler, returns_tuple_holding_uncalled_call_script, awaits_bound_handler_return) and all 6 warning cases; the 3 yield cases and the result/chaining cases pass before and after as guards.test_handler_return_value_resolves_futureis untouched and passes. - After: the two touched modules 633 passed; full unit suite 15663 passed, 66 skipped (the Playwright browser-driver module could not run locally for lack of a Chromium binary).
ruff check,ruff format,pyright reflex tests, pre-commit on changed files: clean.
Type of change
- Bug fix (non-breaking change which fixes an issue)
- This change requires a documentation update (included)
Known limits
- Event-creator detection relies on the
-> EventSpecreturn annotation; an unannotated creator or one returningEventSpec | Noneis treated as a result (and gets the dev warning when clicked). - An inherited handler accessed through a background
StateProxycomes back as afunctools.partialand is not recognised. - The dev warning covers events from the frontend only, not chained or background-enqueued ones.
- The error keeps the exact 0.10.1 text (reads "Returned events of types <class 'function'>" for an uncalled
rx.redirect).
News fragments: news/+mistaken-handler-return-values.bugfix.md and packages/reflex-base/news/+mistaken-handler-return-values.bugfix.md.
Generated by Claude Code