Skip to content

Commit 3aee660

Browse files
authored
Properly handle data() values returned/thrown from resource routes (#14159)
1 parent ca27e99 commit 3aee660

File tree

3 files changed

+86
-6
lines changed

3 files changed

+86
-6
lines changed

.changeset/rotten-steaks-perform.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+
Properly handle data() values returned or thrown from resource routes and return corresponding responses with the data, status, and headers

integration/resource-routes-test.ts

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,12 +85,38 @@ test.describe("loader in an app", async () => {
8585
`,
8686
"app/routes/return-response.tsx": js`
8787
export let loader = () => {
88-
return new Response('Partial', { status: 207 });
88+
return new Response('Partial', { status: 207, headers: { 'X-Foo': 'Bar'} });
8989
}
9090
`,
9191
"app/routes/throw-response.tsx": js`
9292
export let loader = () => {
93-
throw new Response('Partial', { status: 207 });
93+
throw new Response('Partial', { status: 207, headers: { 'X-Foo': 'Bar' } });
94+
}
95+
`,
96+
"app/routes/return-data.tsx": js`
97+
import { data } from "react-router";
98+
export let loader = () => {
99+
return data('Partial', { status: 207, headers: { 'X-Foo': 'Bar'} });
100+
}
101+
`,
102+
"app/routes/throw-data.tsx": js`
103+
import { data } from "react-router";
104+
export let loader = () => {
105+
throw data('Partial', { status: 207, headers: { 'X-Foo': 'Bar'} });
106+
}
107+
`,
108+
"app/routes/return-data-through-middleware.tsx": js`
109+
import { data } from "react-router";
110+
export const unstable_middleware = [(_, next) => next()]
111+
export let loader = () => {
112+
return data('Partial', { status: 207, headers: { 'X-Foo': 'Bar'} });
113+
}
114+
`,
115+
"app/routes/throw-data-through-middleware.tsx": js`
116+
import { data } from "react-router";
117+
export const unstable_middleware = [(_, next) => next()]
118+
export let loader = () => {
119+
throw data('Partial', { status: 207, headers: { 'X-Foo': 'Bar'} });
94120
}
95121
`,
96122
"app/routes/return-object.tsx": js`
@@ -196,6 +222,7 @@ test.describe("loader in an app", async () => {
196222
let app = new PlaywrightFixture(appFixture, page);
197223
let res = await app.goto("/return-response");
198224
expect(res.status()).toBe(207);
225+
expect(res.headers()["x-foo"]).toBe("Bar");
199226
expect(await res.text()).toEqual("Partial");
200227
});
201228

@@ -205,6 +232,45 @@ test.describe("loader in an app", async () => {
205232
let app = new PlaywrightFixture(appFixture, page);
206233
let res = await app.goto("/throw-response");
207234
expect(res.status()).toBe(207);
235+
expect(res.headers()["x-foo"]).toBe("Bar");
236+
expect(await res.text()).toEqual("Partial");
237+
});
238+
239+
test("should handle data() returned from resource routes", async ({
240+
page,
241+
}) => {
242+
let app = new PlaywrightFixture(appFixture, page);
243+
let res = await app.goto("/return-data");
244+
expect(res.status()).toBe(207);
245+
expect(res.headers()["x-foo"]).toBe("Bar");
246+
expect(await res.text()).toEqual("Partial");
247+
});
248+
249+
test("should handle data() thrown from resource routes", async ({ page }) => {
250+
let app = new PlaywrightFixture(appFixture, page);
251+
let res = await app.goto("/throw-data");
252+
expect(res.status()).toBe(207);
253+
expect(res.headers()["x-foo"]).toBe("Bar");
254+
expect(await res.text()).toEqual("Partial");
255+
});
256+
257+
test("should handle data() returned from resource routes through middleware", async ({
258+
page,
259+
}) => {
260+
let app = new PlaywrightFixture(appFixture, page);
261+
let res = await app.goto("/return-data-through-middleware");
262+
expect(res.status()).toBe(207);
263+
expect(res.headers()["x-foo"]).toBe("Bar");
264+
expect(await res.text()).toEqual("Partial");
265+
});
266+
267+
test("should handle data() thrown from resource routes through middleware", async ({
268+
page,
269+
}) => {
270+
let app = new PlaywrightFixture(appFixture, page);
271+
let res = await app.goto("/throw-data-through-middleware");
272+
expect(res.status()).toBe(207);
273+
expect(res.headers()["x-foo"]).toBe("Bar");
208274
expect(await res.text()).toEqual("Partial");
209275
});
210276

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

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4314,10 +4314,19 @@ export function createStaticHandler(
43144314
basename,
43154315
);
43164316
}
4317-
if (isResponse(result.result) && isRouteRequest) {
4318-
// For SSR single-route requests, we want to hand Responses back
4319-
// directly without unwrapping
4320-
throw result;
4317+
4318+
// For SSR single-route requests, we want to hand Responses back
4319+
// directly, as well as upgrade data() calls to Response instances
4320+
// (this allows utilities using data() and be shared between normal and
4321+
// resource routes).
4322+
if (isRouteRequest) {
4323+
if (isResponse(result.result)) {
4324+
throw result;
4325+
} else if (isDataWithResponseInit(result.result)) {
4326+
// Upgrade `data()` to `Response` so utilities using `data()` can be
4327+
// shared between resource and non-resource routes
4328+
throw dataWithResponseInitToResponse(result.result);
4329+
}
43214330
}
43224331

43234332
dataResults[match.route.id] =

0 commit comments

Comments
 (0)