Skip to content

Commit fb014ce

Browse files
authored
Remove middleware depth restrictions (#13172)
1 parent 8ed5e87 commit fb014ce

File tree

5 files changed

+61
-155
lines changed

5 files changed

+61
-155
lines changed

.changeset/popular-hats-love.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"react-router": patch
3+
---
4+
5+
[REMOVE] Remove middleware depth logic and always call middlware for all matches

integration/middleware-test.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -2016,7 +2016,7 @@ test.describe("Middleware", () => {
20162016
appFixture.close();
20172017
});
20182018

2019-
test("only calls middleware as deep as needed for granular data requests", async ({
2019+
test("still calls middleware for all matches on granular data requests", async ({
20202020
page,
20212021
}) => {
20222022
let fixture = await createFixture({
@@ -2101,7 +2101,7 @@ test.describe("Middleware", () => {
21012101

21022102
(await page.$('a[href="/a/b"]'))?.click();
21032103
await page.waitForSelector("[data-b]");
2104-
expect(await page.locator("[data-a]").textContent()).toBe("A: a");
2104+
expect(await page.locator("[data-a]").textContent()).toBe("A: a,b");
21052105
expect(await page.locator("[data-b]").textContent()).toBe("B: a,b");
21062106

21072107
appFixture.close();

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

+8
Original file line numberDiff line numberDiff line change
@@ -762,6 +762,10 @@ describe("context/middleware", () => {
762762
"parent action start",
763763
"child 1 start - throwing",
764764
"parent loader start",
765+
"child 1 loader start",
766+
"child 2 loader start",
767+
"child 2 loader end",
768+
"child 1 loader end",
765769
"parent loader end",
766770
]);
767771
expect(router.state.loaderData).toMatchInlineSnapshot(`
@@ -897,6 +901,10 @@ describe("context/middleware", () => {
897901
"child 2 start",
898902
"child 2 end - throwing",
899903
"parent loader start",
904+
"child 1 loader start",
905+
"child 2 loader start",
906+
"child 2 loader end",
907+
"child 1 loader end",
900908
"parent loader end",
901909
]);
902910
expect(router.state.loaderData).toEqual({

packages/react-router/lib/dom/ssr/single-fetch.tsx

+10-83
Original file line numberDiff line numberDiff line change
@@ -136,17 +136,6 @@ export function StreamTransfer({
136136
}
137137
}
138138

139-
function middlewareErrorHandler(
140-
e: MiddlewareError,
141-
keyedResults: Record<string, DataStrategyResult>
142-
) {
143-
// we caught an error running the middleware, copy that overtop any
144-
// non-error result for the route
145-
Object.assign(keyedResults, {
146-
[e.routeId]: { type: "error", result: e.error },
147-
});
148-
}
149-
150139
export function getSingleFetchDataStrategy(
151140
manifest: AssetsManifest,
152141
routeModules: RouteModules,
@@ -161,17 +150,9 @@ export function getSingleFetchDataStrategy(
161150
if (request.method !== "GET") {
162151
return runMiddlewarePipeline(
163152
args,
164-
matches.findIndex((m) => m.shouldLoad),
165153
false,
166-
async (keyedResults) => {
167-
let results = await singleFetchActionStrategy(
168-
request,
169-
matches,
170-
basename
171-
);
172-
Object.assign(keyedResults, results);
173-
},
174-
middlewareErrorHandler
154+
() => singleFetchActionStrategy(request, matches, basename),
155+
(e) => ({ [e.routeId]: { type: "error", result: e.error } })
175156
) as Promise<Record<string, DataStrategyResult>>;
176157
}
177158

@@ -216,24 +197,11 @@ export function getSingleFetchDataStrategy(
216197
!manifest.routes[m.route.id]?.hasClientLoader
217198
);
218199
if (!foundRevalidatingServerLoader) {
219-
// Skip single fetch and just call the loaders in parallel when this is
220-
// a SPA mode navigation
221-
let tailIdx = [...matches].reverse().findIndex((m) => m.shouldLoad);
222-
let lowestLoadingIndex = tailIdx < 0 ? 0 : matches.length - 1 - tailIdx;
223200
return runMiddlewarePipeline(
224201
args,
225-
lowestLoadingIndex,
226202
false,
227-
async (keyedResults) => {
228-
let results = await nonSsrStrategy(
229-
manifest,
230-
request,
231-
matches,
232-
basename
233-
);
234-
Object.assign(keyedResults, results);
235-
},
236-
middlewareErrorHandler
203+
() => nonSsrStrategy(manifest, request, matches, basename),
204+
(e) => ({ [e.routeId]: { type: "error", result: e.error } })
237205
) as Promise<Record<string, DataStrategyResult>>;
238206
}
239207
}
@@ -242,47 +210,27 @@ export function getSingleFetchDataStrategy(
242210
if (fetcherKey) {
243211
return runMiddlewarePipeline(
244212
args,
245-
matches.findIndex((m) => m.shouldLoad),
246213
false,
247-
async (keyedResults) => {
248-
let results = await singleFetchLoaderFetcherStrategy(
249-
request,
250-
matches,
251-
basename
252-
);
253-
Object.assign(keyedResults, results);
254-
},
255-
middlewareErrorHandler
214+
() => singleFetchLoaderFetcherStrategy(request, matches, basename),
215+
(e) => ({ [e.routeId]: { type: "error", result: e.error } })
256216
) as Promise<Record<string, DataStrategyResult>>;
257217
}
258218

259219
// Navigational loads are more complex...
260-
261-
// Determine how deep to run middleware
262-
let lowestLoadingIndex = getLowestLoadingIndex(
263-
manifest,
264-
routeModules,
265-
getRouter(),
266-
matches
267-
);
268-
269220
return runMiddlewarePipeline(
270221
args,
271-
lowestLoadingIndex,
272222
false,
273-
async (keyedResults) => {
274-
let results = await singleFetchLoaderNavigationStrategy(
223+
() =>
224+
singleFetchLoaderNavigationStrategy(
275225
manifest,
276226
routeModules,
277227
ssr,
278228
getRouter(),
279229
request,
280230
matches,
281231
basename
282-
);
283-
Object.assign(keyedResults, results);
284-
},
285-
middlewareErrorHandler
232+
),
233+
(e) => ({ [e.routeId]: { type: "error", result: e.error } })
286234
) as Promise<Record<string, DataStrategyResult>>;
287235
};
288236
}
@@ -371,27 +319,6 @@ function isOptedOut(
371319
);
372320
}
373321

374-
function getLowestLoadingIndex(
375-
manifest: AssetsManifest,
376-
routeModules: RouteModules,
377-
router: DataRouter,
378-
matches: DataStrategyFunctionArgs["matches"]
379-
) {
380-
let tailIdx = [...matches]
381-
.reverse()
382-
.findIndex(
383-
(m) =>
384-
m.shouldLoad ||
385-
!isOptedOut(
386-
manifest.routes[m.route.id],
387-
routeModules[m.route.id],
388-
m,
389-
router
390-
)
391-
);
392-
return tailIdx < 0 ? 0 : matches.length - 1 - tailIdx;
393-
}
394-
395322
// Loaders are trickier since we only want to hit the server once, so we
396323
// create a singular promise for all server-loader routes to latch onto.
397324
async function singleFetchLoaderNavigationStrategy(

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

+36-70
Original file line numberDiff line numberDiff line change
@@ -3490,12 +3490,6 @@ export function createStaticHandler(
34903490
"`requestContext` must bean instance of `unstable_RouterContextProvider`"
34913491
);
34923492
try {
3493-
// Run middleware as far deep as the deepest loader to be executed
3494-
let tailIdx = [...matches]
3495-
.reverse()
3496-
.findIndex((m) => !filterMatchesToLoad || filterMatchesToLoad(m));
3497-
let lowestLoadingIdx = tailIdx < 0 ? 0 : matches.length - 1 - tailIdx;
3498-
34993493
let renderedStaticContext: StaticHandlerContext | undefined;
35003494
let response = await runMiddlewarePipeline(
35013495
{
@@ -3506,7 +3500,6 @@ export function createStaticHandler(
35063500
// this to the proper type knowing it's not an `AppLoadContext`
35073501
context: requestContext as unstable_RouterContextProvider,
35083502
},
3509-
lowestLoadingIdx,
35103503
true,
35113504
async () => {
35123505
let result = await queryImpl(
@@ -3700,7 +3693,6 @@ export function createStaticHandler(
37003693
// this to the proper type knowing it's not an `AppLoadContext`
37013694
context: requestContext as unstable_RouterContextProvider,
37023695
},
3703-
matches.length - 1,
37043696
true,
37053697
async () => {
37063698
let result = await queryImpl(
@@ -4940,91 +4932,65 @@ async function defaultDataStrategyWithMiddleware(
49404932
return defaultDataStrategy(args);
49414933
}
49424934

4943-
// Determine how far down we'll be loading so we only run middleware to that
4944-
// point. This prevents us from calling middleware below an action error
4945-
// boundary below which we don't run loaders
4946-
let lastIndex = args.matches.length - 1;
4947-
for (let i = lastIndex; i >= 0; i--) {
4948-
if (args.matches[i].shouldLoad) {
4949-
lastIndex = i;
4950-
break;
4951-
}
4952-
}
4953-
4954-
let results = await runMiddlewarePipeline(
4935+
return runMiddlewarePipeline(
49554936
args,
4956-
lastIndex,
49574937
false,
4958-
async (keyedResults: Record<string, DataStrategyResult>) => {
4959-
Object.assign(keyedResults, await defaultDataStrategy(args));
4960-
},
4961-
(e, keyedResults) => {
4962-
// we caught an error running the middleware, copy that overtop any
4963-
// non-error result for the route
4964-
Object.assign(keyedResults, {
4965-
[e.routeId]: { type: "error", result: e.error },
4966-
});
4967-
}
4968-
);
4969-
return results as Record<string, DataStrategyResult>;
4938+
() => defaultDataStrategy(args),
4939+
(e) => ({ [e.routeId]: { type: "error", result: e.error } })
4940+
) as Promise<Record<string, DataStrategyResult>>;
49704941
}
49714942

49724943
type MutableMiddlewareState = {
4973-
keyedResults: Record<string, DataStrategyResult>;
4944+
handlerResult: unknown;
49744945
propagateResult: boolean;
49754946
};
49764947

4977-
export async function runMiddlewarePipeline(
4978-
{
4979-
request,
4980-
params,
4981-
context,
4982-
matches,
4983-
}: (
4948+
export async function runMiddlewarePipeline<T extends boolean>(
4949+
args: (
49844950
| LoaderFunctionArgs<unstable_RouterContextProvider>
49854951
| ActionFunctionArgs<unstable_RouterContextProvider>
49864952
) & {
4953+
// Don't use `DataStrategyFunctionArgs` directly so we can we reduce these
4954+
// back from `DataStrategyMatch` to regular matches for use in the staticHandler
49874955
matches: AgnosticDataRouteMatch[];
49884956
},
4989-
lastIndex: number,
4990-
propagateResult: boolean,
4991-
handler: (results: Record<string, DataStrategyResult>) => unknown,
4992-
errorHandler: (
4993-
error: MiddlewareError,
4994-
results: Record<string, DataStrategyResult>
4995-
) => unknown
4957+
propagateResult: T,
4958+
handler: () => T extends true
4959+
? MaybePromise<Response>
4960+
: MaybePromise<Record<string, DataStrategyResult>>,
4961+
errorHandler: (error: MiddlewareError) => unknown
49964962
): Promise<unknown> {
4963+
let { matches, request, params, context } = args;
49974964
let middlewareState: MutableMiddlewareState = {
4998-
keyedResults: {},
4965+
handlerResult: undefined,
49994966
propagateResult,
50004967
};
50014968
try {
4969+
let tuples = matches.flatMap((m) =>
4970+
m.route.unstable_middleware
4971+
? m.route.unstable_middleware.map((fn) => [m.route.id, fn])
4972+
: []
4973+
) as [string, unstable_MiddlewareFunction][];
50024974
let result = await callRouteMiddleware(
5003-
matches
5004-
.slice(0, lastIndex + 1)
5005-
.flatMap((m) =>
5006-
m.route.unstable_middleware
5007-
? m.route.unstable_middleware.map((fn) => [m.route.id, fn])
5008-
: []
5009-
) as [string, unstable_MiddlewareFunction][],
5010-
0,
50114975
{ request, params, context },
4976+
tuples,
50124977
middlewareState,
50134978
handler
50144979
);
50154980
return middlewareState.propagateResult
50164981
? result
5017-
: middlewareState.keyedResults;
4982+
: middlewareState.handlerResult;
50184983
} catch (e) {
50194984
if (!(e instanceof MiddlewareError)) {
50204985
// This shouldn't happen? This would have to come from a bug in our
50214986
// library code...
50224987
throw e;
50234988
}
5024-
let result = await errorHandler(e, middlewareState.keyedResults);
5025-
return middlewareState.propagateResult
5026-
? result
5027-
: middlewareState.keyedResults;
4989+
let result = await errorHandler(e);
4990+
if (propagateResult || !middlewareState.handlerResult) {
4991+
return result;
4992+
}
4993+
return Object.assign(middlewareState.handlerResult, result);
50284994
}
50294995
}
50304996

@@ -5038,13 +5004,13 @@ export class MiddlewareError {
50385004
}
50395005

50405006
async function callRouteMiddleware(
5041-
middlewares: [string, unstable_MiddlewareFunction][],
5042-
idx: number,
50435007
args:
50445008
| LoaderFunctionArgs<unstable_RouterContextProvider>
50455009
| ActionFunctionArgs<unstable_RouterContextProvider>,
5010+
middlewares: [string, unstable_MiddlewareFunction][],
50465011
middlewareState: MutableMiddlewareState,
5047-
handler: (r: Record<string, DataStrategyResult>) => void
5012+
handler: () => void,
5013+
idx = 0
50485014
): Promise<unknown> {
50495015
let { request } = args;
50505016
if (request.signal.aborted) {
@@ -5059,8 +5025,8 @@ async function callRouteMiddleware(
50595025
let tuple = middlewares[idx];
50605026
if (!tuple) {
50615027
// We reached the end of our middlewares, call the handler
5062-
let result = await handler(middlewareState.keyedResults);
5063-
return result;
5028+
middlewareState.handlerResult = await handler();
5029+
return middlewareState.handlerResult;
50645030
}
50655031

50665032
let [routeId, middleware] = tuple;
@@ -5072,11 +5038,11 @@ async function callRouteMiddleware(
50725038
}
50735039
nextCalled = true;
50745040
let result = await callRouteMiddleware(
5075-
middlewares,
5076-
idx + 1,
50775041
args,
5042+
middlewares,
50785043
middlewareState,
5079-
handler
5044+
handler,
5045+
idx + 1
50805046
);
50815047
if (middlewareState.propagateResult) {
50825048
nextResult = result;

0 commit comments

Comments
 (0)