From efb97c8e1a9db5715fddd2587069d653ef37021f Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Wed, 18 Jun 2025 11:11:45 -0700 Subject: [PATCH 1/5] always send on load even on going on the link --- reflex/.templates/web/utils/state.js | 31 ++++++------------------ tests/integration/test_dynamic_routes.py | 6 +++++ 2 files changed, 14 insertions(+), 23 deletions(-) diff --git a/reflex/.templates/web/utils/state.js b/reflex/.templates/web/utils/state.js index 9e3b75760b2..3413c346c15 100644 --- a/reflex/.templates/web/utils/state.js +++ b/reflex/.templates/web/utils/state.js @@ -1022,30 +1022,15 @@ export const useEventLoop = ( // Route after the initial page hydration useEffect(() => { - // This will run when the location changes - if ( - location.pathname + location.search + location.hash !== - prevLocationRef.current.pathname + - prevLocationRef.current.search + - prevLocationRef.current.hash - ) { - // Equivalent to routeChangeStart - runs when navigation begins - const change_start = () => { - const main_state_dispatch = dispatch["reflex___state____state"]; - if (main_state_dispatch !== undefined) { - main_state_dispatch({ is_hydrated_rx_state_: false }); - } - }; - change_start(); - - // Equivalent to routeChangeComplete - runs after navigation completes - const change_complete = () => addEvents(onLoadInternalEvent()); - change_complete(); - - // Update the ref - prevLocationRef.current = location; + // Equivalent to routeChangeStart - runs when navigation begins + const main_state_dispatch = dispatch["reflex___state____state"]; + if (main_state_dispatch !== undefined) { + main_state_dispatch({ is_hydrated_rx_state_: false }); } - }, [location, dispatch, onLoadInternalEvent, addEvents]); + + // Equivalent to routeChangeComplete - runs after navigation completes + addEvents(onLoadInternalEvent()); + }, [location]); return [addEvents, connectErrors]; }; diff --git a/tests/integration/test_dynamic_routes.py b/tests/integration/test_dynamic_routes.py index a66983f8f86..920c118dd5d 100644 --- a/tests/integration/test_dynamic_routes.py +++ b/tests/integration/test_dynamic_routes.py @@ -386,6 +386,12 @@ async def test_on_load_navigate_non_dynamic( assert urlsplit(driver.current_url).path.removesuffix("/") == "/static/x" await poll_for_order(["/static/x-no page id", "/static/x-no page id"]) + for _ in range(3): + link = driver.find_element(By.ID, "link_page_x") + link.click() + assert urlsplit(driver.current_url).path.removesuffix("/") == "/static/x" + await poll_for_order(["/static/x-no page id"] * 5) + @pytest.mark.asyncio async def test_render_dynamic_arg( From 756a6e00d46a9f15835cd3e5fcef3433508f6d66 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Wed, 18 Jun 2025 13:32:47 -0700 Subject: [PATCH 2/5] restore deps --- reflex/.templates/web/utils/state.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reflex/.templates/web/utils/state.js b/reflex/.templates/web/utils/state.js index 3413c346c15..ecbcc96e136 100644 --- a/reflex/.templates/web/utils/state.js +++ b/reflex/.templates/web/utils/state.js @@ -1030,7 +1030,7 @@ export const useEventLoop = ( // Equivalent to routeChangeComplete - runs after navigation completes addEvents(onLoadInternalEvent()); - }, [location]); + }, [location, dispatch, onLoadInternalEvent, addEvents]); return [addEvents, connectErrors]; }; From 635dea06e9e352a509d66b53371e082e89f125ac Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Fri, 20 Jun 2025 13:50:58 -0700 Subject: [PATCH 3/5] make it closer to what nextjs was doing --- reflex/.templates/web/utils/state.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/reflex/.templates/web/utils/state.js b/reflex/.templates/web/utils/state.js index ecbcc96e136..26a2f2954cf 100644 --- a/reflex/.templates/web/utils/state.js +++ b/reflex/.templates/web/utils/state.js @@ -1022,6 +1022,17 @@ export const useEventLoop = ( // Route after the initial page hydration useEffect(() => { + // This will run when the location changes + if ( + location.pathname + location.search === + prevLocationRef.current.pathname + prevLocationRef.current.search + ) { + if (location.hash) { + // If the hash is the same, we don't need to do anything. + return; + } + } + // Equivalent to routeChangeStart - runs when navigation begins const main_state_dispatch = dispatch["reflex___state____state"]; if (main_state_dispatch !== undefined) { @@ -1030,6 +1041,9 @@ export const useEventLoop = ( // Equivalent to routeChangeComplete - runs after navigation completes addEvents(onLoadInternalEvent()); + + // Update the ref + prevLocationRef.current = location; }, [location, dispatch, onLoadInternalEvent, addEvents]); return [addEvents, connectErrors]; From e459d936b56ca4a6284d2f81713d56e516545ed4 Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Tue, 24 Jun 2025 19:37:58 -0700 Subject: [PATCH 4/5] avoid extra route change on_load * memoize addEvents, which was being overwritten quite a bit * avoid sending the onLoadInternal when the app initially loads (as it will send hydrate itself) --- reflex/.templates/web/utils/state.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/reflex/.templates/web/utils/state.js b/reflex/.templates/web/utils/state.js index 26a2f2954cf..91aafcc9edc 100644 --- a/reflex/.templates/web/utils/state.js +++ b/reflex/.templates/web/utils/state.js @@ -5,7 +5,7 @@ import JSON5 from "json5"; import env from "$/env.json"; import reflexEnvironment from "$/reflex.json"; import Cookies from "universal-cookie"; -import { useEffect, useRef, useState } from "react"; +import { useCallback, useEffect, useRef, useState } from "react"; import { useLocation, useNavigate, @@ -851,7 +851,7 @@ export const useEventLoop = ( }, [paramsR]); // Function to add new events to the event queue. - const addEvents = (events, args, event_actions) => { + const addEvents = useCallback((events, args, event_actions) => { const _events = events.filter((e) => e !== undefined && e !== null); if (!(args instanceof Array)) { @@ -894,7 +894,7 @@ export const useEventLoop = ( } else { queueEvents(_events, socket, false, navigate, () => params.current); } - }; + }, []); const sentHydrate = useRef(false); // Avoid double-hydrate due to React strict-mode useEffect(() => { @@ -1020,8 +1020,15 @@ export const useEventLoop = ( return () => window.removeEventListener("storage", handleStorage); }); + const handleNavigationEvents = useRef(false); // Route after the initial page hydration useEffect(() => { + // The first time this effect runs is initial load, so don't handle + // any navigation events. + if (!handleNavigationEvents.current) { + handleNavigationEvents.current = true; + return; + } // This will run when the location changes if ( location.pathname + location.search === From e5929163d58e7d0c206c8c02972c2f0d5b20d605 Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Tue, 24 Jun 2025 20:41:51 -0700 Subject: [PATCH 5/5] Avoid second on_load when 404 client side routing redirects to same location Since the backend handles determining which on_load goes with the route, it will pick the correct one the first time, whether that's a real dynamic route or the actual 404 page. --- reflex/.templates/web/utils/client_side_routing.js | 2 +- reflex/.templates/web/utils/state.js | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/reflex/.templates/web/utils/client_side_routing.js b/reflex/.templates/web/utils/client_side_routing.js index 2140c6a229e..eb3eaf76737 100644 --- a/reflex/.templates/web/utils/client_side_routing.js +++ b/reflex/.templates/web/utils/client_side_routing.js @@ -25,7 +25,7 @@ export const useClientSideRouting = () => { const search = window.location.search; // Use navigate instead of replace - navigate(path + search, { replace: true }) + navigate(path + search, { replace: true, state: { fromNotFound: true } }) .then(() => { // Check if we're still on a NotFound route // Note: This depends on how your routes are set up diff --git a/reflex/.templates/web/utils/state.js b/reflex/.templates/web/utils/state.js index 91aafcc9edc..bd6d3766a10 100644 --- a/reflex/.templates/web/utils/state.js +++ b/reflex/.templates/web/utils/state.js @@ -1029,6 +1029,11 @@ export const useEventLoop = ( handleNavigationEvents.current = true; return; } + if (location.state?.fromNotFound) { + // If the redirect is from a 404 page, we skip onLoadInternalEvent, + // since it was already run when the 404 page was first rendered. + return; + } // This will run when the location changes if ( location.pathname + location.search ===