Skip to content

Commit 2a128f1

Browse files
authored
Fix cleared loaderData bug on thrown action errors (remix-run#13476)
1 parent 289cf00 commit 2a128f1

File tree

3 files changed

+86
-5
lines changed

3 files changed

+86
-5
lines changed

.changeset/hip-laws-teach.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"react-router": patch
3+
---
4+
5+
Fix bug where bubbled action errors would result in `loaderData` being cleared at the handling `ErrorBoundary` route

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

+62
Original file line numberDiff line numberDiff line change
@@ -1170,4 +1170,66 @@ describe("shouldRevalidate", () => {
11701170

11711171
router.dispose();
11721172
});
1173+
1174+
it("preserves ancestor loaderData on bubbled action errors when not revalidating with a custom data strategy", async () => {
1175+
let history = createMemoryHistory();
1176+
let router = createRouter({
1177+
history,
1178+
routes: [
1179+
{
1180+
id: "root",
1181+
path: "/",
1182+
hasErrorBoundary: true,
1183+
loader: () => "NOPE",
1184+
children: [
1185+
{
1186+
id: "index",
1187+
index: true,
1188+
action: () => {
1189+
throw new Response("ERROR 400", { status: 400 });
1190+
},
1191+
},
1192+
],
1193+
},
1194+
],
1195+
hydrationData: {
1196+
loaderData: {
1197+
root: "ROOT",
1198+
},
1199+
},
1200+
async dataStrategy({ request, matches }) {
1201+
let keyedResults = {};
1202+
let matchesToLoad = matches.filter((match) =>
1203+
match.unstable_shouldCallHandler(
1204+
request.method === "POST"
1205+
? undefined
1206+
: !match.unstable_shouldRevalidateArgs?.actionStatus ||
1207+
match.unstable_shouldRevalidateArgs.actionStatus < 400
1208+
)
1209+
);
1210+
await Promise.all(
1211+
matchesToLoad.map(async (match) => {
1212+
keyedResults[match.route.id] = await match.resolve();
1213+
})
1214+
);
1215+
return keyedResults;
1216+
},
1217+
});
1218+
router.initialize();
1219+
1220+
router.navigate("/?index", {
1221+
formMethod: "post",
1222+
formData: createFormData({ gosh: "dang" }),
1223+
});
1224+
await tick();
1225+
expect(router.state).toMatchObject({
1226+
location: { pathname: "/" },
1227+
navigation: { state: "idle" },
1228+
loaderData: { root: "ROOT" },
1229+
actionData: null,
1230+
errors: { root: new ErrorResponseImpl(400, "", "ERROR 400") },
1231+
});
1232+
1233+
router.dispose();
1234+
});
11731235
});

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

+19-5
Original file line numberDiff line numberDiff line change
@@ -686,7 +686,15 @@ interface ShortCircuitable {
686686
shortCircuited?: boolean;
687687
}
688688

689-
type PendingActionResult = [string, SuccessResult | ErrorResult];
689+
// Track any pending errors from the action (or other pre-loader flows).
690+
// The format is [bubbledRouteId, result, actionRouteId?]
691+
// We should probably change this to an object now that we (optionally) track
692+
// the original action route id so we can clear out loaderData in the right in
693+
// processRouteLoaderData
694+
//
695+
type PendingActionResult =
696+
| [string, SuccessResult | ErrorResult]
697+
| [string, SuccessResult | ErrorResult, string];
690698

691699
interface HandleActionResult extends ShortCircuitable {
692700
/**
@@ -1858,7 +1866,11 @@ export function createRouter(init: RouterInit): Router {
18581866

18591867
return {
18601868
matches,
1861-
pendingActionResult: [boundaryMatch.route.id, result],
1869+
pendingActionResult: [
1870+
boundaryMatch.route.id,
1871+
result,
1872+
actionMatch.route.id,
1873+
],
18621874
};
18631875
}
18641876

@@ -6047,11 +6059,13 @@ function processRouteLoaderData(
60476059
});
60486060

60496061
// If we didn't consume the pending action error (i.e., all loaders
6050-
// resolved), then consume it here. Also clear out any loaderData for the
6051-
// throwing route
6062+
// resolved), then consume it here
60526063
if (pendingError !== undefined && pendingActionResult) {
60536064
errors = { [pendingActionResult[0]]: pendingError };
6054-
loaderData[pendingActionResult[0]] = undefined;
6065+
// Clear out any loaderData for the throwing route
6066+
if (pendingActionResult[2]) {
6067+
loaderData[pendingActionResult[2]] = undefined;
6068+
}
60556069
}
60566070

60576071
return {

0 commit comments

Comments
 (0)