Skip to content

Commit 7a25561

Browse files
authored
Fix TypeError if you throw from patchRoutesOnNavigation when no partial matches exist (#14198)
1 parent 8704ae4 commit 7a25561

File tree

3 files changed

+115
-0
lines changed

3 files changed

+115
-0
lines changed

.changeset/real-rules-compare.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"react-router": patch
3+
---
4+
5+
Fix `TypeError` if you throw from `patchRoutesOnNavigation` when no partial matches exist

packages/react-router/__tests__/router/lazy-discovery-test.ts

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2103,6 +2103,91 @@ describe("Lazy Route Discovery (Fog of War)", () => {
21032103
expect(router.state.matches.map((m) => m.route.id)).toEqual(["a", "b"]);
21042104
});
21052105

2106+
it("handles errors thrown from patchRoutesOnNavigation() when there are no partial matches (GET navigation)", async () => {
2107+
router = createRouter({
2108+
history: createMemoryHistory(),
2109+
routes: [
2110+
{
2111+
id: "a",
2112+
path: "a",
2113+
},
2114+
],
2115+
async patchRoutesOnNavigation({ patch }) {
2116+
await tick();
2117+
throw new Error("broke!");
2118+
},
2119+
});
2120+
2121+
await router.navigate("/b");
2122+
expect(router.state).toMatchObject({
2123+
// A bit odd but this is a result of our best attempt to display some form
2124+
// of error UI to the user - follows the same logic we use on 404s
2125+
matches: [
2126+
{
2127+
params: {},
2128+
pathname: "",
2129+
pathnameBase: "",
2130+
route: {
2131+
children: undefined,
2132+
hasErrorBoundary: false,
2133+
id: "a",
2134+
path: "a",
2135+
},
2136+
},
2137+
],
2138+
location: { pathname: "/b" },
2139+
actionData: null,
2140+
loaderData: {},
2141+
errors: {
2142+
a: new Error("broke!"),
2143+
},
2144+
});
2145+
});
2146+
2147+
it("handles errors thrown from patchRoutesOnNavigation() when there are no partial matches (POST navigation)", async () => {
2148+
router = createRouter({
2149+
history: createMemoryHistory(),
2150+
routes: [
2151+
{
2152+
id: "a",
2153+
path: "a",
2154+
},
2155+
],
2156+
async patchRoutesOnNavigation({ patch }) {
2157+
await tick();
2158+
throw new Error("broke!");
2159+
},
2160+
});
2161+
2162+
await router.navigate("/b", {
2163+
formMethod: "POST",
2164+
formData: createFormData({}),
2165+
});
2166+
expect(router.state).toMatchObject({
2167+
// A bit odd but this is a result of our best attempt to display some form
2168+
// of error UI to the user - follows the same logic we use on 404s
2169+
matches: [
2170+
{
2171+
params: {},
2172+
pathname: "",
2173+
pathnameBase: "",
2174+
route: {
2175+
children: undefined,
2176+
hasErrorBoundary: false,
2177+
id: "a",
2178+
path: "a",
2179+
},
2180+
},
2181+
],
2182+
location: { pathname: "/b" },
2183+
actionData: null,
2184+
loaderData: {},
2185+
errors: {
2186+
a: new Error("broke!"),
2187+
},
2188+
});
2189+
});
2190+
21062191
it("bubbles errors thrown from patchRoutesOnNavigation() during hydration", async () => {
21072192
router = createRouter({
21082193
history: createMemoryHistory({

packages/react-router/lib/router/router.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1805,6 +1805,20 @@ export function createRouter(init: RouterInit): Router {
18051805
if (discoverResult.type === "aborted") {
18061806
return { shortCircuited: true };
18071807
} else if (discoverResult.type === "error") {
1808+
if (discoverResult.partialMatches.length === 0) {
1809+
let { matches, route } = getShortCircuitMatches(dataRoutes);
1810+
return {
1811+
matches,
1812+
pendingActionResult: [
1813+
route.id,
1814+
{
1815+
type: ResultType.error,
1816+
error: discoverResult.error,
1817+
},
1818+
],
1819+
};
1820+
}
1821+
18081822
let boundaryId = findNearestBoundary(discoverResult.partialMatches)
18091823
.route.id;
18101824
return {
@@ -1999,6 +2013,17 @@ export function createRouter(init: RouterInit): Router {
19992013
if (discoverResult.type === "aborted") {
20002014
return { shortCircuited: true };
20012015
} else if (discoverResult.type === "error") {
2016+
if (discoverResult.partialMatches.length === 0) {
2017+
let { matches, route } = getShortCircuitMatches(dataRoutes);
2018+
return {
2019+
matches,
2020+
loaderData: {},
2021+
errors: {
2022+
[route.id]: discoverResult.error,
2023+
},
2024+
};
2025+
}
2026+
20022027
let boundaryId = findNearestBoundary(discoverResult.partialMatches)
20032028
.route.id;
20042029
return {

0 commit comments

Comments
 (0)