Skip to content

Commit 6557000

Browse files
committed
Allow multi-component routes
1 parent 189b484 commit 6557000

File tree

2 files changed

+23
-10
lines changed

2 files changed

+23
-10
lines changed

src/reactpy_router/core.py

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
from __future__ import annotations
44

55
from dataclasses import dataclass, replace
6+
from logging import getLogger
67
from pathlib import Path
7-
from typing import Any, Callable, Iterator, Sequence, TypeVar
8+
from typing import Any, Callable, Iterator, Literal, Sequence, TypeVar
89
from urllib.parse import parse_qs
910

1011
from reactpy import (
@@ -24,6 +25,7 @@
2425

2526
from reactpy_router.types import Route, RouteCompiler, Router, RouteResolver
2627

28+
_logger = getLogger(__name__)
2729
R = TypeVar("R", bound=Route)
2830

2931

@@ -35,18 +37,19 @@ def route(path: str, element: Any | None, *routes: Route) -> Route:
3537
def create_router(compiler: RouteCompiler[R]) -> Router[R]:
3638
"""A decorator that turns a route compiler into a router"""
3739

38-
def wrapper(*routes: R) -> ComponentType:
39-
return router_component(*routes, compiler=compiler)
40+
def wrapper(*routes: R, select: Literal["first", "all"] = "first") -> ComponentType:
41+
return router_component(*routes, select=select, compiler=compiler)
4042

4143
return wrapper
4244

4345

4446
@component
4547
def router_component(
4648
*routes: R,
49+
select: Literal["first", "all"],
4750
compiler: RouteCompiler[R],
4851
) -> VdomDict | None:
49-
"""A component that renders the first matching route using the given compiler"""
52+
"""A component that renders matching route(s) using the given compiler function."""
5053

5154
old_conn = use_connection()
5255
location, set_location = use_state(old_conn.location)
@@ -56,7 +59,7 @@ def router_component(
5659
dependencies=(compiler, hash(routes)),
5760
)
5861

59-
match = use_memo(lambda: _match_route(resolvers, location))
62+
match = use_memo(lambda: _match_route(resolvers, location, select))
6063

6164
if match is not None:
6265
element, params = match
@@ -128,12 +131,22 @@ def _iter_routes(routes: Sequence[R]) -> Iterator[R]:
128131
yield parent
129132

130133

131-
def _match_route(compiled_routes: Sequence[RouteResolver], location: Location) -> tuple[Any, dict[str, Any]] | None:
134+
def _match_route(
135+
compiled_routes: Sequence[RouteResolver], location: Location, select: Literal["first", "all"]
136+
) -> tuple[Any, dict[str, Any]] | None:
137+
matches = []
138+
132139
for resolver in compiled_routes:
133140
match = resolver.resolve(location.pathname)
134141
if match is not None:
135-
return match
136-
return None
142+
if select == "first":
143+
return match
144+
matches.append(match)
145+
146+
if not matches:
147+
_logger.debug("No matching route found for %s", location.pathname)
148+
149+
return matches or None
137150

138151

139152
_link, _history = export(

src/reactpy_router/types.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from __future__ import annotations
44

55
from dataclasses import dataclass, field
6-
from typing import Any, Sequence, TypeVar
6+
from typing import Any, Literal, Sequence, TypeVar
77

88
from reactpy.core.vdom import is_vdom
99
from reactpy.types import ComponentType, Key
@@ -35,7 +35,7 @@ def __hash__(self) -> int:
3535
class Router(Protocol[R]):
3636
"""Return a component that renders the first matching route"""
3737

38-
def __call__(self, *routes: R) -> ComponentType:
38+
def __call__(self, *routes: R, select: Literal["first", "all"]) -> ComponentType:
3939
...
4040

4141

0 commit comments

Comments
 (0)