Fix router.url.query going stale after history.replaceState#6625
Conversation
React Router's location (mirrored in locationRef) does not observe direct window.history.pushState/replaceState calls (e.g. via rx.call_script), so State.router.url.query went stale after the URL query was updated that way. Populate router_data's query string and hash from the live window.location outside embed mode, while keeping the basename-relative pathname from React Router so frontend_path is not applied twice. Embedded (mount target) apps continue to use the in-widget memory router. Fixes #6603 https://claude.ai/code/session_01JwZCEa2bkfK9QDp4PP84d8
Merging this PR will not alter performance
Comparing Footnotes
|
Greptile SummaryFixes stale
Confidence Score: 5/5Safe to merge — the JS change is minimal and surgical, touches only the two lines that build search/hash in router_data, and the embed-mode guard is well-tested against the existing locationRef contract. The change is two lines swapping React Router's mirrored location for window.location (with an embed-mode gate). The pre-existing fallback logic and the fact that pathname is intentionally left on the React Router ref both mean the change is well-contained and non-breaking. The new integration test exercises the exact code path in both dev and prod modes. No files require special attention. Important Files Changed
Reviews (2): Last reviewed commit: "test: codify router query sync and redir..." | Re-trigger Greptile |
The changed source lives in packages/reflex-base, so the towncrier fragment must live under packages/reflex-base/news/ and be named by PR number for the per-package changelog check. https://claude.ai/code/session_01JwZCEa2bkfK9QDp4PP84d8
Rework the router_query integration test per review feedback: - Use rx.var computed vars (router.url.query / query_parameters) rendered in the app, plus on_load and ping counters, to observe behavior directly. - Codify that a direct rx.call_script(history.replaceState) is NOT a navigation: it fires no event and no on_load, and the router only reflects the new URL on the next event sent to the backend. - Codify rx.redirect(target): client-side push navigation that fires on_load and updates the router reactively; back returns to the prior entry. - Codify rx.redirect(target, replace=True): same reactive behavior but replaces the current history entry (back skips it). Also clarify the news fragment: history mutation is intentionally not reactive; rx.redirect(replace=True) is the reactive path. https://claude.ai/code/session_01JwZCEa2bkfK9QDp4PP84d8
Type of change
Description
Fixes #6603:
State.router.url.queryand otherrouter_datafields were going stale when the URL was modified viawindow.history.replaceStateorpushState(e.g., throughrx.call_script).Root cause: React Router's
useLocationhook does not observe direct history manipulation. The frontend was reading the mirrored location from React Router instead of the livewindow.location, causing the backend to receive stale query parameters on subsequent events.Solution: When populating
router_datainapplyEvent, read the livewindow.location.searchandwindow.location.hashinstead of relying on React Router's location object. The pathname remains basename-relative (from React Router) to avoid double-applying the frontend path prefix. In embed mode, the mirrored location is used since the host page'swindow.locationis unrelated to the in-widget memory router.Changes
packages/reflex-base/src/reflex_base/.templates/web/utils/state.js: ModifiedapplyEventto read live query string and hash fromwindow.locationwhenrouter_datais empty, ensuring synchronization with direct history manipulation.tests/integration/tests_playwright/test_router_query.py: Added comprehensive integration test covering both dev and prod modes. The test verifies that:history.replaceStateare correctly reflected inState.router.url.queryreplaceStatecalls keep the router in syncquery_parametersdict is properly populatednews/6603.bugfix.md: Added changelog entry.Test Plan
The new integration test (
test_replace_state_syncs_router_query) covers the fix in both dev and prod modes viaapp_harness_envparametrization. It verifies that:history.replaceState("?name=test"), reading the query returns"name=test"replaceState("?name=other&page=2"), the query is updated correctlyAll existing tests continue to pass.
https://claude.ai/code/session_01JwZCEa2bkfK9QDp4PP84d8