Repository navigation
feat: let the app handle a state schema mismatch - #7653
benedikt-bartscher wants to merge 2 commits into
Conversation
Before a production page reloads or shows the "new version available" banner for a state schema mismatch, it now dispatches a cancelable `reflex:state-mismatch` event on `window`, with the frontend and backend state schema hashes in `event.detail`. A listener that calls preventDefault() skips both, so the app can defer the reload to a safe moment or show its own notice instead. Without a listener nothing changes, and dev mode still only warns without dispatching the event. Closes reflex-dev#7563 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
[Medium impact] The PR appears safe to merge; no actionable issues were found. SummaryLets production apps cancel the default reload or banner when frontend and backend state schemas differ.
Reviews (2) · Last reviewed commit: "test: drop the extra state's event handl..." · Reviewed by Greptile |
Merging this PR will not alter performance1 warning
Comparing |
There was a problem hiding this comment.
1 issue found across 5 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="docs/utility_methods/exception_handlers.md">
<violation number="1" location="docs/utility_methods/exception_handlers.md:32">
P3: This example cannot run as shown because `rx` is undefined. Add `import reflex as rx` before constructing the app.</violation>
</file>
Reply with feedback, questions, or to request a fix.
View guided diff | Re-trigger cubic
| An immediate reload can cost the user input not yet sent or an interaction in progress, for example when a rolling deploy reconnects every open tab to a new backend. Before it reloads or shows the banner, a production page dispatches a cancelable `reflex:state-mismatch` event on `window`, with the state schema hashes of the frontend and the backend in `event.detail.frontend` and `event.detail.backend`. A listener that calls `event.preventDefault()` skips both, and the app decides what to show and when to reload: | ||
|
|
||
| ```python | ||
| app = rx.App( |
There was a problem hiding this comment.
P3: This example cannot run as shown because rx is undefined. Add import reflex as rx before constructing the app.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At docs/utility_methods/exception_handlers.md, line 32:
<comment>This example cannot run as shown because `rx` is undefined. Add `import reflex as rx` before constructing the app.</comment>
<file context>
@@ -26,6 +26,25 @@ On a mismatch:
+An immediate reload can cost the user input not yet sent or an interaction in progress, for example when a rolling deploy reconnects every open tab to a new backend. Before it reloads or shows the banner, a production page dispatches a cancelable `reflex:state-mismatch` event on `window`, with the state schema hashes of the frontend and the backend in `event.detail.frontend` and `event.detail.backend`. A listener that calls `event.preventDefault()` skips both, and the app decides what to show and when to reload:
+
+```python
+app = rx.App(
+ head_components=[
+ rx.script(
</file context>
| app = rx.App( | |
| import reflex as rx | |
| app = rx.App( |
There was a problem hiding this comment.
Leaving this as is. A plain ```python block is display-only (only exec/demo/eval blocks run), and short rx.App(...) config snippets in the docs routinely start without the import, e.g. docs/styling/theming.md, docs/styling/custom-stylesheets.md and docs/styling/tailwind.md (21 such snippets). The snippet only shows where the listener goes.
The mismatch_reports fixture removed ClientErrorExtraState from the state registries but left its generated handlers (setvar) in RegistrationContext.event_handlers. Each definition gets a new dynamic name, so the entries piled up for the rest of the harness's context. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Since #7553, a production page that connects to a backend with another state schema reloads at once, or shows the "new version available" banner when it already reloaded inside
REFLEX_AUTO_RELOAD_COOLDOWN. On a rolling deploy every open tab reconnects to a new pod within seconds, so a deploy that adds a var or renames an event handler reloads all of them in the middle of whatever the user is doing: a drag, a drawing or input not yet sent is lost. The app knows better when a reload is harmless, but it has no way to make that call; the only lever is the cooldown, which the reload on frontend errors shares.Before it reloads or shows the banner,
handleStateMismatchnow dispatches a cancelablereflex:state-mismatchevent onwindow, with the state schema hashes of both sides:A listener that calls
preventDefault()skips both the reload and the banner, so the app can show its own notice, reload after an autosave or when the user pauses, or do nothing. This is the second option from the issue: it is frontend-only, adds no config, and covers the"reload","banner"and"none"policies of the first option as well as anything in between. Detection, the hash, the cooldown and the banner are unchanged. Without a listener, or with one that does not cancel the event, a page behaves exactly as before.In dev the event is not dispatched. That path only warns, because a backend hot reload can reconnect before Vite serves the rebuilt bundle; a listener that reloads would turn that short-lived mismatch into reloads during development.
The event comes with every mismatched connect and reconnect, also after a reload the app started itself. The cooldown only guards the reload Reflex does on its own, which
docs/utility_methods/exception_handlers.mdnow points out next to the example.Tests:
tests/units/reflex_base/client_state_updates.mjsruns the realstate.js: the event is dispatched before the reload and carries both hashes; canceling it skips the reload and the banner (with and without a recent reload), leaves the cooldown untouched and lets events through; a dev page does not dispatch it. The two prod tests fail without the change.tests/integration/tests_playwright/test_client_error.pygains a/handledpage whoserx.scriptcancels the event. In prod the page stays on its first navigation, shows no banner and still handles events; in dev it only warns. The extra backend state the mismatch tests add moved into a fixture both tests share. The prod case fails without the change.uv run pytest tests/unitsanduv run pytest tests/integration/tests_playwright/test_client_error.py(dev and prod) pass, and the pre-commit hooks are clean. I also checked the documented setup, anrx.scriptinhead_components, against a dev and a prod build.Closes #7563
🤖 Generated with Claude Code