Ver Fonte

fix dynamic route

Khaleel Al-Adhami há 5 dias atrás
pai
commit
e3d6bd3e2a

+ 24 - 10
reflex/.templates/web/utils/state.js

@@ -369,7 +369,7 @@ export const applyEvent = async (event, socket, navigate, params) => {
       pathname: window.location.pathname,
       pathname: window.location.pathname,
       query: {
       query: {
         ...Object.fromEntries(new URLSearchParams(window.location.search)),
         ...Object.fromEntries(new URLSearchParams(window.location.search)),
-        ...params,
+        ...params(),
       },
       },
       asPath: window.location.pathname + window.location.search,
       asPath: window.location.pathname + window.location.search,
     };
     };
@@ -498,7 +498,7 @@ export const connect = async (
   setConnectErrors,
   setConnectErrors,
   client_storage = {},
   client_storage = {},
   navigate,
   navigate,
-  params = {},
+  params,
 ) => {
 ) => {
   // Get backend URL object from the endpoint.
   // Get backend URL object from the endpoint.
   const endpoint = getBackendURL(EVENTURL);
   const endpoint = getBackendURL(EVENTURL);
@@ -827,10 +827,15 @@ export const useEventLoop = (
   const socket = useRef(null);
   const socket = useRef(null);
   const location = useLocation();
   const location = useLocation();
   const navigate = useNavigate();
   const navigate = useNavigate();
-  const params = useParams();
+  const paramsR = useParams();
   const prevLocationRef = useRef(location);
   const prevLocationRef = useRef(location);
   const [searchParams] = useSearchParams();
   const [searchParams] = useSearchParams();
   const [connectErrors, setConnectErrors] = useState([]);
   const [connectErrors, setConnectErrors] = useState([]);
+  const params = useRef(paramsR);
+
+  useEffect(() => {
+    params.current = paramsR;
+  }, [paramsR]);
 
 
   // Function to add new events to the event queue.
   // Function to add new events to the event queue.
   const addEvents = (events, args, event_actions) => {
   const addEvents = (events, args, event_actions) => {
@@ -869,11 +874,12 @@ export const useEventLoop = (
       // If debounce is used, queue the events after some delay
       // If debounce is used, queue the events after some delay
       debounce(
       debounce(
         combined_name,
         combined_name,
-        () => queueEvents(_events, socket, false, navigate, params),
+        () =>
+          queueEvents(_events, socket, false, navigate, () => params.current),
         event_actions.debounce,
         event_actions.debounce,
       );
       );
     } else {
     } else {
-      queueEvents(_events, socket, false, navigate, params);
+      queueEvents(_events, socket, false, navigate, () => params.current);
     }
     }
   };
   };
 
 
@@ -885,14 +891,17 @@ export const useEventLoop = (
           ...e,
           ...e,
           router_data: {
           router_data: {
             pathname: location.pathname,
             pathname: location.pathname,
-            query: { ...Object.fromEntries(searchParams.entries()), ...params },
+            query: {
+              ...Object.fromEntries(searchParams.entries()),
+              ...params.current,
+            },
             asPath: location.pathname + location.search,
             asPath: location.pathname + location.search,
           },
           },
         })),
         })),
         socket,
         socket,
         true,
         true,
         navigate,
         navigate,
-        params,
+        () => params.current,
       );
       );
       sentHydrate.current = true;
       sentHydrate.current = true;
     }
     }
@@ -940,7 +949,7 @@ export const useEventLoop = (
           setConnectErrors,
           setConnectErrors,
           client_storage,
           client_storage,
           navigate,
           navigate,
-          params,
+          () => params.current,
         );
         );
       }
       }
     }
     }
@@ -962,7 +971,7 @@ export const useEventLoop = (
     (async () => {
     (async () => {
       // Process all outstanding events.
       // Process all outstanding events.
       while (event_queue.length > 0 && !event_processing) {
       while (event_queue.length > 0 && !event_processing) {
-        await processEvent(socket.current, navigate, params);
+        await processEvent(socket.current, navigate, () => params.current);
       }
       }
     })();
     })();
   });
   });
@@ -1001,7 +1010,12 @@ export const useEventLoop = (
   // Route after the initial page hydration
   // Route after the initial page hydration
   useEffect(() => {
   useEffect(() => {
     // This will run when the location changes
     // This will run when the location changes
-    if (location !== prevLocationRef.current) {
+    if (
+      location.pathname + location.search + location.hash !==
+      prevLocationRef.current.pathname +
+        prevLocationRef.current.search +
+        prevLocationRef.current.hash
+    ) {
       // Equivalent to routeChangeStart - runs when navigation begins
       // Equivalent to routeChangeStart - runs when navigation begins
       const change_start = () => {
       const change_start = () => {
         const main_state_dispatch = dispatch["reflex___state____state"];
         const main_state_dispatch = dispatch["reflex___state____state"];

+ 18 - 2
reflex/app.py

@@ -888,10 +888,26 @@ class App(MiddlewareMixin, LifespanMixin):
         Returns:
         Returns:
             The load events for the route.
             The load events for the route.
         """
         """
-        route = route.lstrip("/")
+        route = route.lstrip("/").rstrip("/")
         if route == "":
         if route == "":
             route = constants.PageNames.INDEX_ROUTE
             route = constants.PageNames.INDEX_ROUTE
-        return self._load_events.get(route, [])
+        parts = route.split("/")
+        for page_route in self._pages:
+            page_path = page_route.lstrip("/").rstrip("/")
+            if page_path == route:
+                return self._load_events.get(page_route, [])
+            page_parts = page_path.split("/")
+            if len(page_parts) != len(parts):
+                continue
+            if all(
+                part == page_part
+                or (page_part.startswith("[") and page_part.endswith("]"))
+                for part, page_part in zip(parts, page_parts, strict=False)
+            ):
+                return self._load_events.get(page_route, [])
+
+        # Default to 404 page load events if no match found.
+        return self._load_events.get("404", [])
 
 
     def _check_routes_conflict(self, new_route: str):
     def _check_routes_conflict(self, new_route: str):
         """Verify if there is any conflict between the new route and any existing route.
         """Verify if there is any conflict between the new route and any existing route.

+ 16 - 21
tests/integration/test_dynamic_routes.py

@@ -9,7 +9,7 @@ from urllib.parse import urlsplit
 import pytest
 import pytest
 from selenium.webdriver.common.by import By
 from selenium.webdriver.common.by import By
 
 
-from reflex.testing import AppHarness, AppHarnessProd, WebDriver
+from reflex.testing import AppHarness, WebDriver
 
 
 from .utils import poll_for_navigation
 from .utils import poll_for_navigation
 
 
@@ -261,11 +261,10 @@ async def test_on_load_navigate(
     """
     """
     dynamic_state_full_name = dynamic_route.get_full_state_name(["_dynamic_state"])
     dynamic_state_full_name = dynamic_route.get_full_state_name(["_dynamic_state"])
     assert dynamic_route.app_instance is not None
     assert dynamic_route.app_instance is not None
-    is_prod = isinstance(dynamic_route, AppHarnessProd)
     link = driver.find_element(By.ID, "link_page_next")
     link = driver.find_element(By.ID, "link_page_next")
     assert link
     assert link
 
 
-    exp_order = [f"/page/[page_id]-{ix}" for ix in range(10)]
+    exp_order = [f"/page/{ix}-{ix}" for ix in range(10)]
     # click the link a few times
     # click the link a few times
     for ix in range(10):
     for ix in range(10):
         # wait for navigation, then assert on url
         # wait for navigation, then assert on url
@@ -288,25 +287,25 @@ async def test_on_load_navigate(
         assert dynamic_route.poll_for_value(raw_path_input) == f"/page/{ix}"
         assert dynamic_route.poll_for_value(raw_path_input) == f"/page/{ix}"
     await poll_for_order(exp_order)
     await poll_for_order(exp_order)
 
 
+    frontend_url = dynamic_route.frontend_url
+    assert frontend_url
+    frontend_url = frontend_url.removesuffix("/")
+
     # manually load the next page to trigger client side routing in prod mode
     # manually load the next page to trigger client side routing in prod mode
-    if is_prod:
-        exp_order += ["/404-no page id"]
-    exp_order += ["/page/[page_id]-10"]
+    exp_order += ["/page/10-10"]
     with poll_for_navigation(driver):
     with poll_for_navigation(driver):
-        driver.get(f"{dynamic_route.frontend_url}/page/10/")
+        driver.get(f"{frontend_url}/page/10")
     await poll_for_order(exp_order)
     await poll_for_order(exp_order)
 
 
     # make sure internal nav still hydrates after redirect
     # make sure internal nav still hydrates after redirect
-    exp_order += ["/page/[page_id]-11"]
+    exp_order += ["/page/11-11"]
     link = driver.find_element(By.ID, "link_page_next")
     link = driver.find_element(By.ID, "link_page_next")
     with poll_for_navigation(driver):
     with poll_for_navigation(driver):
         link.click()
         link.click()
     await poll_for_order(exp_order)
     await poll_for_order(exp_order)
 
 
     # load same page with a query param and make sure it passes through
     # load same page with a query param and make sure it passes through
-    if is_prod:
-        exp_order += ["/404-no page id"]
-    exp_order += ["/page/[page_id]-11"]
+    exp_order += ["/page/11-11"]
     with poll_for_navigation(driver):
     with poll_for_navigation(driver):
         driver.get(f"{driver.current_url}?foo=bar")
         driver.get(f"{driver.current_url}?foo=bar")
     await poll_for_order(exp_order)
     await poll_for_order(exp_order)
@@ -315,32 +314,28 @@ async def test_on_load_navigate(
     ).router.page.params["foo"] == "bar"
     ).router.page.params["foo"] == "bar"
 
 
     # hit a 404 and ensure we still hydrate
     # hit a 404 and ensure we still hydrate
-    exp_order += ["/404-no page id"]
+    exp_order += ["/missing-no page id"]
     with poll_for_navigation(driver):
     with poll_for_navigation(driver):
-        driver.get(f"{dynamic_route.frontend_url}/missing")
+        driver.get(f"{frontend_url}/missing")
     await poll_for_order(exp_order)
     await poll_for_order(exp_order)
 
 
     # browser nav should still trigger hydration
     # browser nav should still trigger hydration
-    if is_prod:
-        exp_order += ["/404-no page id"]
-    exp_order += ["/page/[page_id]-11"]
+    exp_order += ["/page/11-11"]
     with poll_for_navigation(driver):
     with poll_for_navigation(driver):
         driver.back()
         driver.back()
     await poll_for_order(exp_order)
     await poll_for_order(exp_order)
 
 
     # next/link to a 404 and ensure we still hydrate
     # next/link to a 404 and ensure we still hydrate
-    exp_order += ["/404-no page id"]
+    exp_order += ["/missing-no page id"]
     link = driver.find_element(By.ID, "link_missing")
     link = driver.find_element(By.ID, "link_missing")
     with poll_for_navigation(driver):
     with poll_for_navigation(driver):
         link.click()
         link.click()
     await poll_for_order(exp_order)
     await poll_for_order(exp_order)
 
 
     # hit a page that redirects back to dynamic page
     # hit a page that redirects back to dynamic page
-    if is_prod:
-        exp_order += ["/404-no page id"]
-    exp_order += ["on_load_redir-{'foo': 'bar', 'page_id': '0'}", "/page/[page_id]-0"]
+    exp_order += ["on_load_redir-{'foo': 'bar', 'page_id': '0'}", "/page/0-0"]
     with poll_for_navigation(driver):
     with poll_for_navigation(driver):
-        driver.get(f"{dynamic_route.frontend_url}/redirect-page/0/?foo=bar")
+        driver.get(f"{frontend_url}/redirect-page/0/?foo=bar")
     await poll_for_order(exp_order)
     await poll_for_order(exp_order)
     # should have redirected back to page 0
     # should have redirected back to page 0
     assert urlsplit(driver.current_url).path.removesuffix("/") == "/page/0"
     assert urlsplit(driver.current_url).path.removesuffix("/") == "/page/0"