diff --git a/contributors.yml b/contributors.yml
index dafd53ea79..a5286cd163 100644
--- a/contributors.yml
+++ b/contributors.yml
@@ -70,6 +70,7 @@
- timdorr
- tkindy
- turansky
+- tyankatsu0105
- underager
- vijaypushkin
- vikingviolinist
diff --git a/docs/hooks/use-match.md b/docs/hooks/use-match.md
index ddfa4111fb..6e6c8395d2 100644
--- a/docs/hooks/use-match.md
+++ b/docs/hooks/use-match.md
@@ -8,8 +8,8 @@ title: useMatch
Type declaration
```tsx
-declare function useMatch(
- pattern: PathPattern | string
+declare function useMatch, Path extends string>(
+ pattern: PathPattern | Path
): PathMatch | null;
```
diff --git a/docs/utils/generate-path.md b/docs/utils/generate-path.md
index 1de10edfe9..465aee052e 100644
--- a/docs/utils/generate-path.md
+++ b/docs/utils/generate-path.md
@@ -8,9 +8,26 @@ title: generatePath
Type declaration
```tsx
-declare function generatePath(
- path: string,
- params?: Params
+ type PathParams<
+ Path extends string
+> = Path extends `:${infer Param}/${infer Rest}`
+ ? Param | PathParams
+ : Path extends `:${infer Param}`
+ ? Param
+ : Path extends `${any}:${infer Param}`
+ ? PathParams<`:${Param}`>
+ : Path extends `${any}/*`
+ ? "*"
+ : Path extends "*"
+ ? "*"
+ : never
+
+
+declare function generatePath(
+ path: Path,
+ params?: {
+ [key in PathParams]: string
+ }
): string;
```
diff --git a/packages/router/utils.ts b/packages/router/utils.ts
index dad0bd8f76..974b10a966 100644
--- a/packages/router/utils.ts
+++ b/packages/router/utils.ts
@@ -159,51 +159,40 @@ export interface DataRouteObject extends RouteObject {
id: string;
}
-type ParamParseFailed = { failed: true };
-
-type ParamParseSegment =
- // Check here if there exists a forward slash in the string.
- Segment extends `${infer LeftSegment}/${infer RightSegment}`
- ? // If there is a forward slash, then attempt to parse each side of the
- // forward slash.
- ParamParseSegment extends infer LeftResult
- ? ParamParseSegment extends infer RightResult
- ? LeftResult extends string
- ? // If the left side is successfully parsed as a param, then check if
- // the right side can be successfully parsed as well. If both sides
- // can be parsed, then the result is a union of the two sides
- // (read: "foo" | "bar").
- RightResult extends string
- ? LeftResult | RightResult
- : LeftResult
- : // If the left side is not successfully parsed as a param, then check
- // if only the right side can be successfully parse as a param. If it
- // can, then the result is just right, else it's a failure.
- RightResult extends string
- ? RightResult
- : ParamParseFailed
- : ParamParseFailed
- : // If the left side didn't parse into a param, then just check the right
- // side.
- ParamParseSegment extends infer RightResult
- ? RightResult extends string
- ? RightResult
- : ParamParseFailed
- : ParamParseFailed
- : // If there's no forward slash, then check if this segment starts with a
- // colon. If it does, then this is a dynamic segment, so the result is
- // just the remainder of the string, optionally prefixed with another string.
- // Otherwise, it's a failure.
- Segment extends `${string}:${infer Remaining}`
- ? Remaining
- : ParamParseFailed;
+type Star = "*"
+/**
+ * @private
+ * Return string union from path string.
+ * @example
+ * PathParam<"/path/:a/:b"> // "a" | "b"
+ * PathParam<"/path/:a/:b/*"> // "a" | "b" | "*"
+ */
+ type PathParam<
+ Path extends string
+> =
+ // Check path string starts with slash and a param string.
+ Path extends `:${infer Param}/${infer Rest}`
+ ? Param | PathParam
+ // Check path string is a param string.
+ : Path extends `:${infer Param}`
+ ? Param
+ // Check path string ends with slash and a param string.
+ : Path extends `${any}/:${infer Param}`
+ ? PathParam<`:${Param}`>
+ // Check path string ends with slash and a star.
+ : Path extends `${any}/${Star}`
+ ? Star
+ // Check string is star.
+ : Path extends Star
+ ? Star
+ : never
// Attempt to parse the given string segment. If it fails, then just return the
// plain string type as a default fallback. Otherwise return the union of the
// parsed string literals that were referenced as dynamic segments in the route.
export type ParamParseKey =
- ParamParseSegment extends string
- ? ParamParseSegment
+ [PathParam] extends [never]
+ ? PathParam
: string;
/**
@@ -446,14 +435,20 @@ function matchRouteBranch<
*
* @see https://reactrouter.com/docs/en/v6/utils/generate-path
*/
-export function generatePath(path: string, params: Params = {}): string {
+export function generatePath(path: Path, params: {
+ [key in PathParam]: string
+} = {} as any): string {
return path
- .replace(/:(\w+)/g, (_, key) => {
+ .replace(/:(\w+)/g, (_, key: PathParam) => {
invariant(params[key] != null, `Missing ":${key}" param`);
return params[key]!;
})
.replace(/\/*\*$/, (_) =>
- params["*"] == null ? "" : params["*"].replace(/^\/*/, "/")
+ {
+ const star = "*" as PathParam
+
+ return params[star] == null ? "" : params[star].replace(/^\/*/, "/")
+ }
);
}