diff --git a/contributors.yml b/contributors.yml index e6c663f7be..06bcb5c118 100644 --- a/contributors.yml +++ b/contributors.yml @@ -187,6 +187,7 @@ - jmjpro - johnpangalos - jonkoops +- joseph0926 - jrakotoharisoa - jrestall - juanpprieto diff --git a/packages/react-router/__tests__/generatePath-test.tsx b/packages/react-router/__tests__/generatePath-test.tsx index c4fbf35b7b..3bf3a18433 100644 --- a/packages/react-router/__tests__/generatePath-test.tsx +++ b/packages/react-router/__tests__/generatePath-test.tsx @@ -191,4 +191,25 @@ describe("generatePath", () => { consoleWarn.mockRestore(); }); + + describe("with params followed by static text", () => { + it("interpolates params with file extensions", () => { + expect(generatePath("/books/:id.json", { id: "42" })).toBe( + "/books/42.json", + ); + expect(generatePath("/api/:resource.xml", { resource: "users" })).toBe( + "/api/users.xml", + ); + expect(generatePath("/:lang.html", { lang: "en" })).toBe("/en.html"); + }); + + it("handles multiple extensions", () => { + expect(generatePath("/files/:name.tar.gz", { name: "archive" })).toBe( + "/files/archive.tar.gz", + ); + expect(generatePath("/:file.min.js", { file: "app" })).toBe( + "/app.min.js", + ); + }); + }); }); diff --git a/packages/react-router/lib/router/utils.ts b/packages/react-router/lib/router/utils.ts index 14e5e40c51..cd60e83e85 100644 --- a/packages/react-router/lib/router/utils.ts +++ b/packages/react-router/lib/router/utils.ts @@ -1301,12 +1301,12 @@ export function generatePath( return stringify(params[star]); } - const keyMatch = segment.match(/^:([\w-]+)(\??)$/); + const keyMatch = segment.match(/^:([\w-]+)(\??)(.*)/); if (keyMatch) { - const [, key, optional] = keyMatch; + const [, key, optional, suffix] = keyMatch; let param = params[key as PathParam]; invariant(optional === "?" || param != null, `Missing ":${key}" param`); - return encodeURIComponent(stringify(param)); + return encodeURIComponent(stringify(param)) + suffix; } // Remove any optional markers from optional static segments