Skip to content

Commit fbe3516

Browse files
committed
Fix redirects returned from loader using data
1 parent d1bb894 commit fbe3516

File tree

5 files changed

+35
-50
lines changed

5 files changed

+35
-50
lines changed

.changeset/purple-poems-laugh.md

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"react-router": patch
3+
---
4+
5+
- Fix redirects returned from loaders using `data()`
6+
- Allow returning `undefined` from `loader` and `action` functions

integration/defer-loader-test.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,9 @@ test.describe("deferred loaders", () => {
4040
}
4141
);
4242
}
43-
export default function Redirect() {return null;}
43+
export default function Redirect() {
44+
return null;
45+
}
4446
`,
4547

4648
"app/routes/direct-promise-access.tsx": js`
@@ -82,17 +84,15 @@ test.describe("deferred loaders", () => {
8284

8385
test.afterAll(async () => appFixture.close());
8486

85-
test.skip("deferred response can redirect on document request", async ({
87+
test("deferred response can redirect on document request", async ({
8688
page,
8789
}) => {
8890
let app = new PlaywrightFixture(appFixture, page);
8991
await app.goto("/redirect");
9092
await page.waitForURL(/\?redirected/);
9193
});
9294

93-
test.skip("deferred response can redirect on transition", async ({
94-
page,
95-
}) => {
95+
test("deferred response can redirect on transition", async ({ page }) => {
9696
let app = new PlaywrightFixture(appFixture, page);
9797
await app.goto("/");
9898
await app.clickLink("/redirect");

packages/react-router/lib/server-runtime/data.ts

+17-38
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { isDataWithResponseInit } from "../router/router";
2+
import { isRedirectStatusCode } from "./responses";
13
import type {
24
ActionFunction,
35
ActionFunctionArgs,
@@ -20,60 +22,37 @@ export interface AppLoadContext {
2022
*/
2123
export type AppData = unknown;
2224

23-
export async function callRouteAction({
24-
loadContext,
25-
action,
26-
params,
27-
request,
28-
routeId,
29-
}: {
30-
request: Request;
31-
action: ActionFunction;
32-
params: ActionFunctionArgs["params"];
33-
loadContext: AppLoadContext;
34-
routeId: string;
35-
}) {
36-
let result = await action({
37-
request: stripRoutesParam(stripIndexParam(request)),
38-
context: loadContext,
39-
params,
40-
});
41-
42-
if (result === undefined) {
43-
throw new Error(
44-
`You defined an action for route "${routeId}" but didn't return ` +
45-
`anything from your \`action\` function. Please return a value or \`null\`.`
25+
function checkRedirect(result: ReturnType<LoaderFunction | ActionFunction>) {
26+
if (
27+
isDataWithResponseInit(result) &&
28+
result.init &&
29+
isRedirectStatusCode(result.init.status || 200)
30+
) {
31+
throw new Response(
32+
new Headers(result.init.headers).get("Location")!,
33+
result.init
4634
);
4735
}
48-
49-
return result;
5036
}
5137

52-
export async function callRouteLoader({
38+
export async function callRouteHandler({
5339
loadContext,
54-
loader,
40+
handler,
5541
params,
5642
request,
57-
routeId,
5843
}: {
5944
request: Request;
60-
loader: LoaderFunction;
61-
params: LoaderFunctionArgs["params"];
45+
handler: LoaderFunction | ActionFunction;
46+
params: LoaderFunctionArgs["params"] | ActionFunctionArgs["params"];
6247
loadContext: AppLoadContext;
63-
routeId: string;
6448
}) {
65-
let result = await loader({
49+
let result = await handler({
6650
request: stripRoutesParam(stripIndexParam(request)),
6751
context: loadContext,
6852
params,
6953
});
7054

71-
if (result === undefined) {
72-
throw new Error(
73-
`You defined a loader for route "${routeId}" but didn't return ` +
74-
`anything from your \`loader\` function. Please return a value or \`null\`.`
75-
);
76-
}
55+
checkRedirect(result);
7756

7857
return result;
7958
}

packages/react-router/lib/server-runtime/routes.ts

+5-7
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type {
33
LoaderFunctionArgs as RRLoaderFunctionArgs,
44
ActionFunctionArgs as RRActionFunctionArgs,
55
} from "../router/utils";
6-
import { callRouteAction, callRouteLoader } from "./data";
6+
import { callRouteHandler } from "./data";
77
import type { FutureConfig } from "../dom/ssr/entry";
88
import type { ServerRouteModule } from "./routeModules";
99

@@ -92,22 +92,20 @@ export function createStaticHandlerDataRoutes(
9292
? // Need to use RR's version here to permit the optional context even
9393
// though we know it'll always be provided in remix
9494
(args: RRLoaderFunctionArgs, dataStrategyCtx?: unknown) =>
95-
callRouteLoader({
95+
callRouteHandler({
9696
request: args.request,
9797
params: args.params,
9898
loadContext: args.context,
99-
loader: route.module.loader!,
100-
routeId: route.id,
99+
handler: route.module.loader!,
101100
})
102101
: undefined,
103102
action: route.module.action
104103
? (args: RRActionFunctionArgs, dataStrategyCtx?: unknown) =>
105-
callRouteAction({
104+
callRouteHandler({
106105
request: args.request,
107106
params: args.params,
108107
loadContext: args.context,
109-
action: route.module.action!,
110-
routeId: route.id,
108+
handler: route.module.action!,
111109
})
112110
: undefined,
113111
handle: route.module.handle,

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

+2
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,8 @@ async function handleDocumentRequest(
331331
handleError: (err: unknown) => void,
332332
criticalCss?: string
333333
) {
334+
debugger;
335+
334336
let context;
335337
try {
336338
context = await staticHandler.query(request, {

0 commit comments

Comments
 (0)