Skip to content

Commit 88df626

Browse files
authored
Add support for optional segments in nested absolute routes (#14135)
1 parent bb4a55e commit 88df626

File tree

3 files changed

+96
-2
lines changed

3 files changed

+96
-2
lines changed

.changeset/bright-bats-wave.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"react-router": patch
3+
---
4+
5+
Fix usage of optional path segments in nested routes defined using absolute paths

packages/react-router/__tests__/path-matching-test.tsx

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,76 @@ describe("path matching with optional segments", () => {
487487
{ path: "abc", params: {} },
488488
]);
489489
});
490+
491+
test("optional static segments in nested absolute routes (leading)", () => {
492+
let nested = [
493+
{
494+
path: "/en?",
495+
children: [
496+
{
497+
path: "/en?/abc",
498+
children: [
499+
{
500+
path: "/en?/abc/def",
501+
},
502+
],
503+
},
504+
],
505+
},
506+
];
507+
508+
expect(pickPathsAndParams(nested, "/en/abc")).toEqual([
509+
{ path: "/en?", params: {} },
510+
{ path: "/en?/abc", params: {} },
511+
]);
512+
expect(pickPathsAndParams(nested, "/abc")).toEqual([
513+
{ path: "/en?", params: {} },
514+
{ path: "/en?/abc", params: {} },
515+
]);
516+
expect(pickPathsAndParams(nested, "/en/abc/def")).toEqual([
517+
{ path: "/en?", params: {} },
518+
{ path: "/en?/abc", params: {} },
519+
{ path: "/en?/abc/def", params: {} },
520+
]);
521+
expect(pickPathsAndParams(nested, "/abc/def")).toEqual([
522+
{ path: "/en?", params: {} },
523+
{ path: "/en?/abc", params: {} },
524+
{ path: "/en?/abc/def", params: {} },
525+
]);
526+
});
527+
528+
test("optional static segment in nested absolute routes (middle)", () => {
529+
let nested = [
530+
{
531+
path: "/en",
532+
children: [
533+
{
534+
path: "/en/abc?",
535+
children: [
536+
{
537+
path: "/en/abc?/def",
538+
},
539+
],
540+
},
541+
],
542+
},
543+
];
544+
545+
expect(pickPathsAndParams(nested, "/en/abc")).toEqual([
546+
{ path: "/en", params: {} },
547+
{ path: "/en/abc?", params: {} },
548+
]);
549+
expect(pickPathsAndParams(nested, "/en/abc/def")).toEqual([
550+
{ path: "/en", params: {} },
551+
{ path: "/en/abc?", params: {} },
552+
{ path: "/en/abc?/def", params: {} },
553+
]);
554+
expect(pickPathsAndParams(nested, "/en/def")).toEqual([
555+
{ path: "/en", params: {} },
556+
{ path: "/en/abc?", params: {} },
557+
{ path: "/en/abc?/def", params: {} },
558+
]);
559+
});
490560
});
491561

492562
describe("path matching with optional dynamic segments", () => {

packages/react-router/lib/router/utils.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -982,10 +982,12 @@ function flattenRoutes<
982982
branches: RouteBranch<RouteObjectType>[] = [],
983983
parentsMeta: RouteMeta<RouteObjectType>[] = [],
984984
parentPath = "",
985+
_hasParentOptionalSegments = false,
985986
): RouteBranch<RouteObjectType>[] {
986987
let flattenRoute = (
987988
route: RouteObjectType,
988989
index: number,
990+
hasParentOptionalSegments = _hasParentOptionalSegments,
989991
relativePath?: string,
990992
) => {
991993
let meta: RouteMeta<RouteObjectType> = {
@@ -997,6 +999,17 @@ function flattenRoutes<
997999
};
9981000

9991001
if (meta.relativePath.startsWith("/")) {
1002+
if (
1003+
!meta.relativePath.startsWith(parentPath) &&
1004+
hasParentOptionalSegments
1005+
) {
1006+
// If we're inside of a parent route that has optional segments, we don't
1007+
// want to throw a hard error here because due to the route exploding
1008+
// approach, some of the routes won't match by design and we can just
1009+
// discard them instead.
1010+
// https://github.com/remix-run/react-router/issues/9925#issuecomment-1387252214
1011+
return;
1012+
}
10001013
invariant(
10011014
meta.relativePath.startsWith(parentPath),
10021015
`Absolute route path "${meta.relativePath}" nested under path ` +
@@ -1021,7 +1034,13 @@ function flattenRoutes<
10211034
`Index routes must not have child routes. Please remove ` +
10221035
`all child routes from route path "${path}".`,
10231036
);
1024-
flattenRoutes(route.children, branches, routesMeta, path);
1037+
flattenRoutes(
1038+
route.children,
1039+
branches,
1040+
routesMeta,
1041+
path,
1042+
hasParentOptionalSegments,
1043+
);
10251044
}
10261045

10271046
// Routes without a path shouldn't ever match by themselves unless they are
@@ -1042,7 +1061,7 @@ function flattenRoutes<
10421061
flattenRoute(route, index);
10431062
} else {
10441063
for (let exploded of explodeOptionalSegments(route.path)) {
1045-
flattenRoute(route, index, exploded);
1064+
flattenRoute(route, index, true, exploded);
10461065
}
10471066
}
10481067
});

0 commit comments

Comments
 (0)