Repository navigation
Fix uncalled rx.toast hint and dropped-result warning lookup - #7796
Conversation
…result is dropped Since handlers may return a value to the backend code awaiting them (#7673), any non-event return value became the handler's result. That silently swallowed the classic mistakes 0.10.1 reported with "must only return/yield": returning `self.other_handler` (meant `type(self).other_handler`), an uncalled event creator such as `rx.redirect`, or a list holding one. The chained handler never ran and nothing was logged. Those values can never be a meaningful result, so they raise the 0.10.1 TypeError again, naming the user's handler: - a handler function bound to its state (or a background task's state proxy), and the stand-in a background handler binds to, now marked so it can be recognized; - a callable annotated to return an EventSpec (every rx.* event creator, rx.toast included), since reflex does not otherwise mark event creators; - a sequence holding either. Any other value stays the result. In dev mode, on_event warns once per handler when a handler the frontend triggered returns a value, which nothing receives; the frontend discards that future, so the check is exact and costs nothing in prod. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dxjadq3qkhKnqZR2b9WBFi
There was a problem hiding this comment.
All reported issues were addressed across 8 files
Reply with feedback, questions, or to request a fix.
View guided diff | Turn on auto-fix | Re-trigger cubic
[Low impact] The PR appears safe to merge; no blocking issues remain. SummaryThe PR fixes two diagnostic messages:
The latest changes strengthen tests by checking the full warning text. No new issues were found. Previous threads were also checked: All supplied previous threads were unnumbered, so none require a Reviews (6) · Last reviewed commit: "Assert the full dropped-result warning i..." · Reviewed by Greptile |
Merging this PR will not alter performance1 warning
Comparing |
When a handler returns or yields an event creator without calling it, the 0.10.1 "must only return/yield" message now ends with "Did you mean to call it: rx.redirect(...)?", naming the creator as `rx.<name>` when reflex exports it under that name, else by its qualified name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dxjadq3qkhKnqZR2b9WBFi
- on_event benchmark: the mocked enqueue returned a MagicMock, so the dev-mode done callback on the event's future ran as an AsyncMock call per event (call recording plus a never-awaited coroutine), doubling the measured time. enqueue now returns a resolved future, as the real EventFuture is; the callback's real cost is a few microseconds per event, in dev only. - The dropped-result warning goes through the module logger with a once-per-handler set; console.warn is deprecated and added its own notice. - The "call it" hint names a creator from its own function, without scanning module globals; rx.toast now reads Toaster.send_toast(...). - The dropped-result test runs in a clean registration context. - News fragment and docs limit the dropped-value wording to non-event values. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dxjadq3qkhKnqZR2b9WBFi
|
Re the CodSpeed report (#7796 (comment)), Cause. The slowdown came from the benchmark's mock, not from production code. In dev mode, Fix (9679a8f). The benchmark's
With a real future, the remaining cost is a few µs per event, in dev only. Prod adds a single attribute check. Because this PR changes the benchmark, CodSpeed will compare the new harness against main's old one, so the next report may show this benchmark as faster than main. Generated by Claude Code |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dxjadq3qkhKnqZR2b9WBFi
|
Correction to my comment above. The report for 9679a8f does not show Generated by Claude Code |
|
This PR's changes are already on Generated by Claude Code |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dxjadq3qkhKnqZR2b9WBFi
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dxjadq3qkhKnqZR2b9WBFi
The "Did you mean to call it" hint named the internal Toaster.send_toast and Toaster.toast_* methods for an uncalled rx.toast or rx.toast.<type>. Name a creator only when reflex exports it as rx.<name>; otherwise ask "Did you mean to call it?" without a name. The dev-mode dropped-result warning indexed the app's handler registry directly. Use .get() and fall back to the event name.
There was a problem hiding this comment.
All reported issues were addressed across 6 files (changes from recent commits).
Reply with feedback, questions, or to request a fix.
View guided diff | Turn on auto-fix | Re-trigger cubic
|
Update to my earlier comment that this PR could be closed because #7821's squash carried it. FarhanAliRaza's 2540402, on top of a merge of
Merged into Generated by Claude Code |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dxjadq3qkhKnqZR2b9WBFi
2540402 dropped the name for every creator reflex does not export as rx.<name>, which also hid an app's own helper annotated to return an EventSpec. Only a creator defined by reflex has an internal name; an app's creator is named by its qualified name again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dxjadq3qkhKnqZR2b9WBFi
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dxjadq3qkhKnqZR2b9WBFi
There was a problem hiding this comment.
All reported issues were addressed across 2 files (changes from recent commits).
Reply with feedback, questions, or to request a fix.
View guided diff | Turn on auto-fix | Re-trigger cubic
|
CodSpeed on 2540402 flags Update for 439858e: only Generated by Claude Code |
Compare the whole warning message instead of substrings, through one helper that builds the expected text.
* Raise again for mistaken handler return values; warn when a frontend result is dropped Since handlers may return a value to the backend code awaiting them (#7673), any non-event return value became the handler's result. That silently swallowed the classic mistakes 0.10.1 reported with "must only return/yield": returning `self.other_handler` (meant `type(self).other_handler`), an uncalled event creator such as `rx.redirect`, or a list holding one. The chained handler never ran and nothing was logged. Those values can never be a meaningful result, so they raise the 0.10.1 TypeError again, naming the user's handler: - a handler function bound to its state (or a background task's state proxy), and the stand-in a background handler binds to, now marked so it can be recognized; - a callable annotated to return an EventSpec (every rx.* event creator, rx.toast included), since reflex does not otherwise mark event creators; - a sequence holding either. Any other value stays the result. In dev mode, on_event warns once per handler when a handler the frontend triggered returns a value, which nothing receives; the frontend discards that future, so the check is exact and costs nothing in prod. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dxjadq3qkhKnqZR2b9WBFi * Hint to call an uncalled event creator in the invalid return/yield error When a handler returns or yields an event creator without calling it, the 0.10.1 "must only return/yield" message now ends with "Did you mean to call it: rx.redirect(...)?", naming the creator as `rx.<name>` when reflex exports it under that name, else by its qualified name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dxjadq3qkhKnqZR2b9WBFi * Address review feedback; give the on_event benchmark a real future - on_event benchmark: the mocked enqueue returned a MagicMock, so the dev-mode done callback on the event's future ran as an AsyncMock call per event (call recording plus a never-awaited coroutine), doubling the measured time. enqueue now returns a resolved future, as the real EventFuture is; the callback's real cost is a few microseconds per event, in dev only. - The dropped-result warning goes through the module logger with a once-per-handler set; console.warn is deprecated and added its own notice. - The "call it" hint names a creator from its own function, without scanning module globals; rx.toast now reads Toaster.send_toast(...). - The dropped-result test runs in a clean registration context. - News fragment and docs limit the dropped-value wording to non-event values. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dxjadq3qkhKnqZR2b9WBFi * Fix uncalled rx.toast hint and dropped-result warning lookup The "Did you mean to call it" hint named the internal Toaster.send_toast and Toaster.toast_* methods for an uncalled rx.toast or rx.toast.<type>. Name a creator only when reflex exports it as rx.<name>; otherwise ask "Did you mean to call it?" without a name. The dev-mode dropped-result warning indexed the app's handler registry directly. Use .get() and fall back to the event name. * Keep the name of an app's own event creator in the call hint 2540402 dropped the name for every creator reflex does not export as rx.<name>, which also hid an app's own helper annotated to return an EventSpec. Only a creator defined by reflex has an internal name; an app's creator is named by its qualified name again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dxjadq3qkhKnqZR2b9WBFi * Assert the full dropped-result warning in tests Compare the whole warning message instead of substrings, through one helper that builds the expected text. --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Farhan <www.mfarvirus@gmail.com> (cherry picked from commit 51d8f50)
…last open Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dxjadq3qkhKnqZR2b9WBFi
Status
The original change in this PR landed on
mainthrough the squash merge of the stacked #7821 (554eb10). The branch now mergesmain, so the diff holds only two follow-up fixes to that code: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(...)._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(...)?".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.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/andverify_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 ...: returningself.other_handler(meanttype(self).other_handler), an uncalled event creator such asrx.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, plusself.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;EventSpec(everyrx.*event creator incl.rx.toast; reflex does not otherwise mark creators — an unannotated helper is treated as a result);These go through the existing invalid-yield path: the delta is flushed, then the 0.10.1
TypeErrornaming 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.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/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).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.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.ruff check,ruff format,pyright reflex tests, pre-commit on changed files: clean.Type of change
Known limits
-> EventSpecreturn annotation; an unannotated creator or one returningEventSpec | Noneis treated as a result (and gets the dev warning when clicked).StateProxycomes back as afunctools.partialand is not recognised.rx.redirect).News fragments:
news/+mistaken-handler-return-values.bugfix.mdandpackages/reflex-base/news/+mistaken-handler-return-values.bugfix.md.Generated by Claude Code