@remix-run/router 1.15.3 → 1.16.0-pre.0
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 +24 -0
- package/dist/index.d.ts +1 -1
- package/dist/router.cjs.js +441 -250
- package/dist/router.cjs.js.map +1 -1
- package/dist/router.d.ts +7 -1
- package/dist/router.js +427 -238
- package/dist/router.js.map +1 -1
- package/dist/router.umd.js +441 -250
- 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 +747 -359
- 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.0
|
|
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,25 @@ 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.loadRouteIds` is an optional array of routeIds to run only a subset of
|
|
3353
|
+
* loaders during a query() call
|
|
3354
|
+
* - `opts.requestContext` is an optional server context that will be passed
|
|
3355
|
+
* to actions/loaders in the `context` parameter
|
|
3356
|
+
* - `opts.skipLoaderErrorBubbling` is an optional parameter that will prevent
|
|
3357
|
+
* the bubbling of errors which allows single-fetch-type implementations
|
|
3358
|
+
* where the client will handle the bubbling and we may need to return data
|
|
3359
|
+
* for the handling route
|
|
3360
|
+
* - `opts.skipLoaders` is an optional parameter that will prevent loaders
|
|
3361
|
+
* from running after an action
|
|
3325
3362
|
*/
|
|
3326
3363
|
async function query(request, _temp3) {
|
|
3327
3364
|
let {
|
|
3328
|
-
|
|
3365
|
+
loadRouteIds,
|
|
3366
|
+
requestContext,
|
|
3367
|
+
skipLoaderErrorBubbling,
|
|
3368
|
+
skipLoaders,
|
|
3369
|
+
unstable_dataStrategy
|
|
3329
3370
|
} = _temp3 === void 0 ? {} : _temp3;
|
|
3330
3371
|
let url = new URL(request.url);
|
|
3331
3372
|
let method = request.method;
|
|
@@ -3378,7 +3419,7 @@ function createStaticHandler(routes, opts) {
|
|
|
3378
3419
|
activeDeferreds: null
|
|
3379
3420
|
};
|
|
3380
3421
|
}
|
|
3381
|
-
let result = await queryImpl(request, location, matches, requestContext);
|
|
3422
|
+
let result = await queryImpl(request, location, matches, requestContext, unstable_dataStrategy || null, loadRouteIds || null, skipLoaderErrorBubbling === true, skipLoaders === true, null);
|
|
3382
3423
|
if (isResponse(result)) {
|
|
3383
3424
|
return result;
|
|
3384
3425
|
}
|
|
@@ -3411,6 +3452,12 @@ function createStaticHandler(routes, opts) {
|
|
|
3411
3452
|
* serialize the error as they see fit while including the proper response
|
|
3412
3453
|
* code. Examples here are 404 and 405 errors that occur prior to reaching
|
|
3413
3454
|
* any user-defined loaders.
|
|
3455
|
+
*
|
|
3456
|
+
* - `opts.routeId` allows you to specify the specific route handler to call.
|
|
3457
|
+
* If not provided the handler will determine the proper route by matching
|
|
3458
|
+
* against `request.url`
|
|
3459
|
+
* - `opts.requestContext` is an optional server context that will be passed
|
|
3460
|
+
* to actions/loaders in the `context` parameter
|
|
3414
3461
|
*/
|
|
3415
3462
|
async function queryRoute(request, _temp4) {
|
|
3416
3463
|
let {
|
|
@@ -3444,7 +3491,7 @@ function createStaticHandler(routes, opts) {
|
|
|
3444
3491
|
pathname: location.pathname
|
|
3445
3492
|
});
|
|
3446
3493
|
}
|
|
3447
|
-
let result = await queryImpl(request, location, matches, requestContext, match);
|
|
3494
|
+
let result = await queryImpl(request, location, matches, requestContext, null, null, false, false, match);
|
|
3448
3495
|
if (isResponse(result)) {
|
|
3449
3496
|
return result;
|
|
3450
3497
|
}
|
|
@@ -3471,27 +3518,27 @@ function createStaticHandler(routes, opts) {
|
|
|
3471
3518
|
}
|
|
3472
3519
|
return undefined;
|
|
3473
3520
|
}
|
|
3474
|
-
async function queryImpl(request, location, matches, requestContext, routeMatch) {
|
|
3521
|
+
async function queryImpl(request, location, matches, requestContext, unstable_dataStrategy, loadRouteIds, skipLoaderErrorBubbling, skipLoaders, routeMatch) {
|
|
3475
3522
|
invariant(request.signal, "query()/queryRoute() requests must contain an AbortController signal");
|
|
3476
3523
|
try {
|
|
3477
3524
|
if (isMutationMethod(request.method.toLowerCase())) {
|
|
3478
|
-
let result = await submit(request, matches, routeMatch || getTargetMatch(matches, location), requestContext, routeMatch != null);
|
|
3525
|
+
let result = await submit(request, matches, routeMatch || getTargetMatch(matches, location), requestContext, unstable_dataStrategy, loadRouteIds, skipLoaderErrorBubbling, skipLoaders, routeMatch != null);
|
|
3479
3526
|
return result;
|
|
3480
3527
|
}
|
|
3481
|
-
let result = await loadRouteData(request, matches, requestContext, routeMatch);
|
|
3528
|
+
let result = await loadRouteData(request, matches, requestContext, unstable_dataStrategy, loadRouteIds, skipLoaderErrorBubbling, routeMatch);
|
|
3482
3529
|
return isResponse(result) ? result : _extends({}, result, {
|
|
3483
3530
|
actionData: null,
|
|
3484
3531
|
actionHeaders: {}
|
|
3485
3532
|
});
|
|
3486
3533
|
} catch (e) {
|
|
3487
|
-
// If the user threw/returned a Response in callLoaderOrAction
|
|
3488
|
-
//
|
|
3489
|
-
//
|
|
3490
|
-
if (
|
|
3534
|
+
// If the user threw/returned a Response in callLoaderOrAction for a
|
|
3535
|
+
// `queryRoute` call, we throw the `HandlerResult` to bail out early
|
|
3536
|
+
// and then return or throw the raw Response here accordingly
|
|
3537
|
+
if (isHandlerResult(e) && isResponse(e.result)) {
|
|
3491
3538
|
if (e.type === ResultType.error) {
|
|
3492
|
-
throw e.
|
|
3539
|
+
throw e.result;
|
|
3493
3540
|
}
|
|
3494
|
-
return e.
|
|
3541
|
+
return e.result;
|
|
3495
3542
|
}
|
|
3496
3543
|
// Redirects are always returned since they don't propagate to catch
|
|
3497
3544
|
// boundaries
|
|
@@ -3501,7 +3548,7 @@ function createStaticHandler(routes, opts) {
|
|
|
3501
3548
|
throw e;
|
|
3502
3549
|
}
|
|
3503
3550
|
}
|
|
3504
|
-
async function submit(request, matches, actionMatch, requestContext, isRouteRequest) {
|
|
3551
|
+
async function submit(request, matches, actionMatch, requestContext, unstable_dataStrategy, loadRouteIds, skipLoaderErrorBubbling, skipLoaders, isRouteRequest) {
|
|
3505
3552
|
let result;
|
|
3506
3553
|
if (!actionMatch.route.action && !actionMatch.route.lazy) {
|
|
3507
3554
|
let error = getInternalRouterError(405, {
|
|
@@ -3517,11 +3564,8 @@ function createStaticHandler(routes, opts) {
|
|
|
3517
3564
|
error
|
|
3518
3565
|
};
|
|
3519
3566
|
} else {
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
isRouteRequest,
|
|
3523
|
-
requestContext
|
|
3524
|
-
});
|
|
3567
|
+
let results = await callDataStrategy("action", request, [actionMatch], matches, isRouteRequest, requestContext, unstable_dataStrategy);
|
|
3568
|
+
result = results[0];
|
|
3525
3569
|
if (request.signal.aborted) {
|
|
3526
3570
|
throwStaticHandlerAbortedError(request, isRouteRequest, future);
|
|
3527
3571
|
}
|
|
@@ -3532,9 +3576,9 @@ function createStaticHandler(routes, opts) {
|
|
|
3532
3576
|
// can get back on the "throw all redirect responses" train here should
|
|
3533
3577
|
// this ever happen :/
|
|
3534
3578
|
throw new Response(null, {
|
|
3535
|
-
status: result.status,
|
|
3579
|
+
status: result.response.status,
|
|
3536
3580
|
headers: {
|
|
3537
|
-
Location: result.
|
|
3581
|
+
Location: result.response.headers.get("Location")
|
|
3538
3582
|
}
|
|
3539
3583
|
});
|
|
3540
3584
|
}
|
|
@@ -3571,43 +3615,75 @@ function createStaticHandler(routes, opts) {
|
|
|
3571
3615
|
activeDeferreds: null
|
|
3572
3616
|
};
|
|
3573
3617
|
}
|
|
3618
|
+
|
|
3619
|
+
// Create a GET request for the loaders
|
|
3620
|
+
let loaderRequest = new Request(request.url, {
|
|
3621
|
+
headers: request.headers,
|
|
3622
|
+
redirect: request.redirect,
|
|
3623
|
+
signal: request.signal
|
|
3624
|
+
});
|
|
3574
3625
|
if (isErrorResult(result)) {
|
|
3575
3626
|
// Store off the pending error - we use it to determine which loaders
|
|
3576
3627
|
// to call and will commit it when we complete the navigation
|
|
3577
|
-
let boundaryMatch = findNearestBoundary(matches, actionMatch.route.id);
|
|
3578
|
-
let
|
|
3579
|
-
|
|
3580
|
-
|
|
3628
|
+
let boundaryMatch = skipLoaderErrorBubbling ? actionMatch : findNearestBoundary(matches, actionMatch.route.id);
|
|
3629
|
+
let statusCode = isRouteErrorResponse(result.error) ? result.error.status : result.statusCode != null ? result.statusCode : 500;
|
|
3630
|
+
let actionHeaders = _extends({}, result.headers ? {
|
|
3631
|
+
[actionMatch.route.id]: result.headers
|
|
3632
|
+
} : {});
|
|
3633
|
+
if (skipLoaders) {
|
|
3634
|
+
return {
|
|
3635
|
+
matches,
|
|
3636
|
+
loaderData: {},
|
|
3637
|
+
actionData: {},
|
|
3638
|
+
errors: {
|
|
3639
|
+
[boundaryMatch.route.id]: result.error
|
|
3640
|
+
},
|
|
3641
|
+
statusCode,
|
|
3642
|
+
loaderHeaders: {},
|
|
3643
|
+
actionHeaders,
|
|
3644
|
+
activeDeferreds: null
|
|
3645
|
+
};
|
|
3646
|
+
}
|
|
3647
|
+
let context = await loadRouteData(loaderRequest, matches, requestContext, unstable_dataStrategy, loadRouteIds, skipLoaderErrorBubbling, null, [boundaryMatch.route.id, result]);
|
|
3581
3648
|
|
|
3582
3649
|
// action status codes take precedence over loader status codes
|
|
3583
3650
|
return _extends({}, context, {
|
|
3584
|
-
statusCode
|
|
3651
|
+
statusCode,
|
|
3585
3652
|
actionData: null,
|
|
3586
|
-
actionHeaders
|
|
3587
|
-
[actionMatch.route.id]: result.headers
|
|
3588
|
-
} : {})
|
|
3653
|
+
actionHeaders
|
|
3589
3654
|
});
|
|
3590
3655
|
}
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3656
|
+
let actionHeaders = result.headers ? {
|
|
3657
|
+
[actionMatch.route.id]: result.headers
|
|
3658
|
+
} : {};
|
|
3659
|
+
if (skipLoaders) {
|
|
3660
|
+
return {
|
|
3661
|
+
matches,
|
|
3662
|
+
loaderData: {},
|
|
3663
|
+
actionData: {
|
|
3664
|
+
[actionMatch.route.id]: result.data
|
|
3665
|
+
},
|
|
3666
|
+
errors: null,
|
|
3667
|
+
statusCode: result.statusCode || 200,
|
|
3668
|
+
loaderHeaders: {},
|
|
3669
|
+
actionHeaders,
|
|
3670
|
+
activeDeferreds: null
|
|
3671
|
+
};
|
|
3672
|
+
}
|
|
3673
|
+
let context = await loadRouteData(loaderRequest, matches, requestContext, unstable_dataStrategy, loadRouteIds, skipLoaderErrorBubbling, null);
|
|
3674
|
+
return _extends({}, context, {
|
|
3602
3675
|
actionData: {
|
|
3603
3676
|
[actionMatch.route.id]: result.data
|
|
3604
|
-
}
|
|
3605
|
-
|
|
3677
|
+
}
|
|
3678
|
+
}, result.statusCode ? {
|
|
3679
|
+
statusCode: result.statusCode
|
|
3680
|
+
} : {}, {
|
|
3681
|
+
actionHeaders: result.headers ? {
|
|
3606
3682
|
[actionMatch.route.id]: result.headers
|
|
3607
|
-
} : {}
|
|
3683
|
+
} : {}
|
|
3608
3684
|
});
|
|
3609
3685
|
}
|
|
3610
|
-
async function loadRouteData(request, matches, requestContext, routeMatch,
|
|
3686
|
+
async function loadRouteData(request, matches, requestContext, unstable_dataStrategy, loadRouteIds, skipLoaderErrorBubbling, routeMatch, pendingActionResult) {
|
|
3611
3687
|
let isRouteRequest = routeMatch != null;
|
|
3612
3688
|
|
|
3613
3689
|
// Short circuit if we have no loaders to run (queryRoute())
|
|
@@ -3618,8 +3694,11 @@ function createStaticHandler(routes, opts) {
|
|
|
3618
3694
|
routeId: routeMatch == null ? void 0 : routeMatch.route.id
|
|
3619
3695
|
});
|
|
3620
3696
|
}
|
|
3621
|
-
let requestMatches = routeMatch ? [routeMatch] : getLoaderMatchesUntilBoundary(matches,
|
|
3697
|
+
let requestMatches = routeMatch ? [routeMatch] : pendingActionResult && isErrorResult(pendingActionResult[1]) ? getLoaderMatchesUntilBoundary(matches, pendingActionResult[0]) : matches;
|
|
3622
3698
|
let matchesToLoad = requestMatches.filter(m => m.route.loader || m.route.lazy);
|
|
3699
|
+
if (loadRouteIds) {
|
|
3700
|
+
matchesToLoad = matchesToLoad.filter(m => loadRouteIds.includes(m.route.id));
|
|
3701
|
+
}
|
|
3623
3702
|
|
|
3624
3703
|
// Short circuit if we have no loaders to run (query())
|
|
3625
3704
|
if (matchesToLoad.length === 0) {
|
|
@@ -3629,24 +3708,22 @@ function createStaticHandler(routes, opts) {
|
|
|
3629
3708
|
loaderData: matches.reduce((acc, m) => Object.assign(acc, {
|
|
3630
3709
|
[m.route.id]: null
|
|
3631
3710
|
}), {}),
|
|
3632
|
-
errors:
|
|
3711
|
+
errors: pendingActionResult && isErrorResult(pendingActionResult[1]) ? {
|
|
3712
|
+
[pendingActionResult[0]]: pendingActionResult[1].error
|
|
3713
|
+
} : null,
|
|
3633
3714
|
statusCode: 200,
|
|
3634
3715
|
loaderHeaders: {},
|
|
3635
3716
|
activeDeferreds: null
|
|
3636
3717
|
};
|
|
3637
3718
|
}
|
|
3638
|
-
let results = await
|
|
3639
|
-
isStaticRequest: true,
|
|
3640
|
-
isRouteRequest,
|
|
3641
|
-
requestContext
|
|
3642
|
-
}))]);
|
|
3719
|
+
let results = await callDataStrategy("loader", request, matchesToLoad, matches, isRouteRequest, requestContext, unstable_dataStrategy);
|
|
3643
3720
|
if (request.signal.aborted) {
|
|
3644
3721
|
throwStaticHandlerAbortedError(request, isRouteRequest, future);
|
|
3645
3722
|
}
|
|
3646
3723
|
|
|
3647
3724
|
// Process and commit output from loaders
|
|
3648
3725
|
let activeDeferreds = new Map();
|
|
3649
|
-
let context = processRouteLoaderData(matches, matchesToLoad, results,
|
|
3726
|
+
let context = processRouteLoaderData(matches, matchesToLoad, results, pendingActionResult, activeDeferreds, skipLoaderErrorBubbling);
|
|
3650
3727
|
|
|
3651
3728
|
// Add a null for any non-loader matches for proper revalidation on the client
|
|
3652
3729
|
let executedLoaders = new Set(matchesToLoad.map(match => match.route.id));
|
|
@@ -3660,6 +3737,25 @@ function createStaticHandler(routes, opts) {
|
|
|
3660
3737
|
activeDeferreds: activeDeferreds.size > 0 ? Object.fromEntries(activeDeferreds.entries()) : null
|
|
3661
3738
|
});
|
|
3662
3739
|
}
|
|
3740
|
+
|
|
3741
|
+
// Utility wrapper for calling dataStrategy server-side without having to
|
|
3742
|
+
// pass around the manifest, mapRouteProperties, etc.
|
|
3743
|
+
async function callDataStrategy(type, request, matchesToLoad, matches, isRouteRequest, requestContext, unstable_dataStrategy) {
|
|
3744
|
+
let results = await callDataStrategyImpl(unstable_dataStrategy || defaultDataStrategy, type, request, matchesToLoad, matches, manifest, mapRouteProperties, requestContext);
|
|
3745
|
+
return await Promise.all(results.map((result, i) => {
|
|
3746
|
+
if (isRedirectHandlerResult(result)) {
|
|
3747
|
+
let response = result.result;
|
|
3748
|
+
// Throw redirects and let the server handle them with an HTTP redirect
|
|
3749
|
+
throw normalizeRelativeRoutingRedirectResponse(response, request, matchesToLoad[i].route.id, matches, basename, future.v7_relativeSplatPath);
|
|
3750
|
+
}
|
|
3751
|
+
if (isResponse(result.result) && isRouteRequest) {
|
|
3752
|
+
// For SSR single-route requests, we want to hand Responses back
|
|
3753
|
+
// directly without unwrapping
|
|
3754
|
+
throw result;
|
|
3755
|
+
}
|
|
3756
|
+
return convertHandlerResultToDataResult(result);
|
|
3757
|
+
}));
|
|
3758
|
+
}
|
|
3663
3759
|
return {
|
|
3664
3760
|
dataRoutes,
|
|
3665
3761
|
query,
|
|
@@ -3880,14 +3976,20 @@ function getLoaderMatchesUntilBoundary(matches, boundaryId) {
|
|
|
3880
3976
|
}
|
|
3881
3977
|
return boundaryMatches;
|
|
3882
3978
|
}
|
|
3883
|
-
function getMatchesToLoad(history, state, matches, submission, location, isInitialLoad, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, deletedFetchers, fetchLoadMatches, fetchRedirectIds, routesToUse, basename,
|
|
3884
|
-
let actionResult =
|
|
3979
|
+
function getMatchesToLoad(history, state, matches, submission, location, isInitialLoad, skipActionErrorRevalidation, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, deletedFetchers, fetchLoadMatches, fetchRedirectIds, routesToUse, basename, pendingActionResult) {
|
|
3980
|
+
let actionResult = pendingActionResult ? isErrorResult(pendingActionResult[1]) ? pendingActionResult[1].error : pendingActionResult[1].data : undefined;
|
|
3885
3981
|
let currentUrl = history.createURL(state.location);
|
|
3886
3982
|
let nextUrl = history.createURL(location);
|
|
3887
3983
|
|
|
3888
3984
|
// Pick navigation matches that are net-new or qualify for revalidation
|
|
3889
|
-
let boundaryId =
|
|
3890
|
-
let boundaryMatches = getLoaderMatchesUntilBoundary(matches, boundaryId);
|
|
3985
|
+
let boundaryId = pendingActionResult && isErrorResult(pendingActionResult[1]) ? pendingActionResult[0] : undefined;
|
|
3986
|
+
let boundaryMatches = boundaryId ? getLoaderMatchesUntilBoundary(matches, boundaryId) : matches;
|
|
3987
|
+
|
|
3988
|
+
// Don't revalidate loaders by default after action 4xx/5xx responses
|
|
3989
|
+
// when the flag is enabled. They can still opt-into revalidation via
|
|
3990
|
+
// `shouldRevalidate` via `actionResult`
|
|
3991
|
+
let actionStatus = pendingActionResult ? pendingActionResult[1].statusCode : undefined;
|
|
3992
|
+
let shouldSkipRevalidation = skipActionErrorRevalidation && actionStatus && actionStatus >= 400;
|
|
3891
3993
|
let navigationMatches = boundaryMatches.filter((match, index) => {
|
|
3892
3994
|
let {
|
|
3893
3995
|
route
|
|
@@ -3900,7 +4002,7 @@ function getMatchesToLoad(history, state, matches, submission, location, isIniti
|
|
|
3900
4002
|
return false;
|
|
3901
4003
|
}
|
|
3902
4004
|
if (isInitialLoad) {
|
|
3903
|
-
if (route.loader.hydrate) {
|
|
4005
|
+
if (typeof route.loader !== "function" || route.loader.hydrate) {
|
|
3904
4006
|
return true;
|
|
3905
4007
|
}
|
|
3906
4008
|
return state.loaderData[route.id] === undefined && (
|
|
@@ -3926,11 +4028,10 @@ function getMatchesToLoad(history, state, matches, submission, location, isIniti
|
|
|
3926
4028
|
nextParams: nextRouteMatch.params
|
|
3927
4029
|
}, submission, {
|
|
3928
4030
|
actionResult,
|
|
3929
|
-
|
|
4031
|
+
unstable_actionStatus: actionStatus,
|
|
4032
|
+
defaultShouldRevalidate: shouldSkipRevalidation ? false :
|
|
3930
4033
|
// 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 ||
|
|
4034
|
+
isRevalidationRequired || currentUrl.pathname + currentUrl.search === nextUrl.pathname + nextUrl.search ||
|
|
3934
4035
|
// Search params affect all loaders
|
|
3935
4036
|
currentUrl.search !== nextUrl.search || isNewRouteInstance(currentRouteMatch, nextRouteMatch)
|
|
3936
4037
|
}));
|
|
@@ -3992,7 +4093,8 @@ function getMatchesToLoad(history, state, matches, submission, location, isIniti
|
|
|
3992
4093
|
nextParams: matches[matches.length - 1].params
|
|
3993
4094
|
}, submission, {
|
|
3994
4095
|
actionResult,
|
|
3995
|
-
|
|
4096
|
+
unstable_actionStatus: actionStatus,
|
|
4097
|
+
defaultShouldRevalidate: shouldSkipRevalidation ? false : isRevalidationRequired
|
|
3996
4098
|
}));
|
|
3997
4099
|
}
|
|
3998
4100
|
if (shouldRevalidate) {
|
|
@@ -4094,24 +4196,92 @@ async function loadLazyRouteModule(route, mapRouteProperties, manifest) {
|
|
|
4094
4196
|
lazy: undefined
|
|
4095
4197
|
}));
|
|
4096
4198
|
}
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4199
|
+
|
|
4200
|
+
// Default implementation of `dataStrategy` which fetches all loaders in parallel
|
|
4201
|
+
function defaultDataStrategy(opts) {
|
|
4202
|
+
return Promise.all(opts.matches.map(m => m.resolve()));
|
|
4203
|
+
}
|
|
4204
|
+
async function callDataStrategyImpl(dataStrategyImpl, type, request, matchesToLoad, matches, manifest, mapRouteProperties, requestContext) {
|
|
4205
|
+
let routeIdsToLoad = matchesToLoad.reduce((acc, m) => acc.add(m.route.id), new Set());
|
|
4206
|
+
let loadedMatches = new Set();
|
|
4207
|
+
|
|
4208
|
+
// Send all matches here to allow for a middleware-type implementation.
|
|
4209
|
+
// handler will be a no-op for unneeded routes and we filter those results
|
|
4210
|
+
// back out below.
|
|
4211
|
+
let results = await dataStrategyImpl({
|
|
4212
|
+
matches: matches.map(match => {
|
|
4213
|
+
let shouldLoad = routeIdsToLoad.has(match.route.id);
|
|
4214
|
+
// `resolve` encapsulates the route.lazy, executing the
|
|
4215
|
+
// loader/action, and mapping return values/thrown errors to a
|
|
4216
|
+
// HandlerResult. Users can pass a callback to take fine-grained control
|
|
4217
|
+
// over the execution of the loader/action
|
|
4218
|
+
let resolve = handlerOverride => {
|
|
4219
|
+
loadedMatches.add(match.route.id);
|
|
4220
|
+
return shouldLoad ? callLoaderOrAction(type, request, match, manifest, mapRouteProperties, handlerOverride, requestContext) : Promise.resolve({
|
|
4221
|
+
type: ResultType.data,
|
|
4222
|
+
result: undefined
|
|
4223
|
+
});
|
|
4224
|
+
};
|
|
4225
|
+
return _extends({}, match, {
|
|
4226
|
+
shouldLoad,
|
|
4227
|
+
resolve
|
|
4228
|
+
});
|
|
4229
|
+
}),
|
|
4230
|
+
request,
|
|
4231
|
+
params: matches[0].params,
|
|
4232
|
+
context: requestContext
|
|
4233
|
+
});
|
|
4234
|
+
|
|
4235
|
+
// Throw if any loadRoute implementations not called since they are what
|
|
4236
|
+
// ensures a route is fully loaded
|
|
4237
|
+
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."));
|
|
4238
|
+
|
|
4239
|
+
// Filter out any middleware-only matches for which we didn't need to run handlers
|
|
4240
|
+
return results.filter((_, i) => routeIdsToLoad.has(matches[i].route.id));
|
|
4241
|
+
}
|
|
4242
|
+
|
|
4243
|
+
// Default logic for calling a loader/action is the user has no specified a dataStrategy
|
|
4244
|
+
async function callLoaderOrAction(type, request, match, manifest, mapRouteProperties, handlerOverride, staticContext) {
|
|
4102
4245
|
let result;
|
|
4103
4246
|
let onReject;
|
|
4104
4247
|
let runHandler = handler => {
|
|
4105
4248
|
// Setup a promise we can race against so that abort signals short circuit
|
|
4106
4249
|
let reject;
|
|
4250
|
+
// This will never resolve so safe to type it as Promise<HandlerResult> to
|
|
4251
|
+
// satisfy the function return value
|
|
4107
4252
|
let abortPromise = new Promise((_, r) => reject = r);
|
|
4108
4253
|
onReject = () => reject();
|
|
4109
4254
|
request.signal.addEventListener("abort", onReject);
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4255
|
+
let actualHandler = ctx => {
|
|
4256
|
+
if (typeof handler !== "function") {
|
|
4257
|
+
return Promise.reject(new Error("You cannot call the handler for a route which defines a boolean " + ("\"" + type + "\" [routeId: " + match.route.id + "]")));
|
|
4258
|
+
}
|
|
4259
|
+
return handler({
|
|
4260
|
+
request,
|
|
4261
|
+
params: match.params,
|
|
4262
|
+
context: staticContext
|
|
4263
|
+
}, ...(ctx !== undefined ? [ctx] : []));
|
|
4264
|
+
};
|
|
4265
|
+
let handlerPromise;
|
|
4266
|
+
if (handlerOverride) {
|
|
4267
|
+
handlerPromise = handlerOverride(ctx => actualHandler(ctx));
|
|
4268
|
+
} else {
|
|
4269
|
+
handlerPromise = (async () => {
|
|
4270
|
+
try {
|
|
4271
|
+
let val = await actualHandler();
|
|
4272
|
+
return {
|
|
4273
|
+
type: "data",
|
|
4274
|
+
result: val
|
|
4275
|
+
};
|
|
4276
|
+
} catch (e) {
|
|
4277
|
+
return {
|
|
4278
|
+
type: "error",
|
|
4279
|
+
result: e
|
|
4280
|
+
};
|
|
4281
|
+
}
|
|
4282
|
+
})();
|
|
4283
|
+
}
|
|
4284
|
+
return Promise.race([handlerPromise, abortPromise]);
|
|
4115
4285
|
};
|
|
4116
4286
|
try {
|
|
4117
4287
|
let handler = match.route[type];
|
|
@@ -4119,23 +4289,23 @@ async function callLoaderOrAction(type, request, match, matches, manifest, mapRo
|
|
|
4119
4289
|
if (handler) {
|
|
4120
4290
|
// Run statically defined handler in parallel with lazy()
|
|
4121
4291
|
let handlerError;
|
|
4122
|
-
let
|
|
4292
|
+
let [value] = await Promise.all([
|
|
4123
4293
|
// If the handler throws, don't let it immediately bubble out,
|
|
4124
4294
|
// since we need to let the lazy() execution finish so we know if this
|
|
4125
4295
|
// route has a boundary that can handle the error
|
|
4126
4296
|
runHandler(handler).catch(e => {
|
|
4127
4297
|
handlerError = e;
|
|
4128
4298
|
}), loadLazyRouteModule(match.route, mapRouteProperties, manifest)]);
|
|
4129
|
-
if (handlerError) {
|
|
4299
|
+
if (handlerError !== undefined) {
|
|
4130
4300
|
throw handlerError;
|
|
4131
4301
|
}
|
|
4132
|
-
result =
|
|
4302
|
+
result = value;
|
|
4133
4303
|
} else {
|
|
4134
4304
|
// Load lazy route module, then run any returned handler
|
|
4135
4305
|
await loadLazyRouteModule(match.route, mapRouteProperties, manifest);
|
|
4136
4306
|
handler = match.route[type];
|
|
4137
4307
|
if (handler) {
|
|
4138
|
-
// Handler still
|
|
4308
|
+
// Handler still runs even if we got interrupted to maintain consistency
|
|
4139
4309
|
// with un-abortable behavior of handler execution on non-lazy or
|
|
4140
4310
|
// previously-lazy-loaded routes
|
|
4141
4311
|
result = await runHandler(handler);
|
|
@@ -4152,7 +4322,7 @@ async function callLoaderOrAction(type, request, match, matches, manifest, mapRo
|
|
|
4152
4322
|
// hit the invariant below that errors on returning undefined.
|
|
4153
4323
|
return {
|
|
4154
4324
|
type: ResultType.data,
|
|
4155
|
-
|
|
4325
|
+
result: undefined
|
|
4156
4326
|
};
|
|
4157
4327
|
}
|
|
4158
4328
|
}
|
|
@@ -4165,65 +4335,29 @@ async function callLoaderOrAction(type, request, match, matches, manifest, mapRo
|
|
|
4165
4335
|
} else {
|
|
4166
4336
|
result = await runHandler(handler);
|
|
4167
4337
|
}
|
|
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`.");
|
|
4338
|
+
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
4339
|
} catch (e) {
|
|
4170
|
-
|
|
4171
|
-
|
|
4340
|
+
// We should already be catching and converting normal handler executions to
|
|
4341
|
+
// HandlerResults and returning them, so anything that throws here is an
|
|
4342
|
+
// unexpected error we still need to wrap
|
|
4343
|
+
return {
|
|
4344
|
+
type: ResultType.error,
|
|
4345
|
+
result: e
|
|
4346
|
+
};
|
|
4172
4347
|
} finally {
|
|
4173
4348
|
if (onReject) {
|
|
4174
4349
|
request.signal.removeEventListener("abort", onReject);
|
|
4175
4350
|
}
|
|
4176
4351
|
}
|
|
4352
|
+
return result;
|
|
4353
|
+
}
|
|
4354
|
+
async function convertHandlerResultToDataResult(handlerResult) {
|
|
4355
|
+
let {
|
|
4356
|
+
result,
|
|
4357
|
+
type,
|
|
4358
|
+
status
|
|
4359
|
+
} = handlerResult;
|
|
4177
4360
|
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
4361
|
let data;
|
|
4228
4362
|
try {
|
|
4229
4363
|
let contentType = result.headers.get("Content-Type");
|
|
@@ -4244,10 +4378,11 @@ async function callLoaderOrAction(type, request, match, matches, manifest, mapRo
|
|
|
4244
4378
|
error: e
|
|
4245
4379
|
};
|
|
4246
4380
|
}
|
|
4247
|
-
if (
|
|
4381
|
+
if (type === ResultType.error) {
|
|
4248
4382
|
return {
|
|
4249
|
-
type:
|
|
4250
|
-
error: new ErrorResponseImpl(status, result.statusText, data),
|
|
4383
|
+
type: ResultType.error,
|
|
4384
|
+
error: new ErrorResponseImpl(result.status, result.statusText, data),
|
|
4385
|
+
statusCode: result.status,
|
|
4251
4386
|
headers: result.headers
|
|
4252
4387
|
};
|
|
4253
4388
|
}
|
|
@@ -4258,10 +4393,11 @@ async function callLoaderOrAction(type, request, match, matches, manifest, mapRo
|
|
|
4258
4393
|
headers: result.headers
|
|
4259
4394
|
};
|
|
4260
4395
|
}
|
|
4261
|
-
if (
|
|
4396
|
+
if (type === ResultType.error) {
|
|
4262
4397
|
return {
|
|
4263
|
-
type:
|
|
4264
|
-
error: result
|
|
4398
|
+
type: ResultType.error,
|
|
4399
|
+
error: result,
|
|
4400
|
+
statusCode: isRouteErrorResponse(result) ? result.status : status
|
|
4265
4401
|
};
|
|
4266
4402
|
}
|
|
4267
4403
|
if (isDeferredData(result)) {
|
|
@@ -4275,10 +4411,35 @@ async function callLoaderOrAction(type, request, match, matches, manifest, mapRo
|
|
|
4275
4411
|
}
|
|
4276
4412
|
return {
|
|
4277
4413
|
type: ResultType.data,
|
|
4278
|
-
data: result
|
|
4414
|
+
data: result,
|
|
4415
|
+
statusCode: status
|
|
4279
4416
|
};
|
|
4280
4417
|
}
|
|
4281
4418
|
|
|
4419
|
+
// Support relative routing in internal redirects
|
|
4420
|
+
function normalizeRelativeRoutingRedirectResponse(response, request, routeId, matches, basename, v7_relativeSplatPath) {
|
|
4421
|
+
let location = response.headers.get("Location");
|
|
4422
|
+
invariant(location, "Redirects returned/thrown from loaders/actions must have a Location header");
|
|
4423
|
+
if (!ABSOLUTE_URL_REGEX.test(location)) {
|
|
4424
|
+
let trimmedMatches = matches.slice(0, matches.findIndex(m => m.route.id === routeId) + 1);
|
|
4425
|
+
location = normalizeTo(new URL(request.url), trimmedMatches, basename, true, location, v7_relativeSplatPath);
|
|
4426
|
+
response.headers.set("Location", location);
|
|
4427
|
+
}
|
|
4428
|
+
return response;
|
|
4429
|
+
}
|
|
4430
|
+
function normalizeRedirectLocation(location, currentUrl, basename) {
|
|
4431
|
+
if (ABSOLUTE_URL_REGEX.test(location)) {
|
|
4432
|
+
// Strip off the protocol+origin for same-origin + same-basename absolute redirects
|
|
4433
|
+
let normalizedLocation = location;
|
|
4434
|
+
let url = normalizedLocation.startsWith("//") ? new URL(currentUrl.protocol + normalizedLocation) : new URL(normalizedLocation);
|
|
4435
|
+
let isSameBasename = stripBasename(url.pathname, basename) != null;
|
|
4436
|
+
if (url.origin === currentUrl.origin && isSameBasename) {
|
|
4437
|
+
return url.pathname + url.search + url.hash;
|
|
4438
|
+
}
|
|
4439
|
+
}
|
|
4440
|
+
return location;
|
|
4441
|
+
}
|
|
4442
|
+
|
|
4282
4443
|
// Utility method for creating the Request instances for loaders/actions during
|
|
4283
4444
|
// client-side navigations and fetches. During SSR we will always have a
|
|
4284
4445
|
// Request instance from the static handler (query/queryRoute)
|
|
@@ -4329,35 +4490,39 @@ function convertSearchParamsToFormData(searchParams) {
|
|
|
4329
4490
|
}
|
|
4330
4491
|
return formData;
|
|
4331
4492
|
}
|
|
4332
|
-
function processRouteLoaderData(matches, matchesToLoad, results,
|
|
4493
|
+
function processRouteLoaderData(matches, matchesToLoad, results, pendingActionResult, activeDeferreds, skipLoaderErrorBubbling) {
|
|
4333
4494
|
// Fill in loaderData/errors from our loaders
|
|
4334
4495
|
let loaderData = {};
|
|
4335
4496
|
let errors = null;
|
|
4336
4497
|
let statusCode;
|
|
4337
4498
|
let foundError = false;
|
|
4338
4499
|
let loaderHeaders = {};
|
|
4500
|
+
let pendingError = pendingActionResult && isErrorResult(pendingActionResult[1]) ? pendingActionResult[1].error : undefined;
|
|
4339
4501
|
|
|
4340
4502
|
// Process loader results into state.loaderData/state.errors
|
|
4341
4503
|
results.forEach((result, index) => {
|
|
4342
4504
|
let id = matchesToLoad[index].route.id;
|
|
4343
4505
|
invariant(!isRedirectResult(result), "Cannot handle redirect results in processLoaderData");
|
|
4344
4506
|
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
4507
|
let error = result.error;
|
|
4349
4508
|
// If we have a pending action error, we report it at the highest-route
|
|
4350
4509
|
// that throws a loader error, and then clear it out to indicate that
|
|
4351
4510
|
// it was consumed
|
|
4352
|
-
if (pendingError) {
|
|
4353
|
-
error =
|
|
4511
|
+
if (pendingError !== undefined) {
|
|
4512
|
+
error = pendingError;
|
|
4354
4513
|
pendingError = undefined;
|
|
4355
4514
|
}
|
|
4356
4515
|
errors = errors || {};
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
4516
|
+
if (skipLoaderErrorBubbling) {
|
|
4517
|
+
errors[id] = error;
|
|
4518
|
+
} else {
|
|
4519
|
+
// Look upwards from the matched route for the closest ancestor error
|
|
4520
|
+
// boundary, defaulting to the root match. Prefer higher error values
|
|
4521
|
+
// if lower errors bubble to the same boundary
|
|
4522
|
+
let boundaryMatch = findNearestBoundary(matches, id);
|
|
4523
|
+
if (errors[boundaryMatch.route.id] == null) {
|
|
4524
|
+
errors[boundaryMatch.route.id] = error;
|
|
4525
|
+
}
|
|
4361
4526
|
}
|
|
4362
4527
|
|
|
4363
4528
|
// Clear our any prior loaderData for the throwing route
|
|
@@ -4376,17 +4541,24 @@ function processRouteLoaderData(matches, matchesToLoad, results, pendingError, a
|
|
|
4376
4541
|
if (isDeferredResult(result)) {
|
|
4377
4542
|
activeDeferreds.set(id, result.deferredData);
|
|
4378
4543
|
loaderData[id] = result.deferredData.data;
|
|
4544
|
+
// Error status codes always override success status codes, but if all
|
|
4545
|
+
// loaders are successful we take the deepest status code.
|
|
4546
|
+
if (result.statusCode != null && result.statusCode !== 200 && !foundError) {
|
|
4547
|
+
statusCode = result.statusCode;
|
|
4548
|
+
}
|
|
4549
|
+
if (result.headers) {
|
|
4550
|
+
loaderHeaders[id] = result.headers;
|
|
4551
|
+
}
|
|
4379
4552
|
} else {
|
|
4380
4553
|
loaderData[id] = result.data;
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
loaderHeaders[id] = result.headers;
|
|
4554
|
+
// Error status codes always override success status codes, but if all
|
|
4555
|
+
// loaders are successful we take the deepest status code.
|
|
4556
|
+
if (result.statusCode && result.statusCode !== 200 && !foundError) {
|
|
4557
|
+
statusCode = result.statusCode;
|
|
4558
|
+
}
|
|
4559
|
+
if (result.headers) {
|
|
4560
|
+
loaderHeaders[id] = result.headers;
|
|
4561
|
+
}
|
|
4390
4562
|
}
|
|
4391
4563
|
}
|
|
4392
4564
|
});
|
|
@@ -4394,9 +4566,11 @@ function processRouteLoaderData(matches, matchesToLoad, results, pendingError, a
|
|
|
4394
4566
|
// If we didn't consume the pending action error (i.e., all loaders
|
|
4395
4567
|
// resolved), then consume it here. Also clear out any loaderData for the
|
|
4396
4568
|
// throwing route
|
|
4397
|
-
if (pendingError) {
|
|
4398
|
-
errors =
|
|
4399
|
-
|
|
4569
|
+
if (pendingError !== undefined && pendingActionResult) {
|
|
4570
|
+
errors = {
|
|
4571
|
+
[pendingActionResult[0]]: pendingError
|
|
4572
|
+
};
|
|
4573
|
+
loaderData[pendingActionResult[0]] = undefined;
|
|
4400
4574
|
}
|
|
4401
4575
|
return {
|
|
4402
4576
|
loaderData,
|
|
@@ -4405,11 +4579,12 @@ function processRouteLoaderData(matches, matchesToLoad, results, pendingError, a
|
|
|
4405
4579
|
loaderHeaders
|
|
4406
4580
|
};
|
|
4407
4581
|
}
|
|
4408
|
-
function processLoaderData(state, matches, matchesToLoad, results,
|
|
4582
|
+
function processLoaderData(state, matches, matchesToLoad, results, pendingActionResult, revalidatingFetchers, fetcherResults, activeDeferreds) {
|
|
4409
4583
|
let {
|
|
4410
4584
|
loaderData,
|
|
4411
4585
|
errors
|
|
4412
|
-
} = processRouteLoaderData(matches, matchesToLoad, results,
|
|
4586
|
+
} = processRouteLoaderData(matches, matchesToLoad, results, pendingActionResult, activeDeferreds, false // This method is only called client side so we always want to bubble
|
|
4587
|
+
);
|
|
4413
4588
|
|
|
4414
4589
|
// Process results from our revalidating fetchers
|
|
4415
4590
|
for (let index = 0; index < revalidatingFetchers.length; index++) {
|
|
@@ -4471,6 +4646,19 @@ function mergeLoaderData(loaderData, newLoaderData, matches, errors) {
|
|
|
4471
4646
|
}
|
|
4472
4647
|
return mergedLoaderData;
|
|
4473
4648
|
}
|
|
4649
|
+
function getActionDataForCommit(pendingActionResult) {
|
|
4650
|
+
if (!pendingActionResult) {
|
|
4651
|
+
return {};
|
|
4652
|
+
}
|
|
4653
|
+
return isErrorResult(pendingActionResult[1]) ? {
|
|
4654
|
+
// Clear out prior actionData on errors
|
|
4655
|
+
actionData: {}
|
|
4656
|
+
} : {
|
|
4657
|
+
actionData: {
|
|
4658
|
+
[pendingActionResult[0]]: pendingActionResult[1].data
|
|
4659
|
+
}
|
|
4660
|
+
};
|
|
4661
|
+
}
|
|
4474
4662
|
|
|
4475
4663
|
// Find the nearest error boundary, looking upwards from the leaf route (or the
|
|
4476
4664
|
// route specified by routeId) for the closest ancestor error boundary,
|
|
@@ -4566,6 +4754,12 @@ function isHashChangeOnly(a, b) {
|
|
|
4566
4754
|
// /page#hash -> /page
|
|
4567
4755
|
return false;
|
|
4568
4756
|
}
|
|
4757
|
+
function isHandlerResult(result) {
|
|
4758
|
+
return result != null && typeof result === "object" && "type" in result && "result" in result && (result.type === ResultType.data || result.type === ResultType.error);
|
|
4759
|
+
}
|
|
4760
|
+
function isRedirectHandlerResult(result) {
|
|
4761
|
+
return isResponse(result.result) && redirectStatusCodes.has(result.result.status);
|
|
4762
|
+
}
|
|
4569
4763
|
function isDeferredResult(result) {
|
|
4570
4764
|
return result.type === ResultType.deferred;
|
|
4571
4765
|
}
|
|
@@ -4590,9 +4784,6 @@ function isRedirectResponse(result) {
|
|
|
4590
4784
|
let location = result.headers.get("Location");
|
|
4591
4785
|
return status >= 300 && status <= 399 && location != null;
|
|
4592
4786
|
}
|
|
4593
|
-
function isQueryRouteResponse(obj) {
|
|
4594
|
-
return obj && isResponse(obj.response) && (obj.type === ResultType.data || obj.type === ResultType.error);
|
|
4595
|
-
}
|
|
4596
4787
|
function isValidMethod(method) {
|
|
4597
4788
|
return validRequestMethods.has(method.toLowerCase());
|
|
4598
4789
|
}
|