diff --git a/.changeset/invalid-link-to.md b/.changeset/invalid-link-to.md new file mode 100644 index 0000000000..309699aaf8 --- /dev/null +++ b/.changeset/invalid-link-to.md @@ -0,0 +1,5 @@ +--- +"react-router-dom": patch +--- + +Fail gracefully on `` and other invalid URL values diff --git a/packages/react-router-dom/__tests__/link-href-test.tsx b/packages/react-router-dom/__tests__/link-href-test.tsx index 386fb7d9b2..762f972ddc 100644 --- a/packages/react-router-dom/__tests__/link-href-test.tsx +++ b/packages/react-router-dom/__tests__/link-href-test.tsx @@ -907,4 +907,24 @@ describe(" href", () => { ); }); }); + + test("fails gracefully on invalid `to` values", () => { + let warnSpy = jest.spyOn(console, "warn").mockImplementation(() => {}); + let renderer: TestRenderer.ReactTestRenderer; + TestRenderer.act(() => { + renderer = TestRenderer.create( + + + } /> + + + ); + }); + + expect(renderer.root.findByType("a").props.href).toEqual("//"); + expect(warnSpy).toHaveBeenCalledWith( + ' contains an invalid URL which will probably break when clicked - please update to a valid URL path.' + ); + warnSpy.mockRestore(); + }); }); diff --git a/packages/react-router-dom/index.tsx b/packages/react-router-dom/index.tsx index 32e17b2a6c..a8351110e4 100644 --- a/packages/react-router-dom/index.tsx +++ b/packages/react-router-dom/index.tsx @@ -451,17 +451,26 @@ export const Link = React.forwardRef( // Only check for external origins client-side if (isBrowser) { - let currentUrl = new URL(window.location.href); - let targetUrl = to.startsWith("//") - ? new URL(currentUrl.protocol + to) - : new URL(to); - 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; + try { + let currentUrl = new URL(window.location.href); + let targetUrl = to.startsWith("//") + ? new URL(currentUrl.protocol + to) + : new URL(to); + 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; + } + } catch (e) { + // We can't do external URL detection without a valid URL + warning( + false, + ` contains an invalid URL which will probably break ` + + `when clicked - please update to a valid URL path.` + ); } } }