Skip to content

Commit b571356

Browse files
authored
fix initial load 404 scnearios in data mode (#13500)
1 parent 0a24caf commit b571356

File tree

3 files changed

+98
-57
lines changed

3 files changed

+98
-57
lines changed

.changeset/fast-planets-matter.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 initial load 404 scenarios in data mode

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

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1101,6 +1101,37 @@ describe("a router", () => {
11011101
router.dispose();
11021102
});
11031103

1104+
it("handles initial load 404s when the error boundary router has a loader", async () => {
1105+
let router = createRouter({
1106+
history: createMemoryHistory({ initialEntries: ["/404"] }),
1107+
routes: [
1108+
{
1109+
path: "/",
1110+
hasErrorBoundary: true,
1111+
loader: () => {},
1112+
},
1113+
],
1114+
});
1115+
1116+
expect(router.state).toMatchObject({
1117+
historyAction: "POP",
1118+
location: expect.objectContaining({ pathname: "/404" }),
1119+
initialized: true,
1120+
navigation: IDLE_NAVIGATION,
1121+
loaderData: {},
1122+
errors: {
1123+
"0": new ErrorResponseImpl(
1124+
404,
1125+
"Not Found",
1126+
new Error('No route matches URL "/404"'),
1127+
true
1128+
),
1129+
},
1130+
});
1131+
1132+
router.dispose();
1133+
});
1134+
11041135
it("kicks off initial data load when hash is present", async () => {
11051136
let loaderDfd = createDeferred();
11061137
let loaderSpy = jest.fn(() => loaderDfd.promise);

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

Lines changed: 62 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -870,6 +870,7 @@ export function createRouter(init: RouterInit): Router {
870870
let initialMatches = matchRoutes(dataRoutes, init.history.location, basename);
871871
let initialMatchesIsFOW = false;
872872
let initialErrors: RouteData | null = null;
873+
let initialized: boolean;
873874

874875
if (initialMatches == null && !init.patchRoutesOnNavigation) {
875876
// If we do not match a user-provided-route, fall back to the root
@@ -878,69 +879,73 @@ export function createRouter(init: RouterInit): Router {
878879
pathname: init.history.location.pathname,
879880
});
880881
let { matches, route } = getShortCircuitMatches(dataRoutes);
882+
initialized = true;
881883
initialMatches = matches;
882884
initialErrors = { [route.id]: error };
883-
}
884-
885-
// In SPA apps, if the user provided a patchRoutesOnNavigation implementation and
886-
// our initial match is a splat route, clear them out so we run through lazy
887-
// discovery on hydration in case there's a more accurate lazy route match.
888-
// In SSR apps (with `hydrationData`), we expect that the server will send
889-
// up the proper matched routes so we don't want to run lazy discovery on
890-
// initial hydration and want to hydrate into the splat route.
891-
if (initialMatches && !init.hydrationData) {
892-
let fogOfWar = checkFogOfWar(
893-
initialMatches,
894-
dataRoutes,
895-
init.history.location.pathname
896-
);
897-
if (fogOfWar.active) {
898-
initialMatches = null;
885+
} else {
886+
// In SPA apps, if the user provided a patchRoutesOnNavigation implementation and
887+
// our initial match is a splat route, clear them out so we run through lazy
888+
// discovery on hydration in case there's a more accurate lazy route match.
889+
// In SSR apps (with `hydrationData`), we expect that the server will send
890+
// up the proper matched routes so we don't want to run lazy discovery on
891+
// initial hydration and want to hydrate into the splat route.
892+
if (initialMatches && !init.hydrationData) {
893+
let fogOfWar = checkFogOfWar(
894+
initialMatches,
895+
dataRoutes,
896+
init.history.location.pathname
897+
);
898+
if (fogOfWar.active) {
899+
initialMatches = null;
900+
}
899901
}
900-
}
901902

902-
let initialized: boolean;
903-
if (!initialMatches) {
904-
initialized = false;
905-
initialMatches = [];
906-
907-
// If partial hydration and fog of war is enabled, we will be running
908-
// `patchRoutesOnNavigation` during hydration so include any partial matches as
909-
// the initial matches so we can properly render `HydrateFallback`'s
910-
let fogOfWar = checkFogOfWar(
911-
null,
912-
dataRoutes,
913-
init.history.location.pathname
914-
);
915-
if (fogOfWar.active && fogOfWar.matches) {
916-
initialMatchesIsFOW = true;
917-
initialMatches = fogOfWar.matches;
918-
}
919-
} else if (initialMatches.some((m) => m.route.lazy)) {
920-
// All initialMatches need to be loaded before we're ready. If we have lazy
921-
// functions around still then we'll need to run them in initialize()
922-
initialized = false;
923-
} else if (!initialMatches.some((m) => m.route.loader)) {
924-
// If we've got no loaders to run, then we're good to go
925-
initialized = true;
926-
} else {
927-
// With "partial hydration", we're initialized so long as we were
928-
// provided with hydrationData for every route with a loader, and no loaders
929-
// were marked for explicit hydration
930-
let loaderData = init.hydrationData ? init.hydrationData.loaderData : null;
931-
let errors = init.hydrationData ? init.hydrationData.errors : null;
932-
// If errors exist, don't consider routes below the boundary
933-
if (errors) {
934-
let idx = initialMatches.findIndex(
935-
(m) => errors![m.route.id] !== undefined
903+
if (!initialMatches) {
904+
initialized = false;
905+
initialMatches = [];
906+
907+
// If partial hydration and fog of war is enabled, we will be running
908+
// `patchRoutesOnNavigation` during hydration so include any partial matches as
909+
// the initial matches so we can properly render `HydrateFallback`'s
910+
let fogOfWar = checkFogOfWar(
911+
null,
912+
dataRoutes,
913+
init.history.location.pathname
936914
);
937-
initialized = initialMatches
938-
.slice(0, idx + 1)
939-
.every((m) => !shouldLoadRouteOnHydration(m.route, loaderData, errors));
915+
if (fogOfWar.active && fogOfWar.matches) {
916+
initialMatchesIsFOW = true;
917+
initialMatches = fogOfWar.matches;
918+
}
919+
} else if (initialMatches.some((m) => m.route.lazy)) {
920+
// All initialMatches need to be loaded before we're ready. If we have lazy
921+
// functions around still then we'll need to run them in initialize()
922+
initialized = false;
923+
} else if (!initialMatches.some((m) => m.route.loader)) {
924+
// If we've got no loaders to run, then we're good to go
925+
initialized = true;
940926
} else {
941-
initialized = initialMatches.every(
942-
(m) => !shouldLoadRouteOnHydration(m.route, loaderData, errors)
943-
);
927+
// With "partial hydration", we're initialized so long as we were
928+
// provided with hydrationData for every route with a loader, and no loaders
929+
// were marked for explicit hydration
930+
let loaderData = init.hydrationData
931+
? init.hydrationData.loaderData
932+
: null;
933+
let errors = init.hydrationData ? init.hydrationData.errors : null;
934+
// If errors exist, don't consider routes below the boundary
935+
if (errors) {
936+
let idx = initialMatches.findIndex(
937+
(m) => errors![m.route.id] !== undefined
938+
);
939+
initialized = initialMatches
940+
.slice(0, idx + 1)
941+
.every(
942+
(m) => !shouldLoadRouteOnHydration(m.route, loaderData, errors)
943+
);
944+
} else {
945+
initialized = initialMatches.every(
946+
(m) => !shouldLoadRouteOnHydration(m.route, loaderData, errors)
947+
);
948+
}
944949
}
945950
}
946951

0 commit comments

Comments
 (0)