Skip to content

Commit 7884e22

Browse files
adhami3310masenf
andcommitted
always send on load even on going on the link (#5469)
* always send on load even on going on the link * restore deps * make it closer to what nextjs was doing * 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) * 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. --------- Co-authored-by: Masen Furer <m_github@0x26.net>
1 parent 84071a9 commit 7884e22

3 files changed

Lines changed: 39 additions & 22 deletions

File tree

reflex/.templates/web/utils/client_side_routing.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export const useClientSideRouting = () => {
2525
const search = window.location.search;
2626

2727
// Use navigate instead of replace
28-
navigate(path + search, { replace: true })
28+
navigate(path + search, { replace: true, state: { fromNotFound: true } })
2929
.then(() => {
3030
// Check if we're still on a NotFound route
3131
// Note: This depends on how your routes are set up

reflex/.templates/web/utils/state.js

Lines changed: 32 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import JSON5 from "json5";
55
import env from "$/env.json";
66
import reflexEnvironment from "$/reflex.json";
77
import Cookies from "universal-cookie";
8-
import { useEffect, useRef, useState } from "react";
8+
import { useCallback, useEffect, useRef, useState } from "react";
99
import {
1010
useLocation,
1111
useNavigate,
@@ -851,7 +851,7 @@ export const useEventLoop = (
851851
}, [paramsR]);
852852

853853
// Function to add new events to the event queue.
854-
const addEvents = (events, args, event_actions) => {
854+
const addEvents = useCallback((events, args, event_actions) => {
855855
const _events = events.filter((e) => e !== undefined && e !== null);
856856

857857
if (!(args instanceof Array)) {
@@ -894,7 +894,7 @@ export const useEventLoop = (
894894
} else {
895895
queueEvents(_events, socket, false, navigate, () => params.current);
896896
}
897-
};
897+
}, []);
898898

899899
const sentHydrate = useRef(false); // Avoid double-hydrate due to React strict-mode
900900
useEffect(() => {
@@ -1020,31 +1020,42 @@ export const useEventLoop = (
10201020
return () => window.removeEventListener("storage", handleStorage);
10211021
});
10221022

1023+
const handleNavigationEvents = useRef(false);
10231024
// Route after the initial page hydration
10241025
useEffect(() => {
1026+
// The first time this effect runs is initial load, so don't handle
1027+
// any navigation events.
1028+
if (!handleNavigationEvents.current) {
1029+
handleNavigationEvents.current = true;
1030+
return;
1031+
}
1032+
if (location.state?.fromNotFound) {
1033+
// If the redirect is from a 404 page, we skip onLoadInternalEvent,
1034+
// since it was already run when the 404 page was first rendered.
1035+
return;
1036+
}
10251037
// This will run when the location changes
10261038
if (
1027-
location.pathname + location.search + location.hash !==
1028-
prevLocationRef.current.pathname +
1029-
prevLocationRef.current.search +
1030-
prevLocationRef.current.hash
1039+
location.pathname + location.search ===
1040+
prevLocationRef.current.pathname + prevLocationRef.current.search
10311041
) {
1032-
// Equivalent to routeChangeStart - runs when navigation begins
1033-
const change_start = () => {
1034-
const main_state_dispatch = dispatch["reflex___state____state"];
1035-
if (main_state_dispatch !== undefined) {
1036-
main_state_dispatch({ is_hydrated_rx_state_: false });
1037-
}
1038-
};
1039-
change_start();
1040-
1041-
// Equivalent to routeChangeComplete - runs after navigation completes
1042-
const change_complete = () => addEvents(onLoadInternalEvent());
1043-
change_complete();
1042+
if (location.hash) {
1043+
// If the hash is the same, we don't need to do anything.
1044+
return;
1045+
}
1046+
}
10441047

1045-
// Update the ref
1046-
prevLocationRef.current = location;
1048+
// Equivalent to routeChangeStart - runs when navigation begins
1049+
const main_state_dispatch = dispatch["reflex___state____state"];
1050+
if (main_state_dispatch !== undefined) {
1051+
main_state_dispatch({ is_hydrated_rx_state_: false });
10471052
}
1053+
1054+
// Equivalent to routeChangeComplete - runs after navigation completes
1055+
addEvents(onLoadInternalEvent());
1056+
1057+
// Update the ref
1058+
prevLocationRef.current = location;
10481059
}, [location, dispatch, onLoadInternalEvent, addEvents]);
10491060

10501061
return [addEvents, connectErrors];

tests/integration/test_dynamic_routes.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,12 @@ async def test_on_load_navigate_non_dynamic(
386386
assert urlsplit(driver.current_url).path.removesuffix("/") == "/static/x"
387387
await poll_for_order(["/static/x-no page id", "/static/x-no page id"])
388388

389+
for _ in range(3):
390+
link = driver.find_element(By.ID, "link_page_x")
391+
link.click()
392+
assert urlsplit(driver.current_url).path.removesuffix("/") == "/static/x"
393+
await poll_for_order(["/static/x-no page id"] * 5)
394+
389395

390396
@pytest.mark.asyncio
391397
async def test_render_dynamic_arg(

0 commit comments

Comments
 (0)