6363 DECORATED_PAGES ,
6464)
6565from reflex .route import (
66- catchall_in_route ,
67- catchall_prefix ,
6866 get_route_args ,
67+ replace_brackets_with_keywords ,
6968 verify_route_validity ,
7069)
7170from reflex .state import (
@@ -456,6 +455,9 @@ def add_page(
456455 on_load: The event handler(s) that will be called each time the page load.
457456 meta: The metadata of the page.
458457 script_tags: List of script tags to be added to component
458+
459+ Raises:
460+ ValueError: When the specified route name already exists.
459461 """
460462 # If the route is not set, get it from the callable.
461463 if route is None :
@@ -470,6 +472,23 @@ def add_page(
470472 # Check if the route given is valid
471473 verify_route_validity (route )
472474
475+ if route in self .pages and os .getenv (constants .RELOAD_CONFIG ):
476+ # when the app is reloaded(typically for app harness tests), we should maintain
477+ # the latest render function of a route.This applies typically to decorated pages
478+ # since they are only added when app._compile is called.
479+ self .pages .pop (route )
480+
481+ if route in self .pages :
482+ route_name = (
483+ f"`{ route } ` or `/`"
484+ if route == constants .PageNames .INDEX_ROUTE
485+ else f"`{ route } `"
486+ )
487+ raise ValueError (
488+ f"Duplicate page route { route_name } already exists. Make sure you do not have two"
489+ f" pages with the same route"
490+ )
491+
473492 # Setup dynamic args for the route.
474493 # this state assignment is only required for tests using the deprecated state kwarg for App
475494 state = self .state if self .state else State
@@ -561,27 +580,31 @@ def _check_routes_conflict(self, new_route: str):
561580 Args:
562581 new_route: the route being newly added.
563582 """
564- newroute_catchall = catchall_in_route (new_route )
565- if not newroute_catchall :
583+ if "[" not in new_route :
566584 return
567585
586+ segments = (
587+ constants .RouteRegex .SINGLE_SEGMENT ,
588+ constants .RouteRegex .DOUBLE_SEGMENT ,
589+ constants .RouteRegex .SINGLE_CATCHALL_SEGMENT ,
590+ constants .RouteRegex .DOUBLE_CATCHALL_SEGMENT ,
591+ )
568592 for route in self .pages :
569- route = "" if route == "index" else route
570-
571- if new_route .startswith (f"{ route } /[[..." ):
572- raise ValueError (
573- f"You cannot define a route with the same specificity as a optional catch-all route ('{ route } ' and '{ new_route } ')"
574- )
575-
576- route_catchall = catchall_in_route (route )
577- if (
578- route_catchall
579- and newroute_catchall
580- and catchall_prefix (route ) == catchall_prefix (new_route )
593+ replaced_route = replace_brackets_with_keywords (route )
594+ for rw , r , nr in zip (
595+ replaced_route .split ("/" ), route .split ("/" ), new_route .split ("/" )
581596 ):
582- raise ValueError (
583- f"You cannot use multiple catchall for the same dynamic route ({ route } !== { new_route } )"
584- )
597+ if rw in segments and r != nr :
598+ # If the slugs in the segments of both routes are not the same, then the route is invalid
599+ raise ValueError (
600+ f"You cannot use different slug names for the same dynamic path in { route } and { new_route } ('{ r } ' != '{ nr } ')"
601+ )
602+ elif rw not in segments and r != nr :
603+ # if the section being compared in both routes is not a dynamic segment(i.e not wrapped in brackets)
604+ # then we are guaranteed that the route is valid and there's no need checking the rest.
605+ # eg. /posts/[id]/info/[slug1] and /posts/[id]/info1/[slug1] is always going to be valid since
606+ # info1 will break away into its own tree.
607+ break
585608
586609 def add_custom_404_page (
587610 self ,
0 commit comments