@remix-run/router 1.15.3 → 1.16.0-pre.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -0
- package/dist/index.d.ts +1 -1
- package/dist/router.cjs.js +396 -247
- package/dist/router.cjs.js.map +1 -1
- package/dist/router.d.ts +5 -1
- package/dist/router.js +382 -235
- package/dist/router.js.map +1 -1
- package/dist/router.umd.js +396 -247
- package/dist/router.umd.js.map +1 -1
- package/dist/router.umd.min.js +2 -2
- package/dist/router.umd.min.js.map +1 -1
- package/dist/utils.d.ts +28 -10
- package/index.ts +4 -0
- package/package.json +2 -2
- package/router.ts +677 -354
- package/utils.ts +46 -14
package/dist/router.cjs.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @remix-run/router v1.
|
|
2
|
+
* @remix-run/router v1.16.0-pre.1
|
|
3
3
|
*
|
|
4
4
|
* Copyright (c) Remix Software Inc.
|
|
5
5
|
*
|
|
@@ -577,6 +577,10 @@ let ResultType = /*#__PURE__*/function (ResultType) {
|
|
|
577
577
|
* Result from a loader or action - potentially successful or unsuccessful
|
|
578
578
|
*/
|
|
579
579
|
|
|
580
|
+
/**
|
|
581
|
+
* Result from a loader or action called via dataStrategy
|
|
582
|
+
*/
|
|
583
|
+
|
|
580
584
|
/**
|
|
581
585
|
* Users can specify either lowercase or uppercase form methods on `<Form>`,
|
|
582
586
|
* useSubmit(), `<fetcher.Form>`, etc.
|
|
@@ -1593,11 +1597,6 @@ function isRouteErrorResponse(error) {
|
|
|
1593
1597
|
/**
|
|
1594
1598
|
* Identified fetcher.load() calls that need to be revalidated
|
|
1595
1599
|
*/
|
|
1596
|
-
/**
|
|
1597
|
-
* Wrapper object to allow us to throw any response out from callLoaderOrAction
|
|
1598
|
-
* for queryRouter while preserving whether or not it was thrown or returned
|
|
1599
|
-
* from the loader/action
|
|
1600
|
-
*/
|
|
1601
1600
|
const validMutationMethodsArr = ["post", "put", "patch", "delete"];
|
|
1602
1601
|
const validMutationMethods = new Set(validMutationMethodsArr);
|
|
1603
1602
|
const validRequestMethodsArr = ["get", ...validMutationMethodsArr];
|
|
@@ -1669,13 +1668,15 @@ function createRouter(init) {
|
|
|
1669
1668
|
let dataRoutes = convertRoutesToDataRoutes(init.routes, mapRouteProperties, undefined, manifest);
|
|
1670
1669
|
let inFlightDataRoutes;
|
|
1671
1670
|
let basename = init.basename || "/";
|
|
1671
|
+
let dataStrategyImpl = init.unstable_dataStrategy || defaultDataStrategy;
|
|
1672
1672
|
// Config driven behavior flags
|
|
1673
1673
|
let future = _extends({
|
|
1674
1674
|
v7_fetcherPersist: false,
|
|
1675
1675
|
v7_normalizeFormMethod: false,
|
|
1676
1676
|
v7_partialHydration: false,
|
|
1677
1677
|
v7_prependBasename: false,
|
|
1678
|
-
v7_relativeSplatPath: false
|
|
1678
|
+
v7_relativeSplatPath: false,
|
|
1679
|
+
unstable_skipActionErrorRevalidation: false
|
|
1679
1680
|
}, init.future);
|
|
1680
1681
|
// Cleanup function for history
|
|
1681
1682
|
let unlistenHistory = null;
|
|
@@ -1729,9 +1730,13 @@ function createRouter(init) {
|
|
|
1729
1730
|
let errors = init.hydrationData ? init.hydrationData.errors : null;
|
|
1730
1731
|
let isRouteInitialized = m => {
|
|
1731
1732
|
// No loader, nothing to initialize
|
|
1732
|
-
if (!m.route.loader)
|
|
1733
|
+
if (!m.route.loader) {
|
|
1734
|
+
return true;
|
|
1735
|
+
}
|
|
1733
1736
|
// Explicitly opting-in to running on hydration
|
|
1734
|
-
if (m.route.loader.hydrate === true)
|
|
1737
|
+
if (typeof m.route.loader === "function" && m.route.loader.hydrate === true) {
|
|
1738
|
+
return false;
|
|
1739
|
+
}
|
|
1735
1740
|
// Otherwise, initialized if hydrated with data or an error
|
|
1736
1741
|
return loaderData && loaderData[m.route.id] !== undefined || errors && errors[m.route.id] !== undefined;
|
|
1737
1742
|
};
|
|
@@ -2272,34 +2277,31 @@ function createRouter(init) {
|
|
|
2272
2277
|
// Create a controller/Request for this navigation
|
|
2273
2278
|
pendingNavigationController = new AbortController();
|
|
2274
2279
|
let request = createClientSideRequest(init.history, location, pendingNavigationController.signal, opts && opts.submission);
|
|
2275
|
-
let
|
|
2276
|
-
let pendingError;
|
|
2280
|
+
let pendingActionResult;
|
|
2277
2281
|
if (opts && opts.pendingError) {
|
|
2278
2282
|
// If we have a pendingError, it means the user attempted a GET submission
|
|
2279
2283
|
// with binary FormData so assign here and skip to handleLoaders. That
|
|
2280
2284
|
// way we handle calling loaders above the boundary etc. It's not really
|
|
2281
2285
|
// different from an actionError in that sense.
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2286
|
+
pendingActionResult = [findNearestBoundary(matches).route.id, {
|
|
2287
|
+
type: ResultType.error,
|
|
2288
|
+
error: opts.pendingError
|
|
2289
|
+
}];
|
|
2285
2290
|
} else if (opts && opts.submission && isMutationMethod(opts.submission.formMethod)) {
|
|
2286
2291
|
// Call action if we received an action submission
|
|
2287
|
-
let
|
|
2292
|
+
let actionResult = await handleAction(request, location, opts.submission, matches, {
|
|
2288
2293
|
replace: opts.replace,
|
|
2289
2294
|
flushSync
|
|
2290
2295
|
});
|
|
2291
|
-
if (
|
|
2296
|
+
if (actionResult.shortCircuited) {
|
|
2292
2297
|
return;
|
|
2293
2298
|
}
|
|
2294
|
-
|
|
2295
|
-
pendingError = actionOutput.pendingActionError;
|
|
2299
|
+
pendingActionResult = actionResult.pendingActionResult;
|
|
2296
2300
|
loadingNavigation = getLoadingNavigation(location, opts.submission);
|
|
2297
2301
|
flushSync = false;
|
|
2298
2302
|
|
|
2299
2303
|
// Create a GET request for the loaders
|
|
2300
|
-
request =
|
|
2301
|
-
signal: request.signal
|
|
2302
|
-
});
|
|
2304
|
+
request = createClientSideRequest(init.history, request.url, request.signal);
|
|
2303
2305
|
}
|
|
2304
2306
|
|
|
2305
2307
|
// Call loaders
|
|
@@ -2307,7 +2309,7 @@ function createRouter(init) {
|
|
|
2307
2309
|
shortCircuited,
|
|
2308
2310
|
loaderData,
|
|
2309
2311
|
errors
|
|
2310
|
-
} = await handleLoaders(request, location, matches, loadingNavigation, opts && opts.submission, opts && opts.fetcherSubmission, opts && opts.replace, opts && opts.initialHydration === true, flushSync,
|
|
2312
|
+
} = await handleLoaders(request, location, matches, loadingNavigation, opts && opts.submission, opts && opts.fetcherSubmission, opts && opts.replace, opts && opts.initialHydration === true, flushSync, pendingActionResult);
|
|
2311
2313
|
if (shortCircuited) {
|
|
2312
2314
|
return;
|
|
2313
2315
|
}
|
|
@@ -2318,9 +2320,7 @@ function createRouter(init) {
|
|
|
2318
2320
|
pendingNavigationController = null;
|
|
2319
2321
|
completeNavigation(location, _extends({
|
|
2320
2322
|
matches
|
|
2321
|
-
},
|
|
2322
|
-
actionData: pendingActionData
|
|
2323
|
-
} : {}, {
|
|
2323
|
+
}, getActionDataForCommit(pendingActionResult), {
|
|
2324
2324
|
loaderData,
|
|
2325
2325
|
errors
|
|
2326
2326
|
}));
|
|
@@ -2355,7 +2355,8 @@ function createRouter(init) {
|
|
|
2355
2355
|
})
|
|
2356
2356
|
};
|
|
2357
2357
|
} else {
|
|
2358
|
-
|
|
2358
|
+
let results = await callDataStrategy("action", request, [actionMatch], matches);
|
|
2359
|
+
result = results[0];
|
|
2359
2360
|
if (request.signal.aborted) {
|
|
2360
2361
|
return {
|
|
2361
2362
|
shortCircuited: true
|
|
@@ -2370,9 +2371,10 @@ function createRouter(init) {
|
|
|
2370
2371
|
// If the user didn't explicity indicate replace behavior, replace if
|
|
2371
2372
|
// we redirected to the exact same location we're currently at to avoid
|
|
2372
2373
|
// double back-buttons
|
|
2373
|
-
|
|
2374
|
+
let location = normalizeRedirectLocation(result.response.headers.get("Location"), new URL(request.url), basename);
|
|
2375
|
+
replace = location === state.location.pathname + state.location.search;
|
|
2374
2376
|
}
|
|
2375
|
-
await startRedirectNavigation(
|
|
2377
|
+
await startRedirectNavigation(request, result, {
|
|
2376
2378
|
submission,
|
|
2377
2379
|
replace
|
|
2378
2380
|
});
|
|
@@ -2380,6 +2382,11 @@ function createRouter(init) {
|
|
|
2380
2382
|
shortCircuited: true
|
|
2381
2383
|
};
|
|
2382
2384
|
}
|
|
2385
|
+
if (isDeferredResult(result)) {
|
|
2386
|
+
throw getInternalRouterError(400, {
|
|
2387
|
+
type: "defer-action"
|
|
2388
|
+
});
|
|
2389
|
+
}
|
|
2383
2390
|
if (isErrorResult(result)) {
|
|
2384
2391
|
// Store off the pending error - we use it to determine which loaders
|
|
2385
2392
|
// to call and will commit it when we complete the navigation
|
|
@@ -2393,28 +2400,17 @@ function createRouter(init) {
|
|
|
2393
2400
|
pendingAction = Action.Push;
|
|
2394
2401
|
}
|
|
2395
2402
|
return {
|
|
2396
|
-
|
|
2397
|
-
pendingActionData: {},
|
|
2398
|
-
pendingActionError: {
|
|
2399
|
-
[boundaryMatch.route.id]: result.error
|
|
2400
|
-
}
|
|
2403
|
+
pendingActionResult: [boundaryMatch.route.id, result]
|
|
2401
2404
|
};
|
|
2402
2405
|
}
|
|
2403
|
-
if (isDeferredResult(result)) {
|
|
2404
|
-
throw getInternalRouterError(400, {
|
|
2405
|
-
type: "defer-action"
|
|
2406
|
-
});
|
|
2407
|
-
}
|
|
2408
2406
|
return {
|
|
2409
|
-
|
|
2410
|
-
[actionMatch.route.id]: result.data
|
|
2411
|
-
}
|
|
2407
|
+
pendingActionResult: [actionMatch.route.id, result]
|
|
2412
2408
|
};
|
|
2413
2409
|
}
|
|
2414
2410
|
|
|
2415
2411
|
// Call all applicable loaders for the given matches, handling redirects,
|
|
2416
2412
|
// errors, etc.
|
|
2417
|
-
async function handleLoaders(request, location, matches, overrideNavigation, submission, fetcherSubmission, replace, initialHydration, flushSync,
|
|
2413
|
+
async function handleLoaders(request, location, matches, overrideNavigation, submission, fetcherSubmission, replace, initialHydration, flushSync, pendingActionResult) {
|
|
2418
2414
|
// Figure out the right navigation we want to use for data loading
|
|
2419
2415
|
let loadingNavigation = overrideNavigation || getLoadingNavigation(location, submission);
|
|
2420
2416
|
|
|
@@ -2422,7 +2418,7 @@ function createRouter(init) {
|
|
|
2422
2418
|
// we have it on the loading navigation so use that if available
|
|
2423
2419
|
let activeSubmission = submission || fetcherSubmission || getSubmissionFromNavigation(loadingNavigation);
|
|
2424
2420
|
let routesToUse = inFlightDataRoutes || dataRoutes;
|
|
2425
|
-
let [matchesToLoad, revalidatingFetchers] = getMatchesToLoad(init.history, state, matches, activeSubmission, location, future.v7_partialHydration && initialHydration === true, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, deletedFetchers, fetchLoadMatches, fetchRedirectIds, routesToUse, basename,
|
|
2421
|
+
let [matchesToLoad, revalidatingFetchers] = getMatchesToLoad(init.history, state, matches, activeSubmission, location, future.v7_partialHydration && initialHydration === true, future.unstable_skipActionErrorRevalidation, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, deletedFetchers, fetchLoadMatches, fetchRedirectIds, routesToUse, basename, pendingActionResult);
|
|
2426
2422
|
|
|
2427
2423
|
// Cancel pending deferreds for no-longer-matched routes or routes we're
|
|
2428
2424
|
// about to reload. Note that if this is an action reload we would have
|
|
@@ -2437,10 +2433,10 @@ function createRouter(init) {
|
|
|
2437
2433
|
matches,
|
|
2438
2434
|
loaderData: {},
|
|
2439
2435
|
// Commit pending error if we're short circuiting
|
|
2440
|
-
errors:
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
}
|
|
2436
|
+
errors: pendingActionResult && isErrorResult(pendingActionResult[1]) ? {
|
|
2437
|
+
[pendingActionResult[0]]: pendingActionResult[1].error
|
|
2438
|
+
} : null
|
|
2439
|
+
}, getActionDataForCommit(pendingActionResult), updatedFetchers ? {
|
|
2444
2440
|
fetchers: new Map(state.fetchers)
|
|
2445
2441
|
} : {}), {
|
|
2446
2442
|
flushSync
|
|
@@ -2462,12 +2458,24 @@ function createRouter(init) {
|
|
|
2462
2458
|
let revalidatingFetcher = getLoadingFetcher(undefined, fetcher ? fetcher.data : undefined);
|
|
2463
2459
|
state.fetchers.set(rf.key, revalidatingFetcher);
|
|
2464
2460
|
});
|
|
2465
|
-
let actionData
|
|
2461
|
+
let actionData;
|
|
2462
|
+
if (pendingActionResult && !isErrorResult(pendingActionResult[1])) {
|
|
2463
|
+
// This is cast to `any` currently because `RouteData`uses any and it
|
|
2464
|
+
// would be a breaking change to use any.
|
|
2465
|
+
// TODO: v7 - change `RouteData` to use `unknown` instead of `any`
|
|
2466
|
+
actionData = {
|
|
2467
|
+
[pendingActionResult[0]]: pendingActionResult[1].data
|
|
2468
|
+
};
|
|
2469
|
+
} else if (state.actionData) {
|
|
2470
|
+
if (Object.keys(state.actionData).length === 0) {
|
|
2471
|
+
actionData = null;
|
|
2472
|
+
} else {
|
|
2473
|
+
actionData = state.actionData;
|
|
2474
|
+
}
|
|
2475
|
+
}
|
|
2466
2476
|
updateState(_extends({
|
|
2467
2477
|
navigation: loadingNavigation
|
|
2468
|
-
}, actionData
|
|
2469
|
-
actionData: null
|
|
2470
|
-
} : {
|
|
2478
|
+
}, actionData !== undefined ? {
|
|
2471
2479
|
actionData
|
|
2472
2480
|
} : {}, revalidatingFetchers.length > 0 ? {
|
|
2473
2481
|
fetchers: new Map(state.fetchers)
|
|
@@ -2493,7 +2501,6 @@ function createRouter(init) {
|
|
|
2493
2501
|
pendingNavigationController.signal.addEventListener("abort", abortPendingFetchRevalidations);
|
|
2494
2502
|
}
|
|
2495
2503
|
let {
|
|
2496
|
-
results,
|
|
2497
2504
|
loaderResults,
|
|
2498
2505
|
fetcherResults
|
|
2499
2506
|
} = await callLoadersAndMaybeResolveData(state.matches, matches, matchesToLoad, revalidatingFetchers, request);
|
|
@@ -2512,7 +2519,7 @@ function createRouter(init) {
|
|
|
2512
2519
|
revalidatingFetchers.forEach(rf => fetchControllers.delete(rf.key));
|
|
2513
2520
|
|
|
2514
2521
|
// If any loaders returned a redirect Response, start a new REPLACE navigation
|
|
2515
|
-
let redirect = findRedirect(
|
|
2522
|
+
let redirect = findRedirect([...loaderResults, ...fetcherResults]);
|
|
2516
2523
|
if (redirect) {
|
|
2517
2524
|
if (redirect.idx >= matchesToLoad.length) {
|
|
2518
2525
|
// If this redirect came from a fetcher make sure we mark it in
|
|
@@ -2521,7 +2528,7 @@ function createRouter(init) {
|
|
|
2521
2528
|
let fetcherKey = revalidatingFetchers[redirect.idx - matchesToLoad.length].key;
|
|
2522
2529
|
fetchRedirectIds.add(fetcherKey);
|
|
2523
2530
|
}
|
|
2524
|
-
await startRedirectNavigation(
|
|
2531
|
+
await startRedirectNavigation(request, redirect.result, {
|
|
2525
2532
|
replace
|
|
2526
2533
|
});
|
|
2527
2534
|
return {
|
|
@@ -2533,7 +2540,7 @@ function createRouter(init) {
|
|
|
2533
2540
|
let {
|
|
2534
2541
|
loaderData,
|
|
2535
2542
|
errors
|
|
2536
|
-
} = processLoaderData(state, matches, matchesToLoad, loaderResults,
|
|
2543
|
+
} = processLoaderData(state, matches, matchesToLoad, loaderResults, pendingActionResult, revalidatingFetchers, fetcherResults, activeDeferreds);
|
|
2537
2544
|
|
|
2538
2545
|
// Wire up subscribers to update loaderData as promises settle
|
|
2539
2546
|
activeDeferreds.forEach((deferredData, routeId) => {
|
|
@@ -2643,7 +2650,8 @@ function createRouter(init) {
|
|
|
2643
2650
|
let fetchRequest = createClientSideRequest(init.history, path, abortController.signal, submission);
|
|
2644
2651
|
fetchControllers.set(key, abortController);
|
|
2645
2652
|
let originatingLoadId = incrementingLoadId;
|
|
2646
|
-
let
|
|
2653
|
+
let actionResults = await callDataStrategy("action", fetchRequest, [match], requestMatches);
|
|
2654
|
+
let actionResult = actionResults[0];
|
|
2647
2655
|
if (fetchRequest.signal.aborted) {
|
|
2648
2656
|
// We can delete this so long as we weren't aborted by our own fetcher
|
|
2649
2657
|
// re-submit which would have put _new_ controller is in fetchControllers
|
|
@@ -2675,7 +2683,7 @@ function createRouter(init) {
|
|
|
2675
2683
|
} else {
|
|
2676
2684
|
fetchRedirectIds.add(key);
|
|
2677
2685
|
updateFetcherState(key, getLoadingFetcher(submission));
|
|
2678
|
-
return startRedirectNavigation(
|
|
2686
|
+
return startRedirectNavigation(fetchRequest, actionResult, {
|
|
2679
2687
|
fetcherSubmission: submission
|
|
2680
2688
|
});
|
|
2681
2689
|
}
|
|
@@ -2704,10 +2712,7 @@ function createRouter(init) {
|
|
|
2704
2712
|
fetchReloadIds.set(key, loadId);
|
|
2705
2713
|
let loadFetcher = getLoadingFetcher(submission, actionResult.data);
|
|
2706
2714
|
state.fetchers.set(key, loadFetcher);
|
|
2707
|
-
let [matchesToLoad, revalidatingFetchers] = getMatchesToLoad(init.history, state, matches, submission, nextLocation, false, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, deletedFetchers, fetchLoadMatches, fetchRedirectIds, routesToUse, basename,
|
|
2708
|
-
[match.route.id]: actionResult.data
|
|
2709
|
-
}, undefined // No need to send through errors since we short circuit above
|
|
2710
|
-
);
|
|
2715
|
+
let [matchesToLoad, revalidatingFetchers] = getMatchesToLoad(init.history, state, matches, submission, nextLocation, false, future.unstable_skipActionErrorRevalidation, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, deletedFetchers, fetchLoadMatches, fetchRedirectIds, routesToUse, basename, [match.route.id, actionResult]);
|
|
2711
2716
|
|
|
2712
2717
|
// Put all revalidating fetchers into the loading state, except for the
|
|
2713
2718
|
// current fetcher which we want to keep in it's current loading state which
|
|
@@ -2730,7 +2735,6 @@ function createRouter(init) {
|
|
|
2730
2735
|
let abortPendingFetchRevalidations = () => revalidatingFetchers.forEach(rf => abortFetcher(rf.key));
|
|
2731
2736
|
abortController.signal.addEventListener("abort", abortPendingFetchRevalidations);
|
|
2732
2737
|
let {
|
|
2733
|
-
results,
|
|
2734
2738
|
loaderResults,
|
|
2735
2739
|
fetcherResults
|
|
2736
2740
|
} = await callLoadersAndMaybeResolveData(state.matches, matches, matchesToLoad, revalidatingFetchers, revalidationRequest);
|
|
@@ -2741,7 +2745,7 @@ function createRouter(init) {
|
|
|
2741
2745
|
fetchReloadIds.delete(key);
|
|
2742
2746
|
fetchControllers.delete(key);
|
|
2743
2747
|
revalidatingFetchers.forEach(r => fetchControllers.delete(r.key));
|
|
2744
|
-
let redirect = findRedirect(
|
|
2748
|
+
let redirect = findRedirect([...loaderResults, ...fetcherResults]);
|
|
2745
2749
|
if (redirect) {
|
|
2746
2750
|
if (redirect.idx >= matchesToLoad.length) {
|
|
2747
2751
|
// If this redirect came from a fetcher make sure we mark it in
|
|
@@ -2750,7 +2754,7 @@ function createRouter(init) {
|
|
|
2750
2754
|
let fetcherKey = revalidatingFetchers[redirect.idx - matchesToLoad.length].key;
|
|
2751
2755
|
fetchRedirectIds.add(fetcherKey);
|
|
2752
2756
|
}
|
|
2753
|
-
return startRedirectNavigation(
|
|
2757
|
+
return startRedirectNavigation(revalidationRequest, redirect.result);
|
|
2754
2758
|
}
|
|
2755
2759
|
|
|
2756
2760
|
// Process and commit output from loaders
|
|
@@ -2804,7 +2808,8 @@ function createRouter(init) {
|
|
|
2804
2808
|
let fetchRequest = createClientSideRequest(init.history, path, abortController.signal);
|
|
2805
2809
|
fetchControllers.set(key, abortController);
|
|
2806
2810
|
let originatingLoadId = incrementingLoadId;
|
|
2807
|
-
let
|
|
2811
|
+
let results = await callDataStrategy("loader", fetchRequest, [match], matches);
|
|
2812
|
+
let result = results[0];
|
|
2808
2813
|
|
|
2809
2814
|
// Deferred isn't supported for fetcher loads, await everything and treat it
|
|
2810
2815
|
// as a normal load. resolveDeferredData will return undefined if this
|
|
@@ -2839,7 +2844,7 @@ function createRouter(init) {
|
|
|
2839
2844
|
return;
|
|
2840
2845
|
} else {
|
|
2841
2846
|
fetchRedirectIds.add(key);
|
|
2842
|
-
await startRedirectNavigation(
|
|
2847
|
+
await startRedirectNavigation(fetchRequest, result);
|
|
2843
2848
|
return;
|
|
2844
2849
|
}
|
|
2845
2850
|
}
|
|
@@ -2874,26 +2879,28 @@ function createRouter(init) {
|
|
|
2874
2879
|
* actually touch history until we've processed redirects, so we just use
|
|
2875
2880
|
* the history action from the original navigation (PUSH or REPLACE).
|
|
2876
2881
|
*/
|
|
2877
|
-
async function startRedirectNavigation(
|
|
2882
|
+
async function startRedirectNavigation(request, redirect, _temp2) {
|
|
2878
2883
|
let {
|
|
2879
2884
|
submission,
|
|
2880
2885
|
fetcherSubmission,
|
|
2881
2886
|
replace
|
|
2882
2887
|
} = _temp2 === void 0 ? {} : _temp2;
|
|
2883
|
-
if (redirect.
|
|
2888
|
+
if (redirect.response.headers.has("X-Remix-Revalidate")) {
|
|
2884
2889
|
isRevalidationRequired = true;
|
|
2885
2890
|
}
|
|
2886
|
-
let
|
|
2891
|
+
let location = redirect.response.headers.get("Location");
|
|
2892
|
+
invariant(location, "Expected a Location header on the redirect Response");
|
|
2893
|
+
location = normalizeRedirectLocation(location, new URL(request.url), basename);
|
|
2894
|
+
let redirectLocation = createLocation(state.location, location, {
|
|
2887
2895
|
_isRedirect: true
|
|
2888
2896
|
});
|
|
2889
|
-
invariant(redirectLocation, "Expected a location on the redirect navigation");
|
|
2890
2897
|
if (isBrowser) {
|
|
2891
2898
|
let isDocumentReload = false;
|
|
2892
|
-
if (redirect.
|
|
2899
|
+
if (redirect.response.headers.has("X-Remix-Reload-Document")) {
|
|
2893
2900
|
// Hard reload if the response contained X-Remix-Reload-Document
|
|
2894
2901
|
isDocumentReload = true;
|
|
2895
|
-
} else if (ABSOLUTE_URL_REGEX.test(
|
|
2896
|
-
const url = init.history.createURL(
|
|
2902
|
+
} else if (ABSOLUTE_URL_REGEX.test(location)) {
|
|
2903
|
+
const url = init.history.createURL(location);
|
|
2897
2904
|
isDocumentReload =
|
|
2898
2905
|
// Hard reload if it's an absolute URL to a new origin
|
|
2899
2906
|
url.origin !== routerWindow.location.origin ||
|
|
@@ -2902,9 +2909,9 @@ function createRouter(init) {
|
|
|
2902
2909
|
}
|
|
2903
2910
|
if (isDocumentReload) {
|
|
2904
2911
|
if (replace) {
|
|
2905
|
-
routerWindow.location.replace(
|
|
2912
|
+
routerWindow.location.replace(location);
|
|
2906
2913
|
} else {
|
|
2907
|
-
routerWindow.location.assign(
|
|
2914
|
+
routerWindow.location.assign(location);
|
|
2908
2915
|
}
|
|
2909
2916
|
return;
|
|
2910
2917
|
}
|
|
@@ -2930,10 +2937,10 @@ function createRouter(init) {
|
|
|
2930
2937
|
// re-submit the GET/POST/PUT/PATCH/DELETE as a submission navigation to the
|
|
2931
2938
|
// redirected location
|
|
2932
2939
|
let activeSubmission = submission || fetcherSubmission;
|
|
2933
|
-
if (redirectPreserveMethodStatusCodes.has(redirect.status) && activeSubmission && isMutationMethod(activeSubmission.formMethod)) {
|
|
2940
|
+
if (redirectPreserveMethodStatusCodes.has(redirect.response.status) && activeSubmission && isMutationMethod(activeSubmission.formMethod)) {
|
|
2934
2941
|
await startNavigation(redirectHistoryAction, redirectLocation, {
|
|
2935
2942
|
submission: _extends({}, activeSubmission, {
|
|
2936
|
-
formAction:
|
|
2943
|
+
formAction: location
|
|
2937
2944
|
}),
|
|
2938
2945
|
// Preserve this flag across redirects
|
|
2939
2946
|
preventScrollReset: pendingPreventScrollReset
|
|
@@ -2951,28 +2958,47 @@ function createRouter(init) {
|
|
|
2951
2958
|
});
|
|
2952
2959
|
}
|
|
2953
2960
|
}
|
|
2961
|
+
|
|
2962
|
+
// Utility wrapper for calling dataStrategy client-side without having to
|
|
2963
|
+
// pass around the manifest, mapRouteProperties, etc.
|
|
2964
|
+
async function callDataStrategy(type, request, matchesToLoad, matches) {
|
|
2965
|
+
try {
|
|
2966
|
+
let results = await callDataStrategyImpl(dataStrategyImpl, type, request, matchesToLoad, matches, manifest, mapRouteProperties);
|
|
2967
|
+
return await Promise.all(results.map((result, i) => {
|
|
2968
|
+
if (isRedirectHandlerResult(result)) {
|
|
2969
|
+
let response = result.result;
|
|
2970
|
+
return {
|
|
2971
|
+
type: ResultType.redirect,
|
|
2972
|
+
response: normalizeRelativeRoutingRedirectResponse(response, request, matchesToLoad[i].route.id, matches, basename, future.v7_relativeSplatPath)
|
|
2973
|
+
};
|
|
2974
|
+
}
|
|
2975
|
+
return convertHandlerResultToDataResult(result);
|
|
2976
|
+
}));
|
|
2977
|
+
} catch (e) {
|
|
2978
|
+
// If the outer dataStrategy method throws, just return the error for all
|
|
2979
|
+
// matches - and it'll naturally bubble to the root
|
|
2980
|
+
return matchesToLoad.map(() => ({
|
|
2981
|
+
type: ResultType.error,
|
|
2982
|
+
error: e
|
|
2983
|
+
}));
|
|
2984
|
+
}
|
|
2985
|
+
}
|
|
2954
2986
|
async function callLoadersAndMaybeResolveData(currentMatches, matches, matchesToLoad, fetchersToLoad, request) {
|
|
2955
|
-
|
|
2956
|
-
// then slice off the results into separate arrays so we can handle them
|
|
2957
|
-
// accordingly
|
|
2958
|
-
let results = await Promise.all([...matchesToLoad.map(match => callLoaderOrAction("loader", request, match, matches, manifest, mapRouteProperties, basename, future.v7_relativeSplatPath)), ...fetchersToLoad.map(f => {
|
|
2987
|
+
let [loaderResults, ...fetcherResults] = await Promise.all([matchesToLoad.length ? callDataStrategy("loader", request, matchesToLoad, matches) : [], ...fetchersToLoad.map(f => {
|
|
2959
2988
|
if (f.matches && f.match && f.controller) {
|
|
2960
|
-
|
|
2989
|
+
let fetcherRequest = createClientSideRequest(init.history, f.path, f.controller.signal);
|
|
2990
|
+
return callDataStrategy("loader", fetcherRequest, [f.match], f.matches).then(r => r[0]);
|
|
2961
2991
|
} else {
|
|
2962
|
-
|
|
2992
|
+
return Promise.resolve({
|
|
2963
2993
|
type: ResultType.error,
|
|
2964
2994
|
error: getInternalRouterError(404, {
|
|
2965
2995
|
pathname: f.path
|
|
2966
2996
|
})
|
|
2967
|
-
};
|
|
2968
|
-
return error;
|
|
2997
|
+
});
|
|
2969
2998
|
}
|
|
2970
2999
|
})]);
|
|
2971
|
-
let loaderResults = results.slice(0, matchesToLoad.length);
|
|
2972
|
-
let fetcherResults = results.slice(matchesToLoad.length);
|
|
2973
3000
|
await Promise.all([resolveDeferredResults(currentMatches, matchesToLoad, loaderResults, loaderResults.map(() => request.signal), false, state.loaderData), resolveDeferredResults(currentMatches, fetchersToLoad.map(f => f.match), fetcherResults, fetchersToLoad.map(f => f.controller ? f.controller.signal : null), true)]);
|
|
2974
3001
|
return {
|
|
2975
|
-
results,
|
|
2976
3002
|
loaderResults,
|
|
2977
3003
|
fetcherResults
|
|
2978
3004
|
};
|
|
@@ -3322,10 +3348,19 @@ function createStaticHandler(routes, opts) {
|
|
|
3322
3348
|
* redirect response is returned or thrown from any action/loader. We
|
|
3323
3349
|
* propagate that out and return the raw Response so the HTTP server can
|
|
3324
3350
|
* return it directly.
|
|
3351
|
+
*
|
|
3352
|
+
* - `opts.requestContext` is an optional server context that will be passed
|
|
3353
|
+
* to actions/loaders in the `context` parameter
|
|
3354
|
+
* - `opts.skipLoaderErrorBubbling` is an optional parameter that will prevent
|
|
3355
|
+
* the bubbling of errors which allows single-fetch-type implementations
|
|
3356
|
+
* where the client will handle the bubbling and we may need to return data
|
|
3357
|
+
* for the handling route
|
|
3325
3358
|
*/
|
|
3326
3359
|
async function query(request, _temp3) {
|
|
3327
3360
|
let {
|
|
3328
|
-
requestContext
|
|
3361
|
+
requestContext,
|
|
3362
|
+
skipLoaderErrorBubbling,
|
|
3363
|
+
unstable_dataStrategy
|
|
3329
3364
|
} = _temp3 === void 0 ? {} : _temp3;
|
|
3330
3365
|
let url = new URL(request.url);
|
|
3331
3366
|
let method = request.method;
|
|
@@ -3378,7 +3413,7 @@ function createStaticHandler(routes, opts) {
|
|
|
3378
3413
|
activeDeferreds: null
|
|
3379
3414
|
};
|
|
3380
3415
|
}
|
|
3381
|
-
let result = await queryImpl(request, location, matches, requestContext);
|
|
3416
|
+
let result = await queryImpl(request, location, matches, requestContext, unstable_dataStrategy || null, skipLoaderErrorBubbling === true, null);
|
|
3382
3417
|
if (isResponse(result)) {
|
|
3383
3418
|
return result;
|
|
3384
3419
|
}
|
|
@@ -3411,6 +3446,12 @@ function createStaticHandler(routes, opts) {
|
|
|
3411
3446
|
* serialize the error as they see fit while including the proper response
|
|
3412
3447
|
* code. Examples here are 404 and 405 errors that occur prior to reaching
|
|
3413
3448
|
* any user-defined loaders.
|
|
3449
|
+
*
|
|
3450
|
+
* - `opts.routeId` allows you to specify the specific route handler to call.
|
|
3451
|
+
* If not provided the handler will determine the proper route by matching
|
|
3452
|
+
* against `request.url`
|
|
3453
|
+
* - `opts.requestContext` is an optional server context that will be passed
|
|
3454
|
+
* to actions/loaders in the `context` parameter
|
|
3414
3455
|
*/
|
|
3415
3456
|
async function queryRoute(request, _temp4) {
|
|
3416
3457
|
let {
|
|
@@ -3444,7 +3485,7 @@ function createStaticHandler(routes, opts) {
|
|
|
3444
3485
|
pathname: location.pathname
|
|
3445
3486
|
});
|
|
3446
3487
|
}
|
|
3447
|
-
let result = await queryImpl(request, location, matches, requestContext, match);
|
|
3488
|
+
let result = await queryImpl(request, location, matches, requestContext, null, false, match);
|
|
3448
3489
|
if (isResponse(result)) {
|
|
3449
3490
|
return result;
|
|
3450
3491
|
}
|
|
@@ -3471,27 +3512,27 @@ function createStaticHandler(routes, opts) {
|
|
|
3471
3512
|
}
|
|
3472
3513
|
return undefined;
|
|
3473
3514
|
}
|
|
3474
|
-
async function queryImpl(request, location, matches, requestContext, routeMatch) {
|
|
3515
|
+
async function queryImpl(request, location, matches, requestContext, unstable_dataStrategy, skipLoaderErrorBubbling, routeMatch) {
|
|
3475
3516
|
invariant(request.signal, "query()/queryRoute() requests must contain an AbortController signal");
|
|
3476
3517
|
try {
|
|
3477
3518
|
if (isMutationMethod(request.method.toLowerCase())) {
|
|
3478
|
-
let result = await submit(request, matches, routeMatch || getTargetMatch(matches, location), requestContext, routeMatch != null);
|
|
3519
|
+
let result = await submit(request, matches, routeMatch || getTargetMatch(matches, location), requestContext, unstable_dataStrategy, skipLoaderErrorBubbling, routeMatch != null);
|
|
3479
3520
|
return result;
|
|
3480
3521
|
}
|
|
3481
|
-
let result = await loadRouteData(request, matches, requestContext, routeMatch);
|
|
3522
|
+
let result = await loadRouteData(request, matches, requestContext, unstable_dataStrategy, skipLoaderErrorBubbling, routeMatch);
|
|
3482
3523
|
return isResponse(result) ? result : _extends({}, result, {
|
|
3483
3524
|
actionData: null,
|
|
3484
3525
|
actionHeaders: {}
|
|
3485
3526
|
});
|
|
3486
3527
|
} catch (e) {
|
|
3487
|
-
// If the user threw/returned a Response in callLoaderOrAction
|
|
3488
|
-
//
|
|
3489
|
-
//
|
|
3490
|
-
if (
|
|
3528
|
+
// If the user threw/returned a Response in callLoaderOrAction for a
|
|
3529
|
+
// `queryRoute` call, we throw the `HandlerResult` to bail out early
|
|
3530
|
+
// and then return or throw the raw Response here accordingly
|
|
3531
|
+
if (isHandlerResult(e) && isResponse(e.result)) {
|
|
3491
3532
|
if (e.type === ResultType.error) {
|
|
3492
|
-
throw e.
|
|
3533
|
+
throw e.result;
|
|
3493
3534
|
}
|
|
3494
|
-
return e.
|
|
3535
|
+
return e.result;
|
|
3495
3536
|
}
|
|
3496
3537
|
// Redirects are always returned since they don't propagate to catch
|
|
3497
3538
|
// boundaries
|
|
@@ -3501,7 +3542,7 @@ function createStaticHandler(routes, opts) {
|
|
|
3501
3542
|
throw e;
|
|
3502
3543
|
}
|
|
3503
3544
|
}
|
|
3504
|
-
async function submit(request, matches, actionMatch, requestContext, isRouteRequest) {
|
|
3545
|
+
async function submit(request, matches, actionMatch, requestContext, unstable_dataStrategy, skipLoaderErrorBubbling, isRouteRequest) {
|
|
3505
3546
|
let result;
|
|
3506
3547
|
if (!actionMatch.route.action && !actionMatch.route.lazy) {
|
|
3507
3548
|
let error = getInternalRouterError(405, {
|
|
@@ -3517,11 +3558,8 @@ function createStaticHandler(routes, opts) {
|
|
|
3517
3558
|
error
|
|
3518
3559
|
};
|
|
3519
3560
|
} else {
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
isRouteRequest,
|
|
3523
|
-
requestContext
|
|
3524
|
-
});
|
|
3561
|
+
let results = await callDataStrategy("action", request, [actionMatch], matches, isRouteRequest, requestContext, unstable_dataStrategy);
|
|
3562
|
+
result = results[0];
|
|
3525
3563
|
if (request.signal.aborted) {
|
|
3526
3564
|
throwStaticHandlerAbortedError(request, isRouteRequest, future);
|
|
3527
3565
|
}
|
|
@@ -3532,9 +3570,9 @@ function createStaticHandler(routes, opts) {
|
|
|
3532
3570
|
// can get back on the "throw all redirect responses" train here should
|
|
3533
3571
|
// this ever happen :/
|
|
3534
3572
|
throw new Response(null, {
|
|
3535
|
-
status: result.status,
|
|
3573
|
+
status: result.response.status,
|
|
3536
3574
|
headers: {
|
|
3537
|
-
Location: result.
|
|
3575
|
+
Location: result.response.headers.get("Location")
|
|
3538
3576
|
}
|
|
3539
3577
|
});
|
|
3540
3578
|
}
|
|
@@ -3571,43 +3609,42 @@ function createStaticHandler(routes, opts) {
|
|
|
3571
3609
|
activeDeferreds: null
|
|
3572
3610
|
};
|
|
3573
3611
|
}
|
|
3612
|
+
|
|
3613
|
+
// Create a GET request for the loaders
|
|
3614
|
+
let loaderRequest = new Request(request.url, {
|
|
3615
|
+
headers: request.headers,
|
|
3616
|
+
redirect: request.redirect,
|
|
3617
|
+
signal: request.signal
|
|
3618
|
+
});
|
|
3574
3619
|
if (isErrorResult(result)) {
|
|
3575
3620
|
// Store off the pending error - we use it to determine which loaders
|
|
3576
3621
|
// to call and will commit it when we complete the navigation
|
|
3577
|
-
let boundaryMatch = findNearestBoundary(matches, actionMatch.route.id);
|
|
3578
|
-
let context = await loadRouteData(
|
|
3579
|
-
[boundaryMatch.route.id]: result.error
|
|
3580
|
-
});
|
|
3622
|
+
let boundaryMatch = skipLoaderErrorBubbling ? actionMatch : findNearestBoundary(matches, actionMatch.route.id);
|
|
3623
|
+
let context = await loadRouteData(loaderRequest, matches, requestContext, unstable_dataStrategy, skipLoaderErrorBubbling, null, [boundaryMatch.route.id, result]);
|
|
3581
3624
|
|
|
3582
3625
|
// action status codes take precedence over loader status codes
|
|
3583
3626
|
return _extends({}, context, {
|
|
3584
|
-
statusCode: isRouteErrorResponse(result.error) ? result.error.status : 500,
|
|
3627
|
+
statusCode: isRouteErrorResponse(result.error) ? result.error.status : result.statusCode != null ? result.statusCode : 500,
|
|
3585
3628
|
actionData: null,
|
|
3586
3629
|
actionHeaders: _extends({}, result.headers ? {
|
|
3587
3630
|
[actionMatch.route.id]: result.headers
|
|
3588
3631
|
} : {})
|
|
3589
3632
|
});
|
|
3590
3633
|
}
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
let loaderRequest = new Request(request.url, {
|
|
3594
|
-
headers: request.headers,
|
|
3595
|
-
redirect: request.redirect,
|
|
3596
|
-
signal: request.signal
|
|
3597
|
-
});
|
|
3598
|
-
let context = await loadRouteData(loaderRequest, matches, requestContext);
|
|
3599
|
-
return _extends({}, context, result.statusCode ? {
|
|
3600
|
-
statusCode: result.statusCode
|
|
3601
|
-
} : {}, {
|
|
3634
|
+
let context = await loadRouteData(loaderRequest, matches, requestContext, unstable_dataStrategy, skipLoaderErrorBubbling, null);
|
|
3635
|
+
return _extends({}, context, {
|
|
3602
3636
|
actionData: {
|
|
3603
3637
|
[actionMatch.route.id]: result.data
|
|
3604
|
-
}
|
|
3605
|
-
|
|
3638
|
+
}
|
|
3639
|
+
}, result.statusCode ? {
|
|
3640
|
+
statusCode: result.statusCode
|
|
3641
|
+
} : {}, {
|
|
3642
|
+
actionHeaders: result.headers ? {
|
|
3606
3643
|
[actionMatch.route.id]: result.headers
|
|
3607
|
-
} : {}
|
|
3644
|
+
} : {}
|
|
3608
3645
|
});
|
|
3609
3646
|
}
|
|
3610
|
-
async function loadRouteData(request, matches, requestContext, routeMatch,
|
|
3647
|
+
async function loadRouteData(request, matches, requestContext, unstable_dataStrategy, skipLoaderErrorBubbling, routeMatch, pendingActionResult) {
|
|
3611
3648
|
let isRouteRequest = routeMatch != null;
|
|
3612
3649
|
|
|
3613
3650
|
// Short circuit if we have no loaders to run (queryRoute())
|
|
@@ -3618,7 +3655,7 @@ function createStaticHandler(routes, opts) {
|
|
|
3618
3655
|
routeId: routeMatch == null ? void 0 : routeMatch.route.id
|
|
3619
3656
|
});
|
|
3620
3657
|
}
|
|
3621
|
-
let requestMatches = routeMatch ? [routeMatch] : getLoaderMatchesUntilBoundary(matches,
|
|
3658
|
+
let requestMatches = routeMatch ? [routeMatch] : pendingActionResult && isErrorResult(pendingActionResult[1]) ? getLoaderMatchesUntilBoundary(matches, pendingActionResult[0]) : matches;
|
|
3622
3659
|
let matchesToLoad = requestMatches.filter(m => m.route.loader || m.route.lazy);
|
|
3623
3660
|
|
|
3624
3661
|
// Short circuit if we have no loaders to run (query())
|
|
@@ -3629,24 +3666,22 @@ function createStaticHandler(routes, opts) {
|
|
|
3629
3666
|
loaderData: matches.reduce((acc, m) => Object.assign(acc, {
|
|
3630
3667
|
[m.route.id]: null
|
|
3631
3668
|
}), {}),
|
|
3632
|
-
errors:
|
|
3669
|
+
errors: pendingActionResult && isErrorResult(pendingActionResult[1]) ? {
|
|
3670
|
+
[pendingActionResult[0]]: pendingActionResult[1].error
|
|
3671
|
+
} : null,
|
|
3633
3672
|
statusCode: 200,
|
|
3634
3673
|
loaderHeaders: {},
|
|
3635
3674
|
activeDeferreds: null
|
|
3636
3675
|
};
|
|
3637
3676
|
}
|
|
3638
|
-
let results = await
|
|
3639
|
-
isStaticRequest: true,
|
|
3640
|
-
isRouteRequest,
|
|
3641
|
-
requestContext
|
|
3642
|
-
}))]);
|
|
3677
|
+
let results = await callDataStrategy("loader", request, matchesToLoad, matches, isRouteRequest, requestContext, unstable_dataStrategy);
|
|
3643
3678
|
if (request.signal.aborted) {
|
|
3644
3679
|
throwStaticHandlerAbortedError(request, isRouteRequest, future);
|
|
3645
3680
|
}
|
|
3646
3681
|
|
|
3647
3682
|
// Process and commit output from loaders
|
|
3648
3683
|
let activeDeferreds = new Map();
|
|
3649
|
-
let context = processRouteLoaderData(matches, matchesToLoad, results,
|
|
3684
|
+
let context = processRouteLoaderData(matches, matchesToLoad, results, pendingActionResult, activeDeferreds, skipLoaderErrorBubbling);
|
|
3650
3685
|
|
|
3651
3686
|
// Add a null for any non-loader matches for proper revalidation on the client
|
|
3652
3687
|
let executedLoaders = new Set(matchesToLoad.map(match => match.route.id));
|
|
@@ -3660,6 +3695,25 @@ function createStaticHandler(routes, opts) {
|
|
|
3660
3695
|
activeDeferreds: activeDeferreds.size > 0 ? Object.fromEntries(activeDeferreds.entries()) : null
|
|
3661
3696
|
});
|
|
3662
3697
|
}
|
|
3698
|
+
|
|
3699
|
+
// Utility wrapper for calling dataStrategy server-side without having to
|
|
3700
|
+
// pass around the manifest, mapRouteProperties, etc.
|
|
3701
|
+
async function callDataStrategy(type, request, matchesToLoad, matches, isRouteRequest, requestContext, unstable_dataStrategy) {
|
|
3702
|
+
let results = await callDataStrategyImpl(unstable_dataStrategy || defaultDataStrategy, type, request, matchesToLoad, matches, manifest, mapRouteProperties, requestContext);
|
|
3703
|
+
return await Promise.all(results.map((result, i) => {
|
|
3704
|
+
if (isRedirectHandlerResult(result)) {
|
|
3705
|
+
let response = result.result;
|
|
3706
|
+
// Throw redirects and let the server handle them with an HTTP redirect
|
|
3707
|
+
throw normalizeRelativeRoutingRedirectResponse(response, request, matchesToLoad[i].route.id, matches, basename, future.v7_relativeSplatPath);
|
|
3708
|
+
}
|
|
3709
|
+
if (isResponse(result.result) && isRouteRequest) {
|
|
3710
|
+
// For SSR single-route requests, we want to hand Responses back
|
|
3711
|
+
// directly without unwrapping
|
|
3712
|
+
throw result;
|
|
3713
|
+
}
|
|
3714
|
+
return convertHandlerResultToDataResult(result);
|
|
3715
|
+
}));
|
|
3716
|
+
}
|
|
3663
3717
|
return {
|
|
3664
3718
|
dataRoutes,
|
|
3665
3719
|
query,
|
|
@@ -3880,14 +3934,20 @@ function getLoaderMatchesUntilBoundary(matches, boundaryId) {
|
|
|
3880
3934
|
}
|
|
3881
3935
|
return boundaryMatches;
|
|
3882
3936
|
}
|
|
3883
|
-
function getMatchesToLoad(history, state, matches, submission, location, isInitialLoad, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, deletedFetchers, fetchLoadMatches, fetchRedirectIds, routesToUse, basename,
|
|
3884
|
-
let actionResult =
|
|
3937
|
+
function getMatchesToLoad(history, state, matches, submission, location, isInitialLoad, skipActionErrorRevalidation, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, deletedFetchers, fetchLoadMatches, fetchRedirectIds, routesToUse, basename, pendingActionResult) {
|
|
3938
|
+
let actionResult = pendingActionResult ? isErrorResult(pendingActionResult[1]) ? pendingActionResult[1].error : pendingActionResult[1].data : undefined;
|
|
3885
3939
|
let currentUrl = history.createURL(state.location);
|
|
3886
3940
|
let nextUrl = history.createURL(location);
|
|
3887
3941
|
|
|
3888
3942
|
// Pick navigation matches that are net-new or qualify for revalidation
|
|
3889
|
-
let boundaryId =
|
|
3890
|
-
let boundaryMatches = getLoaderMatchesUntilBoundary(matches, boundaryId);
|
|
3943
|
+
let boundaryId = pendingActionResult && isErrorResult(pendingActionResult[1]) ? pendingActionResult[0] : undefined;
|
|
3944
|
+
let boundaryMatches = boundaryId ? getLoaderMatchesUntilBoundary(matches, boundaryId) : matches;
|
|
3945
|
+
|
|
3946
|
+
// Don't revalidate loaders by default after action 4xx/5xx responses
|
|
3947
|
+
// when the flag is enabled. They can still opt-into revalidation via
|
|
3948
|
+
// `shouldRevalidate` via `actionResult`
|
|
3949
|
+
let actionStatus = pendingActionResult ? pendingActionResult[1].statusCode : undefined;
|
|
3950
|
+
let shouldSkipRevalidation = skipActionErrorRevalidation && actionStatus && actionStatus >= 400;
|
|
3891
3951
|
let navigationMatches = boundaryMatches.filter((match, index) => {
|
|
3892
3952
|
let {
|
|
3893
3953
|
route
|
|
@@ -3900,7 +3960,7 @@ function getMatchesToLoad(history, state, matches, submission, location, isIniti
|
|
|
3900
3960
|
return false;
|
|
3901
3961
|
}
|
|
3902
3962
|
if (isInitialLoad) {
|
|
3903
|
-
if (route.loader.hydrate) {
|
|
3963
|
+
if (typeof route.loader !== "function" || route.loader.hydrate) {
|
|
3904
3964
|
return true;
|
|
3905
3965
|
}
|
|
3906
3966
|
return state.loaderData[route.id] === undefined && (
|
|
@@ -3926,11 +3986,10 @@ function getMatchesToLoad(history, state, matches, submission, location, isIniti
|
|
|
3926
3986
|
nextParams: nextRouteMatch.params
|
|
3927
3987
|
}, submission, {
|
|
3928
3988
|
actionResult,
|
|
3929
|
-
|
|
3989
|
+
unstable_actionStatus: actionStatus,
|
|
3990
|
+
defaultShouldRevalidate: shouldSkipRevalidation ? false :
|
|
3930
3991
|
// Forced revalidation due to submission, useRevalidator, or X-Remix-Revalidate
|
|
3931
|
-
isRevalidationRequired ||
|
|
3932
|
-
// Clicked the same link, resubmitted a GET form
|
|
3933
|
-
currentUrl.pathname + currentUrl.search === nextUrl.pathname + nextUrl.search ||
|
|
3992
|
+
isRevalidationRequired || currentUrl.pathname + currentUrl.search === nextUrl.pathname + nextUrl.search ||
|
|
3934
3993
|
// Search params affect all loaders
|
|
3935
3994
|
currentUrl.search !== nextUrl.search || isNewRouteInstance(currentRouteMatch, nextRouteMatch)
|
|
3936
3995
|
}));
|
|
@@ -3992,7 +4051,8 @@ function getMatchesToLoad(history, state, matches, submission, location, isIniti
|
|
|
3992
4051
|
nextParams: matches[matches.length - 1].params
|
|
3993
4052
|
}, submission, {
|
|
3994
4053
|
actionResult,
|
|
3995
|
-
|
|
4054
|
+
unstable_actionStatus: actionStatus,
|
|
4055
|
+
defaultShouldRevalidate: shouldSkipRevalidation ? false : isRevalidationRequired
|
|
3996
4056
|
}));
|
|
3997
4057
|
}
|
|
3998
4058
|
if (shouldRevalidate) {
|
|
@@ -4094,24 +4154,92 @@ async function loadLazyRouteModule(route, mapRouteProperties, manifest) {
|
|
|
4094
4154
|
lazy: undefined
|
|
4095
4155
|
}));
|
|
4096
4156
|
}
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4157
|
+
|
|
4158
|
+
// Default implementation of `dataStrategy` which fetches all loaders in parallel
|
|
4159
|
+
function defaultDataStrategy(opts) {
|
|
4160
|
+
return Promise.all(opts.matches.map(m => m.resolve()));
|
|
4161
|
+
}
|
|
4162
|
+
async function callDataStrategyImpl(dataStrategyImpl, type, request, matchesToLoad, matches, manifest, mapRouteProperties, requestContext) {
|
|
4163
|
+
let routeIdsToLoad = matchesToLoad.reduce((acc, m) => acc.add(m.route.id), new Set());
|
|
4164
|
+
let loadedMatches = new Set();
|
|
4165
|
+
|
|
4166
|
+
// Send all matches here to allow for a middleware-type implementation.
|
|
4167
|
+
// handler will be a no-op for unneeded routes and we filter those results
|
|
4168
|
+
// back out below.
|
|
4169
|
+
let results = await dataStrategyImpl({
|
|
4170
|
+
matches: matches.map(match => {
|
|
4171
|
+
let shouldLoad = routeIdsToLoad.has(match.route.id);
|
|
4172
|
+
// `resolve` encapsulates the route.lazy, executing the
|
|
4173
|
+
// loader/action, and mapping return values/thrown errors to a
|
|
4174
|
+
// HandlerResult. Users can pass a callback to take fine-grained control
|
|
4175
|
+
// over the execution of the loader/action
|
|
4176
|
+
let resolve = handlerOverride => {
|
|
4177
|
+
loadedMatches.add(match.route.id);
|
|
4178
|
+
return shouldLoad ? callLoaderOrAction(type, request, match, manifest, mapRouteProperties, handlerOverride, requestContext) : Promise.resolve({
|
|
4179
|
+
type: ResultType.data,
|
|
4180
|
+
result: undefined
|
|
4181
|
+
});
|
|
4182
|
+
};
|
|
4183
|
+
return _extends({}, match, {
|
|
4184
|
+
shouldLoad,
|
|
4185
|
+
resolve
|
|
4186
|
+
});
|
|
4187
|
+
}),
|
|
4188
|
+
request,
|
|
4189
|
+
params: matches[0].params,
|
|
4190
|
+
context: requestContext
|
|
4191
|
+
});
|
|
4192
|
+
|
|
4193
|
+
// Throw if any loadRoute implementations not called since they are what
|
|
4194
|
+
// ensures a route is fully loaded
|
|
4195
|
+
matches.forEach(m => invariant(loadedMatches.has(m.route.id), "`match.resolve()` was not called for route id \"" + m.route.id + "\". " + "You must call `match.resolve()` on every match passed to " + "`dataStrategy` to ensure all routes are properly loaded."));
|
|
4196
|
+
|
|
4197
|
+
// Filter out any middleware-only matches for which we didn't need to run handlers
|
|
4198
|
+
return results.filter((_, i) => routeIdsToLoad.has(matches[i].route.id));
|
|
4199
|
+
}
|
|
4200
|
+
|
|
4201
|
+
// Default logic for calling a loader/action is the user has no specified a dataStrategy
|
|
4202
|
+
async function callLoaderOrAction(type, request, match, manifest, mapRouteProperties, handlerOverride, staticContext) {
|
|
4102
4203
|
let result;
|
|
4103
4204
|
let onReject;
|
|
4104
4205
|
let runHandler = handler => {
|
|
4105
4206
|
// Setup a promise we can race against so that abort signals short circuit
|
|
4106
4207
|
let reject;
|
|
4208
|
+
// This will never resolve so safe to type it as Promise<HandlerResult> to
|
|
4209
|
+
// satisfy the function return value
|
|
4107
4210
|
let abortPromise = new Promise((_, r) => reject = r);
|
|
4108
4211
|
onReject = () => reject();
|
|
4109
4212
|
request.signal.addEventListener("abort", onReject);
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4213
|
+
let actualHandler = ctx => {
|
|
4214
|
+
if (typeof handler !== "function") {
|
|
4215
|
+
return Promise.reject(new Error("You cannot call the handler for a route which defines a boolean " + ("\"" + type + "\" [routeId: " + match.route.id + "]")));
|
|
4216
|
+
}
|
|
4217
|
+
return handler({
|
|
4218
|
+
request,
|
|
4219
|
+
params: match.params,
|
|
4220
|
+
context: staticContext
|
|
4221
|
+
}, ...(ctx !== undefined ? [ctx] : []));
|
|
4222
|
+
};
|
|
4223
|
+
let handlerPromise;
|
|
4224
|
+
if (handlerOverride) {
|
|
4225
|
+
handlerPromise = handlerOverride(ctx => actualHandler(ctx));
|
|
4226
|
+
} else {
|
|
4227
|
+
handlerPromise = (async () => {
|
|
4228
|
+
try {
|
|
4229
|
+
let val = await actualHandler();
|
|
4230
|
+
return {
|
|
4231
|
+
type: "data",
|
|
4232
|
+
result: val
|
|
4233
|
+
};
|
|
4234
|
+
} catch (e) {
|
|
4235
|
+
return {
|
|
4236
|
+
type: "error",
|
|
4237
|
+
result: e
|
|
4238
|
+
};
|
|
4239
|
+
}
|
|
4240
|
+
})();
|
|
4241
|
+
}
|
|
4242
|
+
return Promise.race([handlerPromise, abortPromise]);
|
|
4115
4243
|
};
|
|
4116
4244
|
try {
|
|
4117
4245
|
let handler = match.route[type];
|
|
@@ -4119,23 +4247,23 @@ async function callLoaderOrAction(type, request, match, matches, manifest, mapRo
|
|
|
4119
4247
|
if (handler) {
|
|
4120
4248
|
// Run statically defined handler in parallel with lazy()
|
|
4121
4249
|
let handlerError;
|
|
4122
|
-
let
|
|
4250
|
+
let [value] = await Promise.all([
|
|
4123
4251
|
// If the handler throws, don't let it immediately bubble out,
|
|
4124
4252
|
// since we need to let the lazy() execution finish so we know if this
|
|
4125
4253
|
// route has a boundary that can handle the error
|
|
4126
4254
|
runHandler(handler).catch(e => {
|
|
4127
4255
|
handlerError = e;
|
|
4128
4256
|
}), loadLazyRouteModule(match.route, mapRouteProperties, manifest)]);
|
|
4129
|
-
if (handlerError) {
|
|
4257
|
+
if (handlerError !== undefined) {
|
|
4130
4258
|
throw handlerError;
|
|
4131
4259
|
}
|
|
4132
|
-
result =
|
|
4260
|
+
result = value;
|
|
4133
4261
|
} else {
|
|
4134
4262
|
// Load lazy route module, then run any returned handler
|
|
4135
4263
|
await loadLazyRouteModule(match.route, mapRouteProperties, manifest);
|
|
4136
4264
|
handler = match.route[type];
|
|
4137
4265
|
if (handler) {
|
|
4138
|
-
// Handler still
|
|
4266
|
+
// Handler still runs even if we got interrupted to maintain consistency
|
|
4139
4267
|
// with un-abortable behavior of handler execution on non-lazy or
|
|
4140
4268
|
// previously-lazy-loaded routes
|
|
4141
4269
|
result = await runHandler(handler);
|
|
@@ -4152,7 +4280,7 @@ async function callLoaderOrAction(type, request, match, matches, manifest, mapRo
|
|
|
4152
4280
|
// hit the invariant below that errors on returning undefined.
|
|
4153
4281
|
return {
|
|
4154
4282
|
type: ResultType.data,
|
|
4155
|
-
|
|
4283
|
+
result: undefined
|
|
4156
4284
|
};
|
|
4157
4285
|
}
|
|
4158
4286
|
}
|
|
@@ -4165,65 +4293,29 @@ async function callLoaderOrAction(type, request, match, matches, manifest, mapRo
|
|
|
4165
4293
|
} else {
|
|
4166
4294
|
result = await runHandler(handler);
|
|
4167
4295
|
}
|
|
4168
|
-
invariant(result !== undefined, "You defined " + (type === "action" ? "an action" : "a loader") + " for route " + ("\"" + match.route.id + "\" but didn't return anything from your `" + type + "` ") + "function. Please return a value or `null`.");
|
|
4296
|
+
invariant(result.result !== undefined, "You defined " + (type === "action" ? "an action" : "a loader") + " for route " + ("\"" + match.route.id + "\" but didn't return anything from your `" + type + "` ") + "function. Please return a value or `null`.");
|
|
4169
4297
|
} catch (e) {
|
|
4170
|
-
|
|
4171
|
-
|
|
4298
|
+
// We should already be catching and converting normal handler executions to
|
|
4299
|
+
// HandlerResults and returning them, so anything that throws here is an
|
|
4300
|
+
// unexpected error we still need to wrap
|
|
4301
|
+
return {
|
|
4302
|
+
type: ResultType.error,
|
|
4303
|
+
result: e
|
|
4304
|
+
};
|
|
4172
4305
|
} finally {
|
|
4173
4306
|
if (onReject) {
|
|
4174
4307
|
request.signal.removeEventListener("abort", onReject);
|
|
4175
4308
|
}
|
|
4176
4309
|
}
|
|
4310
|
+
return result;
|
|
4311
|
+
}
|
|
4312
|
+
async function convertHandlerResultToDataResult(handlerResult) {
|
|
4313
|
+
let {
|
|
4314
|
+
result,
|
|
4315
|
+
type,
|
|
4316
|
+
status
|
|
4317
|
+
} = handlerResult;
|
|
4177
4318
|
if (isResponse(result)) {
|
|
4178
|
-
let status = result.status;
|
|
4179
|
-
|
|
4180
|
-
// Process redirects
|
|
4181
|
-
if (redirectStatusCodes.has(status)) {
|
|
4182
|
-
let location = result.headers.get("Location");
|
|
4183
|
-
invariant(location, "Redirects returned/thrown from loaders/actions must have a Location header");
|
|
4184
|
-
|
|
4185
|
-
// Support relative routing in internal redirects
|
|
4186
|
-
if (!ABSOLUTE_URL_REGEX.test(location)) {
|
|
4187
|
-
location = normalizeTo(new URL(request.url), matches.slice(0, matches.indexOf(match) + 1), basename, true, location, v7_relativeSplatPath);
|
|
4188
|
-
} else if (!opts.isStaticRequest) {
|
|
4189
|
-
// Strip off the protocol+origin for same-origin + same-basename absolute
|
|
4190
|
-
// redirects. If this is a static request, we can let it go back to the
|
|
4191
|
-
// browser as-is
|
|
4192
|
-
let currentUrl = new URL(request.url);
|
|
4193
|
-
let url = location.startsWith("//") ? new URL(currentUrl.protocol + location) : new URL(location);
|
|
4194
|
-
let isSameBasename = stripBasename(url.pathname, basename) != null;
|
|
4195
|
-
if (url.origin === currentUrl.origin && isSameBasename) {
|
|
4196
|
-
location = url.pathname + url.search + url.hash;
|
|
4197
|
-
}
|
|
4198
|
-
}
|
|
4199
|
-
|
|
4200
|
-
// Don't process redirects in the router during static requests requests.
|
|
4201
|
-
// Instead, throw the Response and let the server handle it with an HTTP
|
|
4202
|
-
// redirect. We also update the Location header in place in this flow so
|
|
4203
|
-
// basename and relative routing is taken into account
|
|
4204
|
-
if (opts.isStaticRequest) {
|
|
4205
|
-
result.headers.set("Location", location);
|
|
4206
|
-
throw result;
|
|
4207
|
-
}
|
|
4208
|
-
return {
|
|
4209
|
-
type: ResultType.redirect,
|
|
4210
|
-
status,
|
|
4211
|
-
location,
|
|
4212
|
-
revalidate: result.headers.get("X-Remix-Revalidate") !== null,
|
|
4213
|
-
reloadDocument: result.headers.get("X-Remix-Reload-Document") !== null
|
|
4214
|
-
};
|
|
4215
|
-
}
|
|
4216
|
-
|
|
4217
|
-
// For SSR single-route requests, we want to hand Responses back directly
|
|
4218
|
-
// without unwrapping. We do this with the QueryRouteResponse wrapper
|
|
4219
|
-
// interface so we can know whether it was returned or thrown
|
|
4220
|
-
if (opts.isRouteRequest) {
|
|
4221
|
-
let queryRouteResponse = {
|
|
4222
|
-
type: resultType === ResultType.error ? ResultType.error : ResultType.data,
|
|
4223
|
-
response: result
|
|
4224
|
-
};
|
|
4225
|
-
throw queryRouteResponse;
|
|
4226
|
-
}
|
|
4227
4319
|
let data;
|
|
4228
4320
|
try {
|
|
4229
4321
|
let contentType = result.headers.get("Content-Type");
|
|
@@ -4244,10 +4336,11 @@ async function callLoaderOrAction(type, request, match, matches, manifest, mapRo
|
|
|
4244
4336
|
error: e
|
|
4245
4337
|
};
|
|
4246
4338
|
}
|
|
4247
|
-
if (
|
|
4339
|
+
if (type === ResultType.error) {
|
|
4248
4340
|
return {
|
|
4249
|
-
type:
|
|
4250
|
-
error: new ErrorResponseImpl(status, result.statusText, data),
|
|
4341
|
+
type: ResultType.error,
|
|
4342
|
+
error: new ErrorResponseImpl(result.status, result.statusText, data),
|
|
4343
|
+
statusCode: result.status,
|
|
4251
4344
|
headers: result.headers
|
|
4252
4345
|
};
|
|
4253
4346
|
}
|
|
@@ -4258,10 +4351,11 @@ async function callLoaderOrAction(type, request, match, matches, manifest, mapRo
|
|
|
4258
4351
|
headers: result.headers
|
|
4259
4352
|
};
|
|
4260
4353
|
}
|
|
4261
|
-
if (
|
|
4354
|
+
if (type === ResultType.error) {
|
|
4262
4355
|
return {
|
|
4263
|
-
type:
|
|
4264
|
-
error: result
|
|
4356
|
+
type: ResultType.error,
|
|
4357
|
+
error: result,
|
|
4358
|
+
statusCode: isRouteErrorResponse(result) ? result.status : status
|
|
4265
4359
|
};
|
|
4266
4360
|
}
|
|
4267
4361
|
if (isDeferredData(result)) {
|
|
@@ -4275,10 +4369,35 @@ async function callLoaderOrAction(type, request, match, matches, manifest, mapRo
|
|
|
4275
4369
|
}
|
|
4276
4370
|
return {
|
|
4277
4371
|
type: ResultType.data,
|
|
4278
|
-
data: result
|
|
4372
|
+
data: result,
|
|
4373
|
+
statusCode: status
|
|
4279
4374
|
};
|
|
4280
4375
|
}
|
|
4281
4376
|
|
|
4377
|
+
// Support relative routing in internal redirects
|
|
4378
|
+
function normalizeRelativeRoutingRedirectResponse(response, request, routeId, matches, basename, v7_relativeSplatPath) {
|
|
4379
|
+
let location = response.headers.get("Location");
|
|
4380
|
+
invariant(location, "Redirects returned/thrown from loaders/actions must have a Location header");
|
|
4381
|
+
if (!ABSOLUTE_URL_REGEX.test(location)) {
|
|
4382
|
+
let trimmedMatches = matches.slice(0, matches.findIndex(m => m.route.id === routeId) + 1);
|
|
4383
|
+
location = normalizeTo(new URL(request.url), trimmedMatches, basename, true, location, v7_relativeSplatPath);
|
|
4384
|
+
response.headers.set("Location", location);
|
|
4385
|
+
}
|
|
4386
|
+
return response;
|
|
4387
|
+
}
|
|
4388
|
+
function normalizeRedirectLocation(location, currentUrl, basename) {
|
|
4389
|
+
if (ABSOLUTE_URL_REGEX.test(location)) {
|
|
4390
|
+
// Strip off the protocol+origin for same-origin + same-basename absolute redirects
|
|
4391
|
+
let normalizedLocation = location;
|
|
4392
|
+
let url = normalizedLocation.startsWith("//") ? new URL(currentUrl.protocol + normalizedLocation) : new URL(normalizedLocation);
|
|
4393
|
+
let isSameBasename = stripBasename(url.pathname, basename) != null;
|
|
4394
|
+
if (url.origin === currentUrl.origin && isSameBasename) {
|
|
4395
|
+
return url.pathname + url.search + url.hash;
|
|
4396
|
+
}
|
|
4397
|
+
}
|
|
4398
|
+
return location;
|
|
4399
|
+
}
|
|
4400
|
+
|
|
4282
4401
|
// Utility method for creating the Request instances for loaders/actions during
|
|
4283
4402
|
// client-side navigations and fetches. During SSR we will always have a
|
|
4284
4403
|
// Request instance from the static handler (query/queryRoute)
|
|
@@ -4329,35 +4448,39 @@ function convertSearchParamsToFormData(searchParams) {
|
|
|
4329
4448
|
}
|
|
4330
4449
|
return formData;
|
|
4331
4450
|
}
|
|
4332
|
-
function processRouteLoaderData(matches, matchesToLoad, results,
|
|
4451
|
+
function processRouteLoaderData(matches, matchesToLoad, results, pendingActionResult, activeDeferreds, skipLoaderErrorBubbling) {
|
|
4333
4452
|
// Fill in loaderData/errors from our loaders
|
|
4334
4453
|
let loaderData = {};
|
|
4335
4454
|
let errors = null;
|
|
4336
4455
|
let statusCode;
|
|
4337
4456
|
let foundError = false;
|
|
4338
4457
|
let loaderHeaders = {};
|
|
4458
|
+
let pendingError = pendingActionResult && isErrorResult(pendingActionResult[1]) ? pendingActionResult[1].error : undefined;
|
|
4339
4459
|
|
|
4340
4460
|
// Process loader results into state.loaderData/state.errors
|
|
4341
4461
|
results.forEach((result, index) => {
|
|
4342
4462
|
let id = matchesToLoad[index].route.id;
|
|
4343
4463
|
invariant(!isRedirectResult(result), "Cannot handle redirect results in processLoaderData");
|
|
4344
4464
|
if (isErrorResult(result)) {
|
|
4345
|
-
// Look upwards from the matched route for the closest ancestor
|
|
4346
|
-
// error boundary, defaulting to the root match
|
|
4347
|
-
let boundaryMatch = findNearestBoundary(matches, id);
|
|
4348
4465
|
let error = result.error;
|
|
4349
4466
|
// If we have a pending action error, we report it at the highest-route
|
|
4350
4467
|
// that throws a loader error, and then clear it out to indicate that
|
|
4351
4468
|
// it was consumed
|
|
4352
|
-
if (pendingError) {
|
|
4353
|
-
error =
|
|
4469
|
+
if (pendingError !== undefined) {
|
|
4470
|
+
error = pendingError;
|
|
4354
4471
|
pendingError = undefined;
|
|
4355
4472
|
}
|
|
4356
4473
|
errors = errors || {};
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
4474
|
+
if (skipLoaderErrorBubbling) {
|
|
4475
|
+
errors[id] = error;
|
|
4476
|
+
} else {
|
|
4477
|
+
// Look upwards from the matched route for the closest ancestor error
|
|
4478
|
+
// boundary, defaulting to the root match. Prefer higher error values
|
|
4479
|
+
// if lower errors bubble to the same boundary
|
|
4480
|
+
let boundaryMatch = findNearestBoundary(matches, id);
|
|
4481
|
+
if (errors[boundaryMatch.route.id] == null) {
|
|
4482
|
+
errors[boundaryMatch.route.id] = error;
|
|
4483
|
+
}
|
|
4361
4484
|
}
|
|
4362
4485
|
|
|
4363
4486
|
// Clear our any prior loaderData for the throwing route
|
|
@@ -4376,17 +4499,24 @@ function processRouteLoaderData(matches, matchesToLoad, results, pendingError, a
|
|
|
4376
4499
|
if (isDeferredResult(result)) {
|
|
4377
4500
|
activeDeferreds.set(id, result.deferredData);
|
|
4378
4501
|
loaderData[id] = result.deferredData.data;
|
|
4502
|
+
// Error status codes always override success status codes, but if all
|
|
4503
|
+
// loaders are successful we take the deepest status code.
|
|
4504
|
+
if (result.statusCode != null && result.statusCode !== 200 && !foundError) {
|
|
4505
|
+
statusCode = result.statusCode;
|
|
4506
|
+
}
|
|
4507
|
+
if (result.headers) {
|
|
4508
|
+
loaderHeaders[id] = result.headers;
|
|
4509
|
+
}
|
|
4379
4510
|
} else {
|
|
4380
4511
|
loaderData[id] = result.data;
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
loaderHeaders[id] = result.headers;
|
|
4512
|
+
// Error status codes always override success status codes, but if all
|
|
4513
|
+
// loaders are successful we take the deepest status code.
|
|
4514
|
+
if (result.statusCode && result.statusCode !== 200 && !foundError) {
|
|
4515
|
+
statusCode = result.statusCode;
|
|
4516
|
+
}
|
|
4517
|
+
if (result.headers) {
|
|
4518
|
+
loaderHeaders[id] = result.headers;
|
|
4519
|
+
}
|
|
4390
4520
|
}
|
|
4391
4521
|
}
|
|
4392
4522
|
});
|
|
@@ -4394,9 +4524,11 @@ function processRouteLoaderData(matches, matchesToLoad, results, pendingError, a
|
|
|
4394
4524
|
// If we didn't consume the pending action error (i.e., all loaders
|
|
4395
4525
|
// resolved), then consume it here. Also clear out any loaderData for the
|
|
4396
4526
|
// throwing route
|
|
4397
|
-
if (pendingError) {
|
|
4398
|
-
errors =
|
|
4399
|
-
|
|
4527
|
+
if (pendingError !== undefined && pendingActionResult) {
|
|
4528
|
+
errors = {
|
|
4529
|
+
[pendingActionResult[0]]: pendingError
|
|
4530
|
+
};
|
|
4531
|
+
loaderData[pendingActionResult[0]] = undefined;
|
|
4400
4532
|
}
|
|
4401
4533
|
return {
|
|
4402
4534
|
loaderData,
|
|
@@ -4405,11 +4537,12 @@ function processRouteLoaderData(matches, matchesToLoad, results, pendingError, a
|
|
|
4405
4537
|
loaderHeaders
|
|
4406
4538
|
};
|
|
4407
4539
|
}
|
|
4408
|
-
function processLoaderData(state, matches, matchesToLoad, results,
|
|
4540
|
+
function processLoaderData(state, matches, matchesToLoad, results, pendingActionResult, revalidatingFetchers, fetcherResults, activeDeferreds) {
|
|
4409
4541
|
let {
|
|
4410
4542
|
loaderData,
|
|
4411
4543
|
errors
|
|
4412
|
-
} = processRouteLoaderData(matches, matchesToLoad, results,
|
|
4544
|
+
} = processRouteLoaderData(matches, matchesToLoad, results, pendingActionResult, activeDeferreds, false // This method is only called client side so we always want to bubble
|
|
4545
|
+
);
|
|
4413
4546
|
|
|
4414
4547
|
// Process results from our revalidating fetchers
|
|
4415
4548
|
for (let index = 0; index < revalidatingFetchers.length; index++) {
|
|
@@ -4471,6 +4604,19 @@ function mergeLoaderData(loaderData, newLoaderData, matches, errors) {
|
|
|
4471
4604
|
}
|
|
4472
4605
|
return mergedLoaderData;
|
|
4473
4606
|
}
|
|
4607
|
+
function getActionDataForCommit(pendingActionResult) {
|
|
4608
|
+
if (!pendingActionResult) {
|
|
4609
|
+
return {};
|
|
4610
|
+
}
|
|
4611
|
+
return isErrorResult(pendingActionResult[1]) ? {
|
|
4612
|
+
// Clear out prior actionData on errors
|
|
4613
|
+
actionData: {}
|
|
4614
|
+
} : {
|
|
4615
|
+
actionData: {
|
|
4616
|
+
[pendingActionResult[0]]: pendingActionResult[1].data
|
|
4617
|
+
}
|
|
4618
|
+
};
|
|
4619
|
+
}
|
|
4474
4620
|
|
|
4475
4621
|
// Find the nearest error boundary, looking upwards from the leaf route (or the
|
|
4476
4622
|
// route specified by routeId) for the closest ancestor error boundary,
|
|
@@ -4566,6 +4712,12 @@ function isHashChangeOnly(a, b) {
|
|
|
4566
4712
|
// /page#hash -> /page
|
|
4567
4713
|
return false;
|
|
4568
4714
|
}
|
|
4715
|
+
function isHandlerResult(result) {
|
|
4716
|
+
return result != null && typeof result === "object" && "type" in result && "result" in result && (result.type === ResultType.data || result.type === ResultType.error);
|
|
4717
|
+
}
|
|
4718
|
+
function isRedirectHandlerResult(result) {
|
|
4719
|
+
return isResponse(result.result) && redirectStatusCodes.has(result.result.status);
|
|
4720
|
+
}
|
|
4569
4721
|
function isDeferredResult(result) {
|
|
4570
4722
|
return result.type === ResultType.deferred;
|
|
4571
4723
|
}
|
|
@@ -4590,9 +4742,6 @@ function isRedirectResponse(result) {
|
|
|
4590
4742
|
let location = result.headers.get("Location");
|
|
4591
4743
|
return status >= 300 && status <= 399 && location != null;
|
|
4592
4744
|
}
|
|
4593
|
-
function isQueryRouteResponse(obj) {
|
|
4594
|
-
return obj && isResponse(obj.response) && (obj.type === ResultType.data || obj.type === ResultType.error);
|
|
4595
|
-
}
|
|
4596
4745
|
function isValidMethod(method) {
|
|
4597
4746
|
return validRequestMethods.has(method.toLowerCase());
|
|
4598
4747
|
}
|