44
55from dataclasses import replace
66from logging import getLogger
7- from typing import TYPE_CHECKING , Any , Literal , cast
7+ from typing import TYPE_CHECKING , Any , cast
88
99from reactpy import component , use_memo , use_state
1010from reactpy .backend .types import Connection , Location
1414from reactpy_router .components import History
1515from reactpy_router .hooks import RouteState , _route_state_context
1616from reactpy_router .resolvers import StarletteResolver
17+ from reactpy_router .types import MatchedRoute
1718
1819if TYPE_CHECKING :
1920 from collections .abc import Iterator , Sequence
@@ -57,36 +58,27 @@ def router(
5758 * routes : Route ,
5859 resolver : Resolver [Route ],
5960) -> VdomDict | None :
60- """A component that renders matching route(s) using the given resolver.
61+ """A component that renders matching route using the given resolver.
6162
62- This typically should never be used by a user . Instead, use `create_router` if creating
63+ User notice: This component typically should never be used. Instead, use `create_router` if creating
6364 a custom routing engine."""
6465
65- old_conn = use_connection ()
66- location , set_location = use_state (old_conn .location )
67- first_load , set_first_load = use_state (True )
68-
66+ old_connection = use_connection ()
67+ location , set_location = use_state (cast (Location | None , None ))
6968 resolvers = use_memo (
7069 lambda : tuple (map (resolver , _iter_routes (routes ))),
7170 dependencies = (resolver , hash (routes )),
7271 )
73-
74- match = use_memo (lambda : _match_route (resolvers , location , select = "first" ))
72+ route_element = None
73+ match = use_memo (lambda : _match_route (resolvers , location or old_connection . location ))
7574
7675 if match :
77- if first_load :
78- # We need skip rendering the application on 'first_load' to avoid
79- # rendering it twice. The second render follows the on_history_change event
80- route_elements = []
81- set_first_load (False )
82- else :
83- route_elements = [
84- _route_state_context (
85- element ,
86- value = RouteState (set_location , params ),
87- )
88- for element , params in match
89- ]
76+ # Skip rendering until ReactPy-Router knows what URL the page is on.
77+ if location :
78+ route_element = _route_state_context (
79+ match .element ,
80+ value = RouteState (set_location , match .params ),
81+ )
9082
9183 def on_history_change (event : dict [str , Any ]) -> None :
9284 """Callback function used within the JavaScript `History` component."""
@@ -96,8 +88,8 @@ def on_history_change(event: dict[str, Any]) -> None:
9688
9789 return ConnectionContext (
9890 History ({"onHistoryChangeCallback" : on_history_change }), # type: ignore[return-value]
99- * route_elements ,
100- value = Connection (old_conn .scope , location , old_conn .carrier ),
91+ route_element ,
92+ value = Connection (old_connection .scope , location or old_connection . location , old_connection .carrier ),
10193 )
10294
10395 return None
@@ -110,9 +102,9 @@ def _iter_routes(routes: Sequence[Route]) -> Iterator[Route]:
110102 yield parent
111103
112104
113- def _add_route_key (match : tuple [ Any , dict [ str , Any ]] , key : str | int ) -> Any :
105+ def _add_route_key (match : MatchedRoute , key : str | int ) -> Any :
114106 """Add a key to the VDOM or component on the current route, if it doesn't already have one."""
115- element , _params = match
107+ element = match . element
116108 if hasattr (element , "render" ) and not element .key :
117109 element = cast (ComponentType , element )
118110 element .key = key
@@ -125,24 +117,12 @@ def _add_route_key(match: tuple[Any, dict[str, Any]], key: str | int) -> Any:
125117def _match_route (
126118 compiled_routes : Sequence [CompiledRoute ],
127119 location : Location ,
128- select : Literal ["first" , "all" ],
129- ) -> list [tuple [Any , dict [str , Any ]]]:
130- matches = []
131-
120+ ) -> MatchedRoute | None :
132121 for resolver in compiled_routes :
133122 match = resolver .resolve (location .pathname )
134123 if match is not None :
135- if select == "first" :
136- return [_add_route_key (match , resolver .key )]
124+ return _add_route_key (match , resolver .key )
137125
138- # Matching multiple routes is disabled since `react-router` no longer supports multiple
139- # matches via the `Route` component. However, it's kept here to support future changes
140- # or third-party routers.
141- # TODO: The `resolver.key` value has edge cases where it is not unique enough to use as
142- # a key here. We can potentially fix this by throwing errors for duplicate identical routes.
143- matches .append (_add_route_key (match , resolver .key )) # pragma: no cover
126+ _logger .debug ("No matching route found for %s" , location .pathname )
144127
145- if not matches :
146- _logger .debug ("No matching route found for %s" , location .pathname )
147-
148- return matches
128+ return None
0 commit comments