From a08b55e9af8194acb60c8c44874a009d7b6fd520 Mon Sep 17 00:00:00 2001 From: Zelys Date: Mon, 8 Jun 2026 16:41:34 +0200 Subject: [PATCH 1/4] Add form submission regression test for %23-encoded dynamic route params (#15162) Co-authored-by: Claude Sonnet 4.6 --- .../dom/data-browser-router-test.tsx | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/packages/react-router/__tests__/dom/data-browser-router-test.tsx b/packages/react-router/__tests__/dom/data-browser-router-test.tsx index 0e4d4e4656..fa7ebbd07e 100644 --- a/packages/react-router/__tests__/dom/data-browser-router-test.tsx +++ b/packages/react-router/__tests__/dom/data-browser-router-test.tsx @@ -3574,6 +3574,48 @@ function testDomRouter( "/foo/bar", ); }); + + it("preserves encoded hash (%23) in form action and submission for param routes", async () => { + let actionPathname: string | null = null; + let router = createTestRouter( + [ + { + path: "/", + children: [ + { + path: ":slug", + action: ({ request }) => { + actionPathname = new URL(request.url).pathname; + return "slug action"; + }, + Component: () => { + let actionData = useActionData(); + return ( + <> +
+ +
+ {actionData &&

{String(actionData)}

} + + ); + }, + }, + ], + }, + ], + { window: getWindow("/%23routeWithHashTag") }, + ); + let { container } = render(); + + expect(container.querySelector("form")?.getAttribute("action")).toBe( + "/%23routeWithHashTag", + ); + + fireEvent.click(screen.getByText("Submit")); + await waitFor(() => screen.getByText("slug action")); + + expect(actionPathname).toBe("/%23routeWithHashTag"); + }); }); describe("splat routes", () => { From cad2370e66d0065481b402f42654bbe92f915f51 Mon Sep 17 00:00:00 2001 From: Facundo Farias Date: Mon, 8 Jun 2026 16:54:20 +0200 Subject: [PATCH 2/4] docs(deploying): add DeployHQ as a Git-based deployment option for Node templates (#15167) * docs(deploying): add DeployHQ as a Git-based deployment option for Node templates * Sign CLA --- contributors.yml | 1 + docs/start/framework/deploying.md | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/contributors.yml b/contributors.yml index c7d2621cd0..8de7f34850 100644 --- a/contributors.yml +++ b/contributors.yml @@ -135,6 +135,7 @@ - ericschn - ErlendS - esadek +- facundofarias - faergeek - fernandojbf - FilipJirsak diff --git a/docs/start/framework/deploying.md b/docs/start/framework/deploying.md index 905dbcc24e..b001cb98a6 100644 --- a/docs/start/framework/deploying.md +++ b/docs/start/framework/deploying.md @@ -94,3 +94,8 @@ Netlify maintains their own template for React Router. Checkout the [Netlify Gui ### EdgeOne Pages EdgeOne Pages maintains their own template for React Router. Checkout the [EdgeOne Pages Guide](https://pages.edgeone.ai/document/framework-react-router) for more information. + +### DeployHQ + +DeployHQ maintains their own guide for deploying React Router v7 to your own server. Checkout the [DeployHQ Guide](https://www.deployhq.com/guides/deploy-react-router-from-github) for more information. + From 6351915ac5266a4096d4f8377bbcc2c8b89813d3 Mon Sep 17 00:00:00 2001 From: Remix Run Bot Date: Mon, 8 Jun 2026 14:55:04 +0000 Subject: [PATCH 3/4] chore: format --- docs/start/framework/deploying.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/start/framework/deploying.md b/docs/start/framework/deploying.md index b001cb98a6..8597b4e36e 100644 --- a/docs/start/framework/deploying.md +++ b/docs/start/framework/deploying.md @@ -98,4 +98,3 @@ EdgeOne Pages maintains their own template for React Router. Checkout the [EdgeO ### DeployHQ DeployHQ maintains their own guide for deploying React Router v7 to your own server. Checkout the [DeployHQ Guide](https://www.deployhq.com/guides/deploy-react-router-from-github) for more information. - From 3fce6d67f805a76b9c3e5a2f0352847ffb3355d2 Mon Sep 17 00:00:00 2001 From: Matt Brophy Date: Mon, 8 Jun 2026 11:50:41 -0400 Subject: [PATCH 4/4] Update docs on data router singleton --- docs/api/data-routers/RouterProvider.md | 8 ++++++-- docs/api/data-routers/createBrowserRouter.md | 4 ++++ docs/api/data-routers/createHashRouter.md | 4 ++++ docs/api/data-routers/createMemoryRouter.md | 4 ++++ docs/start/data/installation.md | 4 ++++ packages/react-router/lib/components.tsx | 12 ++++++++++-- packages/react-router/lib/dom/lib.tsx | 8 ++++++++ 7 files changed, 40 insertions(+), 4 deletions(-) diff --git a/docs/api/data-routers/RouterProvider.md b/docs/api/data-routers/RouterProvider.md index 5805419fda..e69de79d7e 100644 --- a/docs/api/data-routers/RouterProvider.md +++ b/docs/api/data-routers/RouterProvider.md @@ -23,7 +23,9 @@ https://github.com/remix-run/react-router/blob/main/packages/react-router/lib/co [Reference Documentation ↗](https://api.reactrouter.com/v7/functions/react-router.RouterProvider.html) Render the UI for the given [`DataRouter`](https://api.reactrouter.com/v7/interfaces/react-router.DataRouter.html). This component should -typically be at the top of an app's element tree. +typically be at the top of an app's element tree. The router prop should +be a single router instance created outside of the React tree. Avoid +creating new routers during React renders/re-renders. ```tsx import { createBrowserRouter } from "react-router"; @@ -86,7 +88,9 @@ and is only present for render errors. ### router -The [`DataRouter`](https://api.reactrouter.com/v7/interfaces/react-router.DataRouter.html) instance to use for navigation and data fetching. +The [`DataRouter`](https://api.reactrouter.com/v7/interfaces/react-router.DataRouter.html) instance to use for navigation and data fetching. The +router prop should be a single router instance created outside of the React +tree. Avoid creating new routers during React renders/re-renders. ### useTransitions diff --git a/docs/api/data-routers/createBrowserRouter.md b/docs/api/data-routers/createBrowserRouter.md index ed47ad12b2..92b98af3bd 100644 --- a/docs/api/data-routers/createBrowserRouter.md +++ b/docs/api/data-routers/createBrowserRouter.md @@ -26,6 +26,10 @@ Create a new [data router](https://api.reactrouter.com/v7/interfaces/react-route path via [`history.pushState`](https://developer.mozilla.org/en-US/docs/Web/API/History/pushState) and [`history.replaceState`](https://developer.mozilla.org/en-US/docs/Web/API/History/replaceState). +Data Routers should not be held in React state. You should create your router +once outside of the React tree and pass it to [``](../data-routers/RouterProvider). +You can use `patchRoutesOnNavigation` to add additional routes programmatically. + ## Signature ```tsx diff --git a/docs/api/data-routers/createHashRouter.md b/docs/api/data-routers/createHashRouter.md index 16fa9e5b23..6839d23c4f 100644 --- a/docs/api/data-routers/createHashRouter.md +++ b/docs/api/data-routers/createHashRouter.md @@ -25,6 +25,10 @@ https://github.com/remix-run/react-router/blob/main/packages/react-router/lib/do Create a new [data router](https://api.reactrouter.com/v7/interfaces/react-router.DataRouter.html) that manages the application path via the URL [`hash`](https://developer.mozilla.org/en-US/docs/Web/API/URL/hash). +Data Routers should not be held in React state. You should create your router +once outside of the React tree and pass it to [``](../data-routers/RouterProvider). +You can use `patchRoutesOnNavigation` to add additional routes programmatically. + ## Signature ```tsx diff --git a/docs/api/data-routers/createMemoryRouter.md b/docs/api/data-routers/createMemoryRouter.md index 25c25b63da..51127ced82 100644 --- a/docs/api/data-routers/createMemoryRouter.md +++ b/docs/api/data-routers/createMemoryRouter.md @@ -26,6 +26,10 @@ Create a new [`DataRouter`](https://api.reactrouter.com/v7/interfaces/react-rout in-memory [`History`](https://developer.mozilla.org/en-US/docs/Web/API/History) stack. Useful for non-browser environments without a DOM API. +Data Routers should not be held in React state. You should create your router +once outside of the React tree and pass it to [``](../data-routers/RouterProvider). +You can use `patchRoutesOnNavigation` to add additional routes programmatically. + ## Signature ```tsx diff --git a/docs/start/data/installation.md b/docs/start/data/installation.md index 81acc6d8f6..1b7648735e 100644 --- a/docs/start/data/installation.md +++ b/docs/start/data/installation.md @@ -47,6 +47,10 @@ ReactDOM.createRoot(root).render( ); ``` +Data Routers should not be held in React state. You should create your router +once outside of the React tree and pass it to ``. You can use +`patchRoutesOnNavigation` to add additional routes programmatically. + --- Next: [Routing](./routing) diff --git a/packages/react-router/lib/components.tsx b/packages/react-router/lib/components.tsx index 8ebf3c063f..bf8ab51850 100644 --- a/packages/react-router/lib/components.tsx +++ b/packages/react-router/lib/components.tsx @@ -290,6 +290,10 @@ export interface MemoryRouterOpts { * in-memory [`History`](https://developer.mozilla.org/en-US/docs/Web/API/History) * stack. Useful for non-browser environments without a DOM API. * + * Data Routers should not be held in React state. You should create your router + * once outside of the React tree and pass it to {@link RouterProvider | ``}. + * You can use `patchRoutesOnNavigation` to add additional routes programmatically. + * * @public * @category Data Routers * @mode data @@ -374,7 +378,9 @@ export interface ClientOnErrorFunction { */ export interface RouterProviderProps { /** - * The {@link DataRouter} instance to use for navigation and data fetching. + * The {@link DataRouter} instance to use for navigation and data fetching. The + * router prop should be a single router instance created outside of the React + * tree. Avoid creating new routers during React renders/re-renders. */ router: DataRouter; /** @@ -429,7 +435,9 @@ export interface RouterProviderProps { /** * Render the UI for the given {@link DataRouter}. This component should - * typically be at the top of an app's element tree. + * typically be at the top of an app's element tree. The router prop should + * be a single router instance created outside of the React tree. Avoid + * creating new routers during React renders/re-renders. * * ```tsx * import { createBrowserRouter } from "react-router"; diff --git a/packages/react-router/lib/dom/lib.tsx b/packages/react-router/lib/dom/lib.tsx index b303a22a49..cf27522181 100644 --- a/packages/react-router/lib/dom/lib.tsx +++ b/packages/react-router/lib/dom/lib.tsx @@ -628,6 +628,10 @@ export interface DOMRouterOpts { * path via [`history.pushState`](https://developer.mozilla.org/en-US/docs/Web/API/History/pushState) * and [`history.replaceState`](https://developer.mozilla.org/en-US/docs/Web/API/History/replaceState). * + * Data Routers should not be held in React state. You should create your router + * once outside of the React tree and pass it to {@link RouterProvider | ``}. + * You can use `patchRoutesOnNavigation` to add additional routes programmatically. + * * @public * @category Data Routers * @mode data @@ -667,6 +671,10 @@ export function createBrowserRouter( * Create a new {@link DataRouter| data router} that manages the application * path via the URL [`hash`](https://developer.mozilla.org/en-US/docs/Web/API/URL/hash). * + * Data Routers should not be held in React state. You should create your router + * once outside of the React tree and pass it to {@link RouterProvider | ``}. + * You can use `patchRoutesOnNavigation` to add additional routes programmatically. + * * @public * @category Data Routers * @mode data