diff --git a/.changeset/big-olives-doubt.md b/.changeset/big-olives-doubt.md new file mode 100644 index 0000000000..006cb6ce52 --- /dev/null +++ b/.changeset/big-olives-doubt.md @@ -0,0 +1,5 @@ +--- +"react-router-dom": patch +--- + +Treat absolute/same-origin/different-basename values as external diff --git a/package.json b/package.json index 446186dc2d..7ededba1ec 100644 --- a/package.json +++ b/package.json @@ -114,7 +114,7 @@ "none": "15 kB" }, "packages/react-router-dom/dist/react-router-dom.production.min.js": { - "none": "11.5 kB" + "none": "11.6 kB" }, "packages/react-router-dom/dist/umd/react-router-dom.production.min.js": { "none": "17.5 kB" diff --git a/packages/react-router-dom/__tests__/link-click-test.tsx b/packages/react-router-dom/__tests__/link-click-test.tsx index d3a8facd4c..29e878ce76 100644 --- a/packages/react-router-dom/__tests__/link-click-test.tsx +++ b/packages/react-router-dom/__tests__/link-click-test.tsx @@ -138,7 +138,6 @@ describe("A click", () => {

Home

{ handlerCalled = true; @@ -171,6 +170,78 @@ describe("A click", () => { }); }); + describe("when a same-origin/different-basename absolute URL is specified", () => { + it("does not prevent default", () => { + function Home() { + return ( +
+

Home

+ About +
+ ); + } + + act(() => { + ReactDOM.createRoot(node).render( + + + } /> + + + ); + }); + + let anchor = node.querySelector("a"); + expect(anchor).not.toBeNull(); + + let event: MouseEvent; + act(() => { + event = click(anchor); + }); + + expect(event.defaultPrevented).toBe(false); + }); + + it("calls provided listener", () => { + let handlerCalled; + let defaultPrevented; + + function Home() { + return ( +
+

Home

+ { + handlerCalled = true; + defaultPrevented = e.defaultPrevented; + }} + > + About + +
+ ); + } + + act(() => { + ReactDOM.createRoot(node).render( + + + } /> + + + ); + }); + + act(() => { + click(node.querySelector("a")); + }); + + expect(handlerCalled).toBe(true); + expect(defaultPrevented).toBe(false); + }); + }); + describe("when reloadDocument is specified", () => { it("does not prevent default", () => { function Home() { diff --git a/packages/react-router-dom/__tests__/link-push-test.tsx b/packages/react-router-dom/__tests__/link-push-test.tsx index 305f1f1009..07b3263429 100644 --- a/packages/react-router-dom/__tests__/link-push-test.tsx +++ b/packages/react-router-dom/__tests__/link-push-test.tsx @@ -283,4 +283,47 @@ describe("Link push and replace", () => { `); }); }); + + describe("to an absolute same-origin/same-basename URL, when it is clicked", () => { + it("performs a push", () => { + function Home() { + return ( +
+

Home

+ About +
+ ); + } + + let renderer: TestRenderer.ReactTestRenderer; + TestRenderer.act(() => { + renderer = TestRenderer.create( + + + } /> + } /> + + + ); + }); + + let anchor = renderer.root.findByType("a"); + + TestRenderer.act(() => { + anchor.props.onClick( + new MouseEvent("click", { + view: window, + bubbles: true, + cancelable: true, + }) + ); + }); + + expect(renderer.toJSON()).toMatchInlineSnapshot(` +

+ PUSH +

+ `); + }); + }); }); diff --git a/packages/react-router-dom/index.tsx b/packages/react-router-dom/index.tsx index 1c24998adc..e7884bc18b 100644 --- a/packages/react-router-dom/index.tsx +++ b/packages/react-router-dom/index.tsx @@ -42,6 +42,7 @@ import { createHashHistory, UNSAFE_invariant as invariant, joinPaths, + stripBasename, ErrorResponse, } from "@remix-run/router"; @@ -420,6 +421,8 @@ export const Link = React.forwardRef( }, ref ) { + let { basename } = React.useContext(NavigationContext); + // Rendered into for absolute URLs let absoluteHref; let isExternal = false; @@ -434,9 +437,11 @@ export const Link = React.forwardRef( let targetUrl = to.startsWith("//") ? new URL(currentUrl.protocol + to) : new URL(to); - if (targetUrl.origin === currentUrl.origin) { - // Strip the protocol/origin for same-origin absolute URLs - to = targetUrl.pathname + targetUrl.search + targetUrl.hash; + let path = stripBasename(targetUrl.pathname, basename); + + if (targetUrl.origin === currentUrl.origin && path != null) { + // Strip the protocol/origin/basename for same-origin absolute URLs + to = path + targetUrl.search + targetUrl.hash; } else { isExternal = true; }