diff --git a/.changeset/big-trainers-cough.md b/.changeset/big-trainers-cough.md new file mode 100644 index 0000000000..7cb00838e7 --- /dev/null +++ b/.changeset/big-trainers-cough.md @@ -0,0 +1,5 @@ +--- +"react-router": patch +--- + +Fix regression and properly decode paths inside `useMatch` so matches/params reflect decoded params diff --git a/packages/react-router-dom/__tests__/special-characters-test.tsx b/packages/react-router-dom/__tests__/special-characters-test.tsx index ce0dfcb2c7..408d96696f 100644 --- a/packages/react-router-dom/__tests__/special-characters-test.tsx +++ b/packages/react-router-dom/__tests__/special-characters-test.tsx @@ -15,6 +15,7 @@ import { HashRouter, MemoryRouter, Link, + Outlet, Routes, Route, RouterProvider, @@ -23,6 +24,7 @@ import { createMemoryRouter, createRoutesFromElements, useLocation, + useMatch, useNavigate, useParams, } from "react-router-dom"; @@ -962,6 +964,51 @@ describe("special character tests", () => { `"
{"pathname":"/with%20space","search":"","hash":""}
"` ); }); + + it("properly decodes params in useMatch", () => { + let testWindow = getWindow("/user/bücherwurm"); + + let router = createBrowserRouter( + [ + { + path: "/", + Component() { + let match = useMatch("/user/:username"); + return ( + <> +
{JSON.stringify(match, null, 2)}
+ + + ); + }, + children: [ + { + path: "user/:username", + element: null, + }, + ], + }, + ], + { window: testWindow } + ); + let ctx = render(); + + expect(testWindow.location.pathname).toBe("/user/b%C3%BCcherwurm"); + expect(ctx.container.innerHTML).toMatchInlineSnapshot(` + "
{
+            "params": {
+              "username": "bücherwurm"
+            },
+            "pathname": "/user/bücherwurm",
+            "pathnameBase": "/user/bücherwurm",
+            "pattern": {
+              "path": "/user/:username",
+              "caseSensitive": false,
+              "end": true
+            }
+          }
" + `); + }); }); describe("hash routers", () => { diff --git a/packages/react-router/lib/hooks.tsx b/packages/react-router/lib/hooks.tsx index 37697ee647..d57dfd1cc3 100644 --- a/packages/react-router/lib/hooks.tsx +++ b/packages/react-router/lib/hooks.tsx @@ -18,6 +18,7 @@ import { IDLE_BLOCKER, Action as NavigationType, UNSAFE_convertRouteMatchToUiMatch as convertRouteMatchToUiMatch, + UNSAFE_decodePath as decodePath, UNSAFE_getResolveToMatches as getResolveToMatches, UNSAFE_invariant as invariant, isRouteErrorResponse, @@ -141,7 +142,7 @@ export function useMatch< let { pathname } = useLocation(); return React.useMemo( - () => matchPath(pattern, pathname), + () => matchPath(pattern, decodePath(pathname)), [pathname, pattern] ); } diff --git a/packages/router/index.ts b/packages/router/index.ts index f5f984d57f..172b95644d 100644 --- a/packages/router/index.ts +++ b/packages/router/index.ts @@ -92,6 +92,7 @@ export { ErrorResponseImpl as UNSAFE_ErrorResponseImpl, convertRoutesToDataRoutes as UNSAFE_convertRoutesToDataRoutes, convertRouteMatchToUiMatch as UNSAFE_convertRouteMatchToUiMatch, + decodePath as UNSAFE_decodePath, getResolveToMatches as UNSAFE_getResolveToMatches, } from "./utils"; diff --git a/packages/router/utils.ts b/packages/router/utils.ts index 4e22db1566..517bf7e566 100644 --- a/packages/router/utils.ts +++ b/packages/router/utils.ts @@ -1075,7 +1075,7 @@ function compilePath( return [matcher, params]; } -function decodePath(value: string) { +export function decodePath(value: string) { try { return value .split("/")