@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.umd.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
|
*
|
|
@@ -579,6 +579,10 @@
|
|
|
579
579
|
* Result from a loader or action - potentially successful or unsuccessful
|
|
580
580
|
*/
|
|
581
581
|
|
|
582
|
+
/**
|
|
583
|
+
* Result from a loader or action called via dataStrategy
|
|
584
|
+
*/
|
|
585
|
+
|
|
582
586
|
/**
|
|
583
587
|
* Users can specify either lowercase or uppercase form methods on `<Form>`,
|
|
584
588
|
* useSubmit(), `<fetcher.Form>`, etc.
|
|
@@ -1595,11 +1599,6 @@
|
|
|
1595
1599
|
/**
|
|
1596
1600
|
* Identified fetcher.load() calls that need to be revalidated
|
|
1597
1601
|
*/
|
|
1598
|
-
/**
|
|
1599
|
-
* Wrapper object to allow us to throw any response out from callLoaderOrAction
|
|
1600
|
-
* for queryRouter while preserving whether or not it was thrown or returned
|
|
1601
|
-
* from the loader/action
|
|
1602
|
-
*/
|
|
1603
1602
|
const validMutationMethodsArr = ["post", "put", "patch", "delete"];
|
|
1604
1603
|
const validMutationMethods = new Set(validMutationMethodsArr);
|
|
1605
1604
|
const validRequestMethodsArr = ["get", ...validMutationMethodsArr];
|
|
@@ -1671,13 +1670,15 @@
|
|
|
1671
1670
|
let dataRoutes = convertRoutesToDataRoutes(init.routes, mapRouteProperties, undefined, manifest);
|
|
1672
1671
|
let inFlightDataRoutes;
|
|
1673
1672
|
let basename = init.basename || "/";
|
|
1673
|
+
let dataStrategyImpl = init.unstable_dataStrategy || defaultDataStrategy;
|
|
1674
1674
|
// Config driven behavior flags
|
|
1675
1675
|
let future = _extends({
|
|
1676
1676
|
v7_fetcherPersist: false,
|
|
1677
1677
|
v7_normalizeFormMethod: false,
|
|
1678
1678
|
v7_partialHydration: false,
|
|
1679
1679
|
v7_prependBasename: false,
|
|
1680
|
-
v7_relativeSplatPath: false
|
|
1680
|
+
v7_relativeSplatPath: false,
|
|
1681
|
+
unstable_skipActionErrorRevalidation: false
|
|
1681
1682
|
}, init.future);
|
|
1682
1683
|
// Cleanup function for history
|
|
1683
1684
|
let unlistenHistory = null;
|
|
@@ -1731,9 +1732,13 @@
|
|
|
1731
1732
|
let errors = init.hydrationData ? init.hydrationData.errors : null;
|
|
1732
1733
|
let isRouteInitialized = m => {
|
|
1733
1734
|
// No loader, nothing to initialize
|
|
1734
|
-
if (!m.route.loader)
|
|
1735
|
+
if (!m.route.loader) {
|
|
1736
|
+
return true;
|
|
1737
|
+
}
|
|
1735
1738
|
// Explicitly opting-in to running on hydration
|
|
1736
|
-
if (m.route.loader.hydrate === true)
|
|
1739
|
+
if (typeof m.route.loader === "function" && m.route.loader.hydrate === true) {
|
|
1740
|
+
return false;
|
|
1741
|
+
}
|
|
1737
1742
|
// Otherwise, initialized if hydrated with data or an error
|
|
1738
1743
|
return loaderData && loaderData[m.route.id] !== undefined || errors && errors[m.route.id] !== undefined;
|
|
1739
1744
|
};
|
|
@@ -2274,34 +2279,31 @@
|
|
|
2274
2279
|
// Create a controller/Request for this navigation
|
|
2275
2280
|
pendingNavigationController = new AbortController();
|
|
2276
2281
|
let request = createClientSideRequest(init.history, location, pendingNavigationController.signal, opts && opts.submission);
|
|
2277
|
-
let
|
|
2278
|
-
let pendingError;
|
|
2282
|
+
let pendingActionResult;
|
|
2279
2283
|
if (opts && opts.pendingError) {
|
|
2280
2284
|
// If we have a pendingError, it means the user attempted a GET submission
|
|
2281
2285
|
// with binary FormData so assign here and skip to handleLoaders. That
|
|
2282
2286
|
// way we handle calling loaders above the boundary etc. It's not really
|
|
2283
2287
|
// different from an actionError in that sense.
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2288
|
+
pendingActionResult = [findNearestBoundary(matches).route.id, {
|
|
2289
|
+
type: ResultType.error,
|
|
2290
|
+
error: opts.pendingError
|
|
2291
|
+
}];
|
|
2287
2292
|
} else if (opts && opts.submission && isMutationMethod(opts.submission.formMethod)) {
|
|
2288
2293
|
// Call action if we received an action submission
|
|
2289
|
-
let
|
|
2294
|
+
let actionResult = await handleAction(request, location, opts.submission, matches, {
|
|
2290
2295
|
replace: opts.replace,
|
|
2291
2296
|
flushSync
|
|
2292
2297
|
});
|
|
2293
|
-
if (
|
|
2298
|
+
if (actionResult.shortCircuited) {
|
|
2294
2299
|
return;
|
|
2295
2300
|
}
|
|
2296
|
-
|
|
2297
|
-
pendingError = actionOutput.pendingActionError;
|
|
2301
|
+
pendingActionResult = actionResult.pendingActionResult;
|
|
2298
2302
|
loadingNavigation = getLoadingNavigation(location, opts.submission);
|
|
2299
2303
|
flushSync = false;
|
|
2300
2304
|
|
|
2301
2305
|
// Create a GET request for the loaders
|
|
2302
|
-
request =
|
|
2303
|
-
signal: request.signal
|
|
2304
|
-
});
|
|
2306
|
+
request = createClientSideRequest(init.history, request.url, request.signal);
|
|
2305
2307
|
}
|
|
2306
2308
|
|
|
2307
2309
|
// Call loaders
|
|
@@ -2309,7 +2311,7 @@
|
|
|
2309
2311
|
shortCircuited,
|
|
2310
2312
|
loaderData,
|
|
2311
2313
|
errors
|
|
2312
|
-
} = await handleLoaders(request, location, matches, loadingNavigation, opts && opts.submission, opts && opts.fetcherSubmission, opts && opts.replace, opts && opts.initialHydration === true, flushSync,
|
|
2314
|
+
} = await handleLoaders(request, location, matches, loadingNavigation, opts && opts.submission, opts && opts.fetcherSubmission, opts && opts.replace, opts && opts.initialHydration === true, flushSync, pendingActionResult);
|
|
2313
2315
|
if (shortCircuited) {
|
|
2314
2316
|
return;
|
|
2315
2317
|
}
|
|
@@ -2320,9 +2322,7 @@
|
|
|
2320
2322
|
pendingNavigationController = null;
|
|
2321
2323
|
completeNavigation(location, _extends({
|
|
2322
2324
|
matches
|
|
2323
|
-
},
|
|
2324
|
-
actionData: pendingActionData
|
|
2325
|
-
} : {}, {
|
|
2325
|
+
}, getActionDataForCommit(pendingActionResult), {
|
|
2326
2326
|
loaderData,
|
|
2327
2327
|
errors
|
|
2328
2328
|
}));
|
|
@@ -2357,7 +2357,8 @@
|
|
|
2357
2357
|
})
|
|
2358
2358
|
};
|
|
2359
2359
|
} else {
|
|
2360
|
-
|
|
2360
|
+
let results = await callDataStrategy("action", request, [actionMatch], matches);
|
|
2361
|
+
result = results[0];
|
|
2361
2362
|
if (request.signal.aborted) {
|
|
2362
2363
|
return {
|
|
2363
2364
|
shortCircuited: true
|
|
@@ -2372,9 +2373,10 @@
|
|
|
2372
2373
|
// If the user didn't explicity indicate replace behavior, replace if
|
|
2373
2374
|
// we redirected to the exact same location we're currently at to avoid
|
|
2374
2375
|
// double back-buttons
|
|
2375
|
-
|
|
2376
|
+
let location = normalizeRedirectLocation(result.response.headers.get("Location"), new URL(request.url), basename);
|
|
2377
|
+
replace = location === state.location.pathname + state.location.search;
|
|
2376
2378
|
}
|
|
2377
|
-
await startRedirectNavigation(
|
|
2379
|
+
await startRedirectNavigation(request, result, {
|
|
2378
2380
|
submission,
|
|
2379
2381
|
replace
|
|
2380
2382
|
});
|
|
@@ -2382,6 +2384,11 @@
|
|
|
2382
2384
|
shortCircuited: true
|
|
2383
2385
|
};
|
|
2384
2386
|
}
|
|
2387
|
+
if (isDeferredResult(result)) {
|
|
2388
|
+
throw getInternalRouterError(400, {
|
|
2389
|
+
type: "defer-action"
|
|
2390
|
+
});
|
|
2391
|
+
}
|
|
2385
2392
|
if (isErrorResult(result)) {
|
|
2386
2393
|
// Store off the pending error - we use it to determine which loaders
|
|
2387
2394
|
// to call and will commit it when we complete the navigation
|
|
@@ -2395,28 +2402,17 @@
|
|
|
2395
2402
|
pendingAction = Action.Push;
|
|
2396
2403
|
}
|
|
2397
2404
|
return {
|
|
2398
|
-
|
|
2399
|
-
pendingActionData: {},
|
|
2400
|
-
pendingActionError: {
|
|
2401
|
-
[boundaryMatch.route.id]: result.error
|
|
2402
|
-
}
|
|
2405
|
+
pendingActionResult: [boundaryMatch.route.id, result]
|
|
2403
2406
|
};
|
|
2404
2407
|
}
|
|
2405
|
-
if (isDeferredResult(result)) {
|
|
2406
|
-
throw getInternalRouterError(400, {
|
|
2407
|
-
type: "defer-action"
|
|
2408
|
-
});
|
|
2409
|
-
}
|
|
2410
2408
|
return {
|
|
2411
|
-
|
|
2412
|
-
[actionMatch.route.id]: result.data
|
|
2413
|
-
}
|
|
2409
|
+
pendingActionResult: [actionMatch.route.id, result]
|
|
2414
2410
|
};
|
|
2415
2411
|
}
|
|
2416
2412
|
|
|
2417
2413
|
// Call all applicable loaders for the given matches, handling redirects,
|
|
2418
2414
|
// errors, etc.
|
|
2419
|
-
async function handleLoaders(request, location, matches, overrideNavigation, submission, fetcherSubmission, replace, initialHydration, flushSync,
|
|
2415
|
+
async function handleLoaders(request, location, matches, overrideNavigation, submission, fetcherSubmission, replace, initialHydration, flushSync, pendingActionResult) {
|
|
2420
2416
|
// Figure out the right navigation we want to use for data loading
|
|
2421
2417
|
let loadingNavigation = overrideNavigation || getLoadingNavigation(location, submission);
|
|
2422
2418
|
|
|
@@ -2424,7 +2420,7 @@
|
|
|
2424
2420
|
// we have it on the loading navigation so use that if available
|
|
2425
2421
|
let activeSubmission = submission || fetcherSubmission || getSubmissionFromNavigation(loadingNavigation);
|
|
2426
2422
|
let routesToUse = inFlightDataRoutes || dataRoutes;
|
|
2427
|
-
let [matchesToLoad, revalidatingFetchers] = getMatchesToLoad(init.history, state, matches, activeSubmission, location, future.v7_partialHydration && initialHydration === true, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, deletedFetchers, fetchLoadMatches, fetchRedirectIds, routesToUse, basename,
|
|
2423
|
+
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);
|
|
2428
2424
|
|
|
2429
2425
|
// Cancel pending deferreds for no-longer-matched routes or routes we're
|
|
2430
2426
|
// about to reload. Note that if this is an action reload we would have
|
|
@@ -2439,10 +2435,10 @@
|
|
|
2439
2435
|
matches,
|
|
2440
2436
|
loaderData: {},
|
|
2441
2437
|
// Commit pending error if we're short circuiting
|
|
2442
|
-
errors:
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
}
|
|
2438
|
+
errors: pendingActionResult && isErrorResult(pendingActionResult[1]) ? {
|
|
2439
|
+
[pendingActionResult[0]]: pendingActionResult[1].error
|
|
2440
|
+
} : null
|
|
2441
|
+
}, getActionDataForCommit(pendingActionResult), updatedFetchers ? {
|
|
2446
2442
|
fetchers: new Map(state.fetchers)
|
|
2447
2443
|
} : {}), {
|
|
2448
2444
|
flushSync
|
|
@@ -2464,12 +2460,24 @@
|
|
|
2464
2460
|
let revalidatingFetcher = getLoadingFetcher(undefined, fetcher ? fetcher.data : undefined);
|
|
2465
2461
|
state.fetchers.set(rf.key, revalidatingFetcher);
|
|
2466
2462
|
});
|
|
2467
|
-
let actionData
|
|
2463
|
+
let actionData;
|
|
2464
|
+
if (pendingActionResult && !isErrorResult(pendingActionResult[1])) {
|
|
2465
|
+
// This is cast to `any` currently because `RouteData`uses any and it
|
|
2466
|
+
// would be a breaking change to use any.
|
|
2467
|
+
// TODO: v7 - change `RouteData` to use `unknown` instead of `any`
|
|
2468
|
+
actionData = {
|
|
2469
|
+
[pendingActionResult[0]]: pendingActionResult[1].data
|
|
2470
|
+
};
|
|
2471
|
+
} else if (state.actionData) {
|
|
2472
|
+
if (Object.keys(state.actionData).length === 0) {
|
|
2473
|
+
actionData = null;
|
|
2474
|
+
} else {
|
|
2475
|
+
actionData = state.actionData;
|
|
2476
|
+
}
|
|
2477
|
+
}
|
|
2468
2478
|
updateState(_extends({
|
|
2469
2479
|
navigation: loadingNavigation
|
|
2470
|
-
}, actionData
|
|
2471
|
-
actionData: null
|
|
2472
|
-
} : {
|
|
2480
|
+
}, actionData !== undefined ? {
|
|
2473
2481
|
actionData
|
|
2474
2482
|
} : {}, revalidatingFetchers.length > 0 ? {
|
|
2475
2483
|
fetchers: new Map(state.fetchers)
|
|
@@ -2495,7 +2503,6 @@
|
|
|
2495
2503
|
pendingNavigationController.signal.addEventListener("abort", abortPendingFetchRevalidations);
|
|
2496
2504
|
}
|
|
2497
2505
|
let {
|
|
2498
|
-
results,
|
|
2499
2506
|
loaderResults,
|
|
2500
2507
|
fetcherResults
|
|
2501
2508
|
} = await callLoadersAndMaybeResolveData(state.matches, matches, matchesToLoad, revalidatingFetchers, request);
|
|
@@ -2514,7 +2521,7 @@
|
|
|
2514
2521
|
revalidatingFetchers.forEach(rf => fetchControllers.delete(rf.key));
|
|
2515
2522
|
|
|
2516
2523
|
// If any loaders returned a redirect Response, start a new REPLACE navigation
|
|
2517
|
-
let redirect = findRedirect(
|
|
2524
|
+
let redirect = findRedirect([...loaderResults, ...fetcherResults]);
|
|
2518
2525
|
if (redirect) {
|
|
2519
2526
|
if (redirect.idx >= matchesToLoad.length) {
|
|
2520
2527
|
// If this redirect came from a fetcher make sure we mark it in
|
|
@@ -2523,7 +2530,7 @@
|
|
|
2523
2530
|
let fetcherKey = revalidatingFetchers[redirect.idx - matchesToLoad.length].key;
|
|
2524
2531
|
fetchRedirectIds.add(fetcherKey);
|
|
2525
2532
|
}
|
|
2526
|
-
await startRedirectNavigation(
|
|
2533
|
+
await startRedirectNavigation(request, redirect.result, {
|
|
2527
2534
|
replace
|
|
2528
2535
|
});
|
|
2529
2536
|
return {
|
|
@@ -2535,7 +2542,7 @@
|
|
|
2535
2542
|
let {
|
|
2536
2543
|
loaderData,
|
|
2537
2544
|
errors
|
|
2538
|
-
} = processLoaderData(state, matches, matchesToLoad, loaderResults,
|
|
2545
|
+
} = processLoaderData(state, matches, matchesToLoad, loaderResults, pendingActionResult, revalidatingFetchers, fetcherResults, activeDeferreds);
|
|
2539
2546
|
|
|
2540
2547
|
// Wire up subscribers to update loaderData as promises settle
|
|
2541
2548
|
activeDeferreds.forEach((deferredData, routeId) => {
|
|
@@ -2645,7 +2652,8 @@
|
|
|
2645
2652
|
let fetchRequest = createClientSideRequest(init.history, path, abortController.signal, submission);
|
|
2646
2653
|
fetchControllers.set(key, abortController);
|
|
2647
2654
|
let originatingLoadId = incrementingLoadId;
|
|
2648
|
-
let
|
|
2655
|
+
let actionResults = await callDataStrategy("action", fetchRequest, [match], requestMatches);
|
|
2656
|
+
let actionResult = actionResults[0];
|
|
2649
2657
|
if (fetchRequest.signal.aborted) {
|
|
2650
2658
|
// We can delete this so long as we weren't aborted by our own fetcher
|
|
2651
2659
|
// re-submit which would have put _new_ controller is in fetchControllers
|
|
@@ -2677,7 +2685,7 @@
|
|
|
2677
2685
|
} else {
|
|
2678
2686
|
fetchRedirectIds.add(key);
|
|
2679
2687
|
updateFetcherState(key, getLoadingFetcher(submission));
|
|
2680
|
-
return startRedirectNavigation(
|
|
2688
|
+
return startRedirectNavigation(fetchRequest, actionResult, {
|
|
2681
2689
|
fetcherSubmission: submission
|
|
2682
2690
|
});
|
|
2683
2691
|
}
|
|
@@ -2706,10 +2714,7 @@
|
|
|
2706
2714
|
fetchReloadIds.set(key, loadId);
|
|
2707
2715
|
let loadFetcher = getLoadingFetcher(submission, actionResult.data);
|
|
2708
2716
|
state.fetchers.set(key, loadFetcher);
|
|
2709
|
-
let [matchesToLoad, revalidatingFetchers] = getMatchesToLoad(init.history, state, matches, submission, nextLocation, false, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, deletedFetchers, fetchLoadMatches, fetchRedirectIds, routesToUse, basename,
|
|
2710
|
-
[match.route.id]: actionResult.data
|
|
2711
|
-
}, undefined // No need to send through errors since we short circuit above
|
|
2712
|
-
);
|
|
2717
|
+
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]);
|
|
2713
2718
|
|
|
2714
2719
|
// Put all revalidating fetchers into the loading state, except for the
|
|
2715
2720
|
// current fetcher which we want to keep in it's current loading state which
|
|
@@ -2732,7 +2737,6 @@
|
|
|
2732
2737
|
let abortPendingFetchRevalidations = () => revalidatingFetchers.forEach(rf => abortFetcher(rf.key));
|
|
2733
2738
|
abortController.signal.addEventListener("abort", abortPendingFetchRevalidations);
|
|
2734
2739
|
let {
|
|
2735
|
-
results,
|
|
2736
2740
|
loaderResults,
|
|
2737
2741
|
fetcherResults
|
|
2738
2742
|
} = await callLoadersAndMaybeResolveData(state.matches, matches, matchesToLoad, revalidatingFetchers, revalidationRequest);
|
|
@@ -2743,7 +2747,7 @@
|
|
|
2743
2747
|
fetchReloadIds.delete(key);
|
|
2744
2748
|
fetchControllers.delete(key);
|
|
2745
2749
|
revalidatingFetchers.forEach(r => fetchControllers.delete(r.key));
|
|
2746
|
-
let redirect = findRedirect(
|
|
2750
|
+
let redirect = findRedirect([...loaderResults, ...fetcherResults]);
|
|
2747
2751
|
if (redirect) {
|
|
2748
2752
|
if (redirect.idx >= matchesToLoad.length) {
|
|
2749
2753
|
// If this redirect came from a fetcher make sure we mark it in
|
|
@@ -2752,7 +2756,7 @@
|
|
|
2752
2756
|
let fetcherKey = revalidatingFetchers[redirect.idx - matchesToLoad.length].key;
|
|
2753
2757
|
fetchRedirectIds.add(fetcherKey);
|
|
2754
2758
|
}
|
|
2755
|
-
return startRedirectNavigation(
|
|
2759
|
+
return startRedirectNavigation(revalidationRequest, redirect.result);
|
|
2756
2760
|
}
|
|
2757
2761
|
|
|
2758
2762
|
// Process and commit output from loaders
|
|
@@ -2806,7 +2810,8 @@
|
|
|
2806
2810
|
let fetchRequest = createClientSideRequest(init.history, path, abortController.signal);
|
|
2807
2811
|
fetchControllers.set(key, abortController);
|
|
2808
2812
|
let originatingLoadId = incrementingLoadId;
|
|
2809
|
-
let
|
|
2813
|
+
let results = await callDataStrategy("loader", fetchRequest, [match], matches);
|
|
2814
|
+
let result = results[0];
|
|
2810
2815
|
|
|
2811
2816
|
// Deferred isn't supported for fetcher loads, await everything and treat it
|
|
2812
2817
|
// as a normal load. resolveDeferredData will return undefined if this
|
|
@@ -2841,7 +2846,7 @@
|
|
|
2841
2846
|
return;
|
|
2842
2847
|
} else {
|
|
2843
2848
|
fetchRedirectIds.add(key);
|
|
2844
|
-
await startRedirectNavigation(
|
|
2849
|
+
await startRedirectNavigation(fetchRequest, result);
|
|
2845
2850
|
return;
|
|
2846
2851
|
}
|
|
2847
2852
|
}
|
|
@@ -2876,26 +2881,28 @@
|
|
|
2876
2881
|
* actually touch history until we've processed redirects, so we just use
|
|
2877
2882
|
* the history action from the original navigation (PUSH or REPLACE).
|
|
2878
2883
|
*/
|
|
2879
|
-
async function startRedirectNavigation(
|
|
2884
|
+
async function startRedirectNavigation(request, redirect, _temp2) {
|
|
2880
2885
|
let {
|
|
2881
2886
|
submission,
|
|
2882
2887
|
fetcherSubmission,
|
|
2883
2888
|
replace
|
|
2884
2889
|
} = _temp2 === void 0 ? {} : _temp2;
|
|
2885
|
-
if (redirect.
|
|
2890
|
+
if (redirect.response.headers.has("X-Remix-Revalidate")) {
|
|
2886
2891
|
isRevalidationRequired = true;
|
|
2887
2892
|
}
|
|
2888
|
-
let
|
|
2893
|
+
let location = redirect.response.headers.get("Location");
|
|
2894
|
+
invariant(location, "Expected a Location header on the redirect Response");
|
|
2895
|
+
location = normalizeRedirectLocation(location, new URL(request.url), basename);
|
|
2896
|
+
let redirectLocation = createLocation(state.location, location, {
|
|
2889
2897
|
_isRedirect: true
|
|
2890
2898
|
});
|
|
2891
|
-
invariant(redirectLocation, "Expected a location on the redirect navigation");
|
|
2892
2899
|
if (isBrowser) {
|
|
2893
2900
|
let isDocumentReload = false;
|
|
2894
|
-
if (redirect.
|
|
2901
|
+
if (redirect.response.headers.has("X-Remix-Reload-Document")) {
|
|
2895
2902
|
// Hard reload if the response contained X-Remix-Reload-Document
|
|
2896
2903
|
isDocumentReload = true;
|
|
2897
|
-
} else if (ABSOLUTE_URL_REGEX.test(
|
|
2898
|
-
const url = init.history.createURL(
|
|
2904
|
+
} else if (ABSOLUTE_URL_REGEX.test(location)) {
|
|
2905
|
+
const url = init.history.createURL(location);
|
|
2899
2906
|
isDocumentReload =
|
|
2900
2907
|
// Hard reload if it's an absolute URL to a new origin
|
|
2901
2908
|
url.origin !== routerWindow.location.origin ||
|
|
@@ -2904,9 +2911,9 @@
|
|
|
2904
2911
|
}
|
|
2905
2912
|
if (isDocumentReload) {
|
|
2906
2913
|
if (replace) {
|
|
2907
|
-
routerWindow.location.replace(
|
|
2914
|
+
routerWindow.location.replace(location);
|
|
2908
2915
|
} else {
|
|
2909
|
-
routerWindow.location.assign(
|
|
2916
|
+
routerWindow.location.assign(location);
|
|
2910
2917
|
}
|
|
2911
2918
|
return;
|
|
2912
2919
|
}
|
|
@@ -2932,10 +2939,10 @@
|
|
|
2932
2939
|
// re-submit the GET/POST/PUT/PATCH/DELETE as a submission navigation to the
|
|
2933
2940
|
// redirected location
|
|
2934
2941
|
let activeSubmission = submission || fetcherSubmission;
|
|
2935
|
-
if (redirectPreserveMethodStatusCodes.has(redirect.status) && activeSubmission && isMutationMethod(activeSubmission.formMethod)) {
|
|
2942
|
+
if (redirectPreserveMethodStatusCodes.has(redirect.response.status) && activeSubmission && isMutationMethod(activeSubmission.formMethod)) {
|
|
2936
2943
|
await startNavigation(redirectHistoryAction, redirectLocation, {
|
|
2937
2944
|
submission: _extends({}, activeSubmission, {
|
|
2938
|
-
formAction:
|
|
2945
|
+
formAction: location
|
|
2939
2946
|
}),
|
|
2940
2947
|
// Preserve this flag across redirects
|
|
2941
2948
|
preventScrollReset: pendingPreventScrollReset
|
|
@@ -2953,28 +2960,47 @@
|
|
|
2953
2960
|
});
|
|
2954
2961
|
}
|
|
2955
2962
|
}
|
|
2963
|
+
|
|
2964
|
+
// Utility wrapper for calling dataStrategy client-side without having to
|
|
2965
|
+
// pass around the manifest, mapRouteProperties, etc.
|
|
2966
|
+
async function callDataStrategy(type, request, matchesToLoad, matches) {
|
|
2967
|
+
try {
|
|
2968
|
+
let results = await callDataStrategyImpl(dataStrategyImpl, type, request, matchesToLoad, matches, manifest, mapRouteProperties);
|
|
2969
|
+
return await Promise.all(results.map((result, i) => {
|
|
2970
|
+
if (isRedirectHandlerResult(result)) {
|
|
2971
|
+
let response = result.result;
|
|
2972
|
+
return {
|
|
2973
|
+
type: ResultType.redirect,
|
|
2974
|
+
response: normalizeRelativeRoutingRedirectResponse(response, request, matchesToLoad[i].route.id, matches, basename, future.v7_relativeSplatPath)
|
|
2975
|
+
};
|
|
2976
|
+
}
|
|
2977
|
+
return convertHandlerResultToDataResult(result);
|
|
2978
|
+
}));
|
|
2979
|
+
} catch (e) {
|
|
2980
|
+
// If the outer dataStrategy method throws, just return the error for all
|
|
2981
|
+
// matches - and it'll naturally bubble to the root
|
|
2982
|
+
return matchesToLoad.map(() => ({
|
|
2983
|
+
type: ResultType.error,
|
|
2984
|
+
error: e
|
|
2985
|
+
}));
|
|
2986
|
+
}
|
|
2987
|
+
}
|
|
2956
2988
|
async function callLoadersAndMaybeResolveData(currentMatches, matches, matchesToLoad, fetchersToLoad, request) {
|
|
2957
|
-
|
|
2958
|
-
// then slice off the results into separate arrays so we can handle them
|
|
2959
|
-
// accordingly
|
|
2960
|
-
let results = await Promise.all([...matchesToLoad.map(match => callLoaderOrAction("loader", request, match, matches, manifest, mapRouteProperties, basename, future.v7_relativeSplatPath)), ...fetchersToLoad.map(f => {
|
|
2989
|
+
let [loaderResults, ...fetcherResults] = await Promise.all([matchesToLoad.length ? callDataStrategy("loader", request, matchesToLoad, matches) : [], ...fetchersToLoad.map(f => {
|
|
2961
2990
|
if (f.matches && f.match && f.controller) {
|
|
2962
|
-
|
|
2991
|
+
let fetcherRequest = createClientSideRequest(init.history, f.path, f.controller.signal);
|
|
2992
|
+
return callDataStrategy("loader", fetcherRequest, [f.match], f.matches).then(r => r[0]);
|
|
2963
2993
|
} else {
|
|
2964
|
-
|
|
2994
|
+
return Promise.resolve({
|
|
2965
2995
|
type: ResultType.error,
|
|
2966
2996
|
error: getInternalRouterError(404, {
|
|
2967
2997
|
pathname: f.path
|
|
2968
2998
|
})
|
|
2969
|
-
};
|
|
2970
|
-
return error;
|
|
2999
|
+
});
|
|
2971
3000
|
}
|
|
2972
3001
|
})]);
|
|
2973
|
-
let loaderResults = results.slice(0, matchesToLoad.length);
|
|
2974
|
-
let fetcherResults = results.slice(matchesToLoad.length);
|
|
2975
3002
|
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)]);
|
|
2976
3003
|
return {
|
|
2977
|
-
results,
|
|
2978
3004
|
loaderResults,
|
|
2979
3005
|
fetcherResults
|
|
2980
3006
|
};
|
|
@@ -3324,10 +3350,25 @@
|
|
|
3324
3350
|
* redirect response is returned or thrown from any action/loader. We
|
|
3325
3351
|
* propagate that out and return the raw Response so the HTTP server can
|
|
3326
3352
|
* return it directly.
|
|
3353
|
+
*
|
|
3354
|
+
* - `opts.loadRouteIds` is an optional array of routeIds to run only a subset of
|
|
3355
|
+
* loaders during a query() call
|
|
3356
|
+
* - `opts.requestContext` is an optional server context that will be passed
|
|
3357
|
+
* to actions/loaders in the `context` parameter
|
|
3358
|
+
* - `opts.skipLoaderErrorBubbling` is an optional parameter that will prevent
|
|
3359
|
+
* the bubbling of errors which allows single-fetch-type implementations
|
|
3360
|
+
* where the client will handle the bubbling and we may need to return data
|
|
3361
|
+
* for the handling route
|
|
3362
|
+
* - `opts.skipLoaders` is an optional parameter that will prevent loaders
|
|
3363
|
+
* from running after an action
|
|
3327
3364
|
*/
|
|
3328
3365
|
async function query(request, _temp3) {
|
|
3329
3366
|
let {
|
|
3330
|
-
|
|
3367
|
+
loadRouteIds,
|
|
3368
|
+
requestContext,
|
|
3369
|
+
skipLoaderErrorBubbling,
|
|
3370
|
+
skipLoaders,
|
|
3371
|
+
unstable_dataStrategy
|
|
3331
3372
|
} = _temp3 === void 0 ? {} : _temp3;
|
|
3332
3373
|
let url = new URL(request.url);
|
|
3333
3374
|
let method = request.method;
|
|
@@ -3380,7 +3421,7 @@
|
|
|
3380
3421
|
activeDeferreds: null
|
|
3381
3422
|
};
|
|
3382
3423
|
}
|
|
3383
|
-
let result = await queryImpl(request, location, matches, requestContext);
|
|
3424
|
+
let result = await queryImpl(request, location, matches, requestContext, unstable_dataStrategy || null, loadRouteIds || null, skipLoaderErrorBubbling === true, skipLoaders === true, null);
|
|
3384
3425
|
if (isResponse(result)) {
|
|
3385
3426
|
return result;
|
|
3386
3427
|
}
|
|
@@ -3413,6 +3454,12 @@
|
|
|
3413
3454
|
* serialize the error as they see fit while including the proper response
|
|
3414
3455
|
* code. Examples here are 404 and 405 errors that occur prior to reaching
|
|
3415
3456
|
* any user-defined loaders.
|
|
3457
|
+
*
|
|
3458
|
+
* - `opts.routeId` allows you to specify the specific route handler to call.
|
|
3459
|
+
* If not provided the handler will determine the proper route by matching
|
|
3460
|
+
* against `request.url`
|
|
3461
|
+
* - `opts.requestContext` is an optional server context that will be passed
|
|
3462
|
+
* to actions/loaders in the `context` parameter
|
|
3416
3463
|
*/
|
|
3417
3464
|
async function queryRoute(request, _temp4) {
|
|
3418
3465
|
let {
|
|
@@ -3446,7 +3493,7 @@
|
|
|
3446
3493
|
pathname: location.pathname
|
|
3447
3494
|
});
|
|
3448
3495
|
}
|
|
3449
|
-
let result = await queryImpl(request, location, matches, requestContext, match);
|
|
3496
|
+
let result = await queryImpl(request, location, matches, requestContext, null, null, false, false, match);
|
|
3450
3497
|
if (isResponse(result)) {
|
|
3451
3498
|
return result;
|
|
3452
3499
|
}
|
|
@@ -3473,27 +3520,27 @@
|
|
|
3473
3520
|
}
|
|
3474
3521
|
return undefined;
|
|
3475
3522
|
}
|
|
3476
|
-
async function queryImpl(request, location, matches, requestContext, routeMatch) {
|
|
3523
|
+
async function queryImpl(request, location, matches, requestContext, unstable_dataStrategy, loadRouteIds, skipLoaderErrorBubbling, skipLoaders, routeMatch) {
|
|
3477
3524
|
invariant(request.signal, "query()/queryRoute() requests must contain an AbortController signal");
|
|
3478
3525
|
try {
|
|
3479
3526
|
if (isMutationMethod(request.method.toLowerCase())) {
|
|
3480
|
-
let result = await submit(request, matches, routeMatch || getTargetMatch(matches, location), requestContext, routeMatch != null);
|
|
3527
|
+
let result = await submit(request, matches, routeMatch || getTargetMatch(matches, location), requestContext, unstable_dataStrategy, loadRouteIds, skipLoaderErrorBubbling, skipLoaders, routeMatch != null);
|
|
3481
3528
|
return result;
|
|
3482
3529
|
}
|
|
3483
|
-
let result = await loadRouteData(request, matches, requestContext, routeMatch);
|
|
3530
|
+
let result = await loadRouteData(request, matches, requestContext, unstable_dataStrategy, loadRouteIds, skipLoaderErrorBubbling, routeMatch);
|
|
3484
3531
|
return isResponse(result) ? result : _extends({}, result, {
|
|
3485
3532
|
actionData: null,
|
|
3486
3533
|
actionHeaders: {}
|
|
3487
3534
|
});
|
|
3488
3535
|
} catch (e) {
|
|
3489
|
-
// If the user threw/returned a Response in callLoaderOrAction
|
|
3490
|
-
//
|
|
3491
|
-
//
|
|
3492
|
-
if (
|
|
3536
|
+
// If the user threw/returned a Response in callLoaderOrAction for a
|
|
3537
|
+
// `queryRoute` call, we throw the `HandlerResult` to bail out early
|
|
3538
|
+
// and then return or throw the raw Response here accordingly
|
|
3539
|
+
if (isHandlerResult(e) && isResponse(e.result)) {
|
|
3493
3540
|
if (e.type === ResultType.error) {
|
|
3494
|
-
throw e.
|
|
3541
|
+
throw e.result;
|
|
3495
3542
|
}
|
|
3496
|
-
return e.
|
|
3543
|
+
return e.result;
|
|
3497
3544
|
}
|
|
3498
3545
|
// Redirects are always returned since they don't propagate to catch
|
|
3499
3546
|
// boundaries
|
|
@@ -3503,7 +3550,7 @@
|
|
|
3503
3550
|
throw e;
|
|
3504
3551
|
}
|
|
3505
3552
|
}
|
|
3506
|
-
async function submit(request, matches, actionMatch, requestContext, isRouteRequest) {
|
|
3553
|
+
async function submit(request, matches, actionMatch, requestContext, unstable_dataStrategy, loadRouteIds, skipLoaderErrorBubbling, skipLoaders, isRouteRequest) {
|
|
3507
3554
|
let result;
|
|
3508
3555
|
if (!actionMatch.route.action && !actionMatch.route.lazy) {
|
|
3509
3556
|
let error = getInternalRouterError(405, {
|
|
@@ -3519,11 +3566,8 @@
|
|
|
3519
3566
|
error
|
|
3520
3567
|
};
|
|
3521
3568
|
} else {
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
isRouteRequest,
|
|
3525
|
-
requestContext
|
|
3526
|
-
});
|
|
3569
|
+
let results = await callDataStrategy("action", request, [actionMatch], matches, isRouteRequest, requestContext, unstable_dataStrategy);
|
|
3570
|
+
result = results[0];
|
|
3527
3571
|
if (request.signal.aborted) {
|
|
3528
3572
|
throwStaticHandlerAbortedError(request, isRouteRequest, future);
|
|
3529
3573
|
}
|
|
@@ -3534,9 +3578,9 @@
|
|
|
3534
3578
|
// can get back on the "throw all redirect responses" train here should
|
|
3535
3579
|
// this ever happen :/
|
|
3536
3580
|
throw new Response(null, {
|
|
3537
|
-
status: result.status,
|
|
3581
|
+
status: result.response.status,
|
|
3538
3582
|
headers: {
|
|
3539
|
-
Location: result.
|
|
3583
|
+
Location: result.response.headers.get("Location")
|
|
3540
3584
|
}
|
|
3541
3585
|
});
|
|
3542
3586
|
}
|
|
@@ -3573,43 +3617,75 @@
|
|
|
3573
3617
|
activeDeferreds: null
|
|
3574
3618
|
};
|
|
3575
3619
|
}
|
|
3620
|
+
|
|
3621
|
+
// Create a GET request for the loaders
|
|
3622
|
+
let loaderRequest = new Request(request.url, {
|
|
3623
|
+
headers: request.headers,
|
|
3624
|
+
redirect: request.redirect,
|
|
3625
|
+
signal: request.signal
|
|
3626
|
+
});
|
|
3576
3627
|
if (isErrorResult(result)) {
|
|
3577
3628
|
// Store off the pending error - we use it to determine which loaders
|
|
3578
3629
|
// to call and will commit it when we complete the navigation
|
|
3579
|
-
let boundaryMatch = findNearestBoundary(matches, actionMatch.route.id);
|
|
3580
|
-
let
|
|
3581
|
-
|
|
3582
|
-
|
|
3630
|
+
let boundaryMatch = skipLoaderErrorBubbling ? actionMatch : findNearestBoundary(matches, actionMatch.route.id);
|
|
3631
|
+
let statusCode = isRouteErrorResponse(result.error) ? result.error.status : result.statusCode != null ? result.statusCode : 500;
|
|
3632
|
+
let actionHeaders = _extends({}, result.headers ? {
|
|
3633
|
+
[actionMatch.route.id]: result.headers
|
|
3634
|
+
} : {});
|
|
3635
|
+
if (skipLoaders) {
|
|
3636
|
+
return {
|
|
3637
|
+
matches,
|
|
3638
|
+
loaderData: {},
|
|
3639
|
+
actionData: {},
|
|
3640
|
+
errors: {
|
|
3641
|
+
[boundaryMatch.route.id]: result.error
|
|
3642
|
+
},
|
|
3643
|
+
statusCode,
|
|
3644
|
+
loaderHeaders: {},
|
|
3645
|
+
actionHeaders,
|
|
3646
|
+
activeDeferreds: null
|
|
3647
|
+
};
|
|
3648
|
+
}
|
|
3649
|
+
let context = await loadRouteData(loaderRequest, matches, requestContext, unstable_dataStrategy, loadRouteIds, skipLoaderErrorBubbling, null, [boundaryMatch.route.id, result]);
|
|
3583
3650
|
|
|
3584
3651
|
// action status codes take precedence over loader status codes
|
|
3585
3652
|
return _extends({}, context, {
|
|
3586
|
-
statusCode
|
|
3653
|
+
statusCode,
|
|
3587
3654
|
actionData: null,
|
|
3588
|
-
actionHeaders
|
|
3589
|
-
[actionMatch.route.id]: result.headers
|
|
3590
|
-
} : {})
|
|
3655
|
+
actionHeaders
|
|
3591
3656
|
});
|
|
3592
3657
|
}
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3658
|
+
let actionHeaders = result.headers ? {
|
|
3659
|
+
[actionMatch.route.id]: result.headers
|
|
3660
|
+
} : {};
|
|
3661
|
+
if (skipLoaders) {
|
|
3662
|
+
return {
|
|
3663
|
+
matches,
|
|
3664
|
+
loaderData: {},
|
|
3665
|
+
actionData: {
|
|
3666
|
+
[actionMatch.route.id]: result.data
|
|
3667
|
+
},
|
|
3668
|
+
errors: null,
|
|
3669
|
+
statusCode: result.statusCode || 200,
|
|
3670
|
+
loaderHeaders: {},
|
|
3671
|
+
actionHeaders,
|
|
3672
|
+
activeDeferreds: null
|
|
3673
|
+
};
|
|
3674
|
+
}
|
|
3675
|
+
let context = await loadRouteData(loaderRequest, matches, requestContext, unstable_dataStrategy, loadRouteIds, skipLoaderErrorBubbling, null);
|
|
3676
|
+
return _extends({}, context, {
|
|
3604
3677
|
actionData: {
|
|
3605
3678
|
[actionMatch.route.id]: result.data
|
|
3606
|
-
}
|
|
3607
|
-
|
|
3679
|
+
}
|
|
3680
|
+
}, result.statusCode ? {
|
|
3681
|
+
statusCode: result.statusCode
|
|
3682
|
+
} : {}, {
|
|
3683
|
+
actionHeaders: result.headers ? {
|
|
3608
3684
|
[actionMatch.route.id]: result.headers
|
|
3609
|
-
} : {}
|
|
3685
|
+
} : {}
|
|
3610
3686
|
});
|
|
3611
3687
|
}
|
|
3612
|
-
async function loadRouteData(request, matches, requestContext, routeMatch,
|
|
3688
|
+
async function loadRouteData(request, matches, requestContext, unstable_dataStrategy, loadRouteIds, skipLoaderErrorBubbling, routeMatch, pendingActionResult) {
|
|
3613
3689
|
let isRouteRequest = routeMatch != null;
|
|
3614
3690
|
|
|
3615
3691
|
// Short circuit if we have no loaders to run (queryRoute())
|
|
@@ -3620,8 +3696,11 @@
|
|
|
3620
3696
|
routeId: routeMatch == null ? void 0 : routeMatch.route.id
|
|
3621
3697
|
});
|
|
3622
3698
|
}
|
|
3623
|
-
let requestMatches = routeMatch ? [routeMatch] : getLoaderMatchesUntilBoundary(matches,
|
|
3699
|
+
let requestMatches = routeMatch ? [routeMatch] : pendingActionResult && isErrorResult(pendingActionResult[1]) ? getLoaderMatchesUntilBoundary(matches, pendingActionResult[0]) : matches;
|
|
3624
3700
|
let matchesToLoad = requestMatches.filter(m => m.route.loader || m.route.lazy);
|
|
3701
|
+
if (loadRouteIds) {
|
|
3702
|
+
matchesToLoad = matchesToLoad.filter(m => loadRouteIds.includes(m.route.id));
|
|
3703
|
+
}
|
|
3625
3704
|
|
|
3626
3705
|
// Short circuit if we have no loaders to run (query())
|
|
3627
3706
|
if (matchesToLoad.length === 0) {
|
|
@@ -3631,24 +3710,22 @@
|
|
|
3631
3710
|
loaderData: matches.reduce((acc, m) => Object.assign(acc, {
|
|
3632
3711
|
[m.route.id]: null
|
|
3633
3712
|
}), {}),
|
|
3634
|
-
errors:
|
|
3713
|
+
errors: pendingActionResult && isErrorResult(pendingActionResult[1]) ? {
|
|
3714
|
+
[pendingActionResult[0]]: pendingActionResult[1].error
|
|
3715
|
+
} : null,
|
|
3635
3716
|
statusCode: 200,
|
|
3636
3717
|
loaderHeaders: {},
|
|
3637
3718
|
activeDeferreds: null
|
|
3638
3719
|
};
|
|
3639
3720
|
}
|
|
3640
|
-
let results = await
|
|
3641
|
-
isStaticRequest: true,
|
|
3642
|
-
isRouteRequest,
|
|
3643
|
-
requestContext
|
|
3644
|
-
}))]);
|
|
3721
|
+
let results = await callDataStrategy("loader", request, matchesToLoad, matches, isRouteRequest, requestContext, unstable_dataStrategy);
|
|
3645
3722
|
if (request.signal.aborted) {
|
|
3646
3723
|
throwStaticHandlerAbortedError(request, isRouteRequest, future);
|
|
3647
3724
|
}
|
|
3648
3725
|
|
|
3649
3726
|
// Process and commit output from loaders
|
|
3650
3727
|
let activeDeferreds = new Map();
|
|
3651
|
-
let context = processRouteLoaderData(matches, matchesToLoad, results,
|
|
3728
|
+
let context = processRouteLoaderData(matches, matchesToLoad, results, pendingActionResult, activeDeferreds, skipLoaderErrorBubbling);
|
|
3652
3729
|
|
|
3653
3730
|
// Add a null for any non-loader matches for proper revalidation on the client
|
|
3654
3731
|
let executedLoaders = new Set(matchesToLoad.map(match => match.route.id));
|
|
@@ -3662,6 +3739,25 @@
|
|
|
3662
3739
|
activeDeferreds: activeDeferreds.size > 0 ? Object.fromEntries(activeDeferreds.entries()) : null
|
|
3663
3740
|
});
|
|
3664
3741
|
}
|
|
3742
|
+
|
|
3743
|
+
// Utility wrapper for calling dataStrategy server-side without having to
|
|
3744
|
+
// pass around the manifest, mapRouteProperties, etc.
|
|
3745
|
+
async function callDataStrategy(type, request, matchesToLoad, matches, isRouteRequest, requestContext, unstable_dataStrategy) {
|
|
3746
|
+
let results = await callDataStrategyImpl(unstable_dataStrategy || defaultDataStrategy, type, request, matchesToLoad, matches, manifest, mapRouteProperties, requestContext);
|
|
3747
|
+
return await Promise.all(results.map((result, i) => {
|
|
3748
|
+
if (isRedirectHandlerResult(result)) {
|
|
3749
|
+
let response = result.result;
|
|
3750
|
+
// Throw redirects and let the server handle them with an HTTP redirect
|
|
3751
|
+
throw normalizeRelativeRoutingRedirectResponse(response, request, matchesToLoad[i].route.id, matches, basename, future.v7_relativeSplatPath);
|
|
3752
|
+
}
|
|
3753
|
+
if (isResponse(result.result) && isRouteRequest) {
|
|
3754
|
+
// For SSR single-route requests, we want to hand Responses back
|
|
3755
|
+
// directly without unwrapping
|
|
3756
|
+
throw result;
|
|
3757
|
+
}
|
|
3758
|
+
return convertHandlerResultToDataResult(result);
|
|
3759
|
+
}));
|
|
3760
|
+
}
|
|
3665
3761
|
return {
|
|
3666
3762
|
dataRoutes,
|
|
3667
3763
|
query,
|
|
@@ -3882,14 +3978,20 @@
|
|
|
3882
3978
|
}
|
|
3883
3979
|
return boundaryMatches;
|
|
3884
3980
|
}
|
|
3885
|
-
function getMatchesToLoad(history, state, matches, submission, location, isInitialLoad, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, deletedFetchers, fetchLoadMatches, fetchRedirectIds, routesToUse, basename,
|
|
3886
|
-
let actionResult =
|
|
3981
|
+
function getMatchesToLoad(history, state, matches, submission, location, isInitialLoad, skipActionErrorRevalidation, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, deletedFetchers, fetchLoadMatches, fetchRedirectIds, routesToUse, basename, pendingActionResult) {
|
|
3982
|
+
let actionResult = pendingActionResult ? isErrorResult(pendingActionResult[1]) ? pendingActionResult[1].error : pendingActionResult[1].data : undefined;
|
|
3887
3983
|
let currentUrl = history.createURL(state.location);
|
|
3888
3984
|
let nextUrl = history.createURL(location);
|
|
3889
3985
|
|
|
3890
3986
|
// Pick navigation matches that are net-new or qualify for revalidation
|
|
3891
|
-
let boundaryId =
|
|
3892
|
-
let boundaryMatches = getLoaderMatchesUntilBoundary(matches, boundaryId);
|
|
3987
|
+
let boundaryId = pendingActionResult && isErrorResult(pendingActionResult[1]) ? pendingActionResult[0] : undefined;
|
|
3988
|
+
let boundaryMatches = boundaryId ? getLoaderMatchesUntilBoundary(matches, boundaryId) : matches;
|
|
3989
|
+
|
|
3990
|
+
// Don't revalidate loaders by default after action 4xx/5xx responses
|
|
3991
|
+
// when the flag is enabled. They can still opt-into revalidation via
|
|
3992
|
+
// `shouldRevalidate` via `actionResult`
|
|
3993
|
+
let actionStatus = pendingActionResult ? pendingActionResult[1].statusCode : undefined;
|
|
3994
|
+
let shouldSkipRevalidation = skipActionErrorRevalidation && actionStatus && actionStatus >= 400;
|
|
3893
3995
|
let navigationMatches = boundaryMatches.filter((match, index) => {
|
|
3894
3996
|
let {
|
|
3895
3997
|
route
|
|
@@ -3902,7 +4004,7 @@
|
|
|
3902
4004
|
return false;
|
|
3903
4005
|
}
|
|
3904
4006
|
if (isInitialLoad) {
|
|
3905
|
-
if (route.loader.hydrate) {
|
|
4007
|
+
if (typeof route.loader !== "function" || route.loader.hydrate) {
|
|
3906
4008
|
return true;
|
|
3907
4009
|
}
|
|
3908
4010
|
return state.loaderData[route.id] === undefined && (
|
|
@@ -3928,11 +4030,10 @@
|
|
|
3928
4030
|
nextParams: nextRouteMatch.params
|
|
3929
4031
|
}, submission, {
|
|
3930
4032
|
actionResult,
|
|
3931
|
-
|
|
4033
|
+
unstable_actionStatus: actionStatus,
|
|
4034
|
+
defaultShouldRevalidate: shouldSkipRevalidation ? false :
|
|
3932
4035
|
// Forced revalidation due to submission, useRevalidator, or X-Remix-Revalidate
|
|
3933
|
-
isRevalidationRequired ||
|
|
3934
|
-
// Clicked the same link, resubmitted a GET form
|
|
3935
|
-
currentUrl.pathname + currentUrl.search === nextUrl.pathname + nextUrl.search ||
|
|
4036
|
+
isRevalidationRequired || currentUrl.pathname + currentUrl.search === nextUrl.pathname + nextUrl.search ||
|
|
3936
4037
|
// Search params affect all loaders
|
|
3937
4038
|
currentUrl.search !== nextUrl.search || isNewRouteInstance(currentRouteMatch, nextRouteMatch)
|
|
3938
4039
|
}));
|
|
@@ -3994,7 +4095,8 @@
|
|
|
3994
4095
|
nextParams: matches[matches.length - 1].params
|
|
3995
4096
|
}, submission, {
|
|
3996
4097
|
actionResult,
|
|
3997
|
-
|
|
4098
|
+
unstable_actionStatus: actionStatus,
|
|
4099
|
+
defaultShouldRevalidate: shouldSkipRevalidation ? false : isRevalidationRequired
|
|
3998
4100
|
}));
|
|
3999
4101
|
}
|
|
4000
4102
|
if (shouldRevalidate) {
|
|
@@ -4096,24 +4198,92 @@
|
|
|
4096
4198
|
lazy: undefined
|
|
4097
4199
|
}));
|
|
4098
4200
|
}
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4201
|
+
|
|
4202
|
+
// Default implementation of `dataStrategy` which fetches all loaders in parallel
|
|
4203
|
+
function defaultDataStrategy(opts) {
|
|
4204
|
+
return Promise.all(opts.matches.map(m => m.resolve()));
|
|
4205
|
+
}
|
|
4206
|
+
async function callDataStrategyImpl(dataStrategyImpl, type, request, matchesToLoad, matches, manifest, mapRouteProperties, requestContext) {
|
|
4207
|
+
let routeIdsToLoad = matchesToLoad.reduce((acc, m) => acc.add(m.route.id), new Set());
|
|
4208
|
+
let loadedMatches = new Set();
|
|
4209
|
+
|
|
4210
|
+
// Send all matches here to allow for a middleware-type implementation.
|
|
4211
|
+
// handler will be a no-op for unneeded routes and we filter those results
|
|
4212
|
+
// back out below.
|
|
4213
|
+
let results = await dataStrategyImpl({
|
|
4214
|
+
matches: matches.map(match => {
|
|
4215
|
+
let shouldLoad = routeIdsToLoad.has(match.route.id);
|
|
4216
|
+
// `resolve` encapsulates the route.lazy, executing the
|
|
4217
|
+
// loader/action, and mapping return values/thrown errors to a
|
|
4218
|
+
// HandlerResult. Users can pass a callback to take fine-grained control
|
|
4219
|
+
// over the execution of the loader/action
|
|
4220
|
+
let resolve = handlerOverride => {
|
|
4221
|
+
loadedMatches.add(match.route.id);
|
|
4222
|
+
return shouldLoad ? callLoaderOrAction(type, request, match, manifest, mapRouteProperties, handlerOverride, requestContext) : Promise.resolve({
|
|
4223
|
+
type: ResultType.data,
|
|
4224
|
+
result: undefined
|
|
4225
|
+
});
|
|
4226
|
+
};
|
|
4227
|
+
return _extends({}, match, {
|
|
4228
|
+
shouldLoad,
|
|
4229
|
+
resolve
|
|
4230
|
+
});
|
|
4231
|
+
}),
|
|
4232
|
+
request,
|
|
4233
|
+
params: matches[0].params,
|
|
4234
|
+
context: requestContext
|
|
4235
|
+
});
|
|
4236
|
+
|
|
4237
|
+
// Throw if any loadRoute implementations not called since they are what
|
|
4238
|
+
// ensures a route is fully loaded
|
|
4239
|
+
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."));
|
|
4240
|
+
|
|
4241
|
+
// Filter out any middleware-only matches for which we didn't need to run handlers
|
|
4242
|
+
return results.filter((_, i) => routeIdsToLoad.has(matches[i].route.id));
|
|
4243
|
+
}
|
|
4244
|
+
|
|
4245
|
+
// Default logic for calling a loader/action is the user has no specified a dataStrategy
|
|
4246
|
+
async function callLoaderOrAction(type, request, match, manifest, mapRouteProperties, handlerOverride, staticContext) {
|
|
4104
4247
|
let result;
|
|
4105
4248
|
let onReject;
|
|
4106
4249
|
let runHandler = handler => {
|
|
4107
4250
|
// Setup a promise we can race against so that abort signals short circuit
|
|
4108
4251
|
let reject;
|
|
4252
|
+
// This will never resolve so safe to type it as Promise<HandlerResult> to
|
|
4253
|
+
// satisfy the function return value
|
|
4109
4254
|
let abortPromise = new Promise((_, r) => reject = r);
|
|
4110
4255
|
onReject = () => reject();
|
|
4111
4256
|
request.signal.addEventListener("abort", onReject);
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4257
|
+
let actualHandler = ctx => {
|
|
4258
|
+
if (typeof handler !== "function") {
|
|
4259
|
+
return Promise.reject(new Error("You cannot call the handler for a route which defines a boolean " + ("\"" + type + "\" [routeId: " + match.route.id + "]")));
|
|
4260
|
+
}
|
|
4261
|
+
return handler({
|
|
4262
|
+
request,
|
|
4263
|
+
params: match.params,
|
|
4264
|
+
context: staticContext
|
|
4265
|
+
}, ...(ctx !== undefined ? [ctx] : []));
|
|
4266
|
+
};
|
|
4267
|
+
let handlerPromise;
|
|
4268
|
+
if (handlerOverride) {
|
|
4269
|
+
handlerPromise = handlerOverride(ctx => actualHandler(ctx));
|
|
4270
|
+
} else {
|
|
4271
|
+
handlerPromise = (async () => {
|
|
4272
|
+
try {
|
|
4273
|
+
let val = await actualHandler();
|
|
4274
|
+
return {
|
|
4275
|
+
type: "data",
|
|
4276
|
+
result: val
|
|
4277
|
+
};
|
|
4278
|
+
} catch (e) {
|
|
4279
|
+
return {
|
|
4280
|
+
type: "error",
|
|
4281
|
+
result: e
|
|
4282
|
+
};
|
|
4283
|
+
}
|
|
4284
|
+
})();
|
|
4285
|
+
}
|
|
4286
|
+
return Promise.race([handlerPromise, abortPromise]);
|
|
4117
4287
|
};
|
|
4118
4288
|
try {
|
|
4119
4289
|
let handler = match.route[type];
|
|
@@ -4121,23 +4291,23 @@
|
|
|
4121
4291
|
if (handler) {
|
|
4122
4292
|
// Run statically defined handler in parallel with lazy()
|
|
4123
4293
|
let handlerError;
|
|
4124
|
-
let
|
|
4294
|
+
let [value] = await Promise.all([
|
|
4125
4295
|
// If the handler throws, don't let it immediately bubble out,
|
|
4126
4296
|
// since we need to let the lazy() execution finish so we know if this
|
|
4127
4297
|
// route has a boundary that can handle the error
|
|
4128
4298
|
runHandler(handler).catch(e => {
|
|
4129
4299
|
handlerError = e;
|
|
4130
4300
|
}), loadLazyRouteModule(match.route, mapRouteProperties, manifest)]);
|
|
4131
|
-
if (handlerError) {
|
|
4301
|
+
if (handlerError !== undefined) {
|
|
4132
4302
|
throw handlerError;
|
|
4133
4303
|
}
|
|
4134
|
-
result =
|
|
4304
|
+
result = value;
|
|
4135
4305
|
} else {
|
|
4136
4306
|
// Load lazy route module, then run any returned handler
|
|
4137
4307
|
await loadLazyRouteModule(match.route, mapRouteProperties, manifest);
|
|
4138
4308
|
handler = match.route[type];
|
|
4139
4309
|
if (handler) {
|
|
4140
|
-
// Handler still
|
|
4310
|
+
// Handler still runs even if we got interrupted to maintain consistency
|
|
4141
4311
|
// with un-abortable behavior of handler execution on non-lazy or
|
|
4142
4312
|
// previously-lazy-loaded routes
|
|
4143
4313
|
result = await runHandler(handler);
|
|
@@ -4154,7 +4324,7 @@
|
|
|
4154
4324
|
// hit the invariant below that errors on returning undefined.
|
|
4155
4325
|
return {
|
|
4156
4326
|
type: ResultType.data,
|
|
4157
|
-
|
|
4327
|
+
result: undefined
|
|
4158
4328
|
};
|
|
4159
4329
|
}
|
|
4160
4330
|
}
|
|
@@ -4167,65 +4337,29 @@
|
|
|
4167
4337
|
} else {
|
|
4168
4338
|
result = await runHandler(handler);
|
|
4169
4339
|
}
|
|
4170
|
-
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`.");
|
|
4340
|
+
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`.");
|
|
4171
4341
|
} catch (e) {
|
|
4172
|
-
|
|
4173
|
-
|
|
4342
|
+
// We should already be catching and converting normal handler executions to
|
|
4343
|
+
// HandlerResults and returning them, so anything that throws here is an
|
|
4344
|
+
// unexpected error we still need to wrap
|
|
4345
|
+
return {
|
|
4346
|
+
type: ResultType.error,
|
|
4347
|
+
result: e
|
|
4348
|
+
};
|
|
4174
4349
|
} finally {
|
|
4175
4350
|
if (onReject) {
|
|
4176
4351
|
request.signal.removeEventListener("abort", onReject);
|
|
4177
4352
|
}
|
|
4178
4353
|
}
|
|
4354
|
+
return result;
|
|
4355
|
+
}
|
|
4356
|
+
async function convertHandlerResultToDataResult(handlerResult) {
|
|
4357
|
+
let {
|
|
4358
|
+
result,
|
|
4359
|
+
type,
|
|
4360
|
+
status
|
|
4361
|
+
} = handlerResult;
|
|
4179
4362
|
if (isResponse(result)) {
|
|
4180
|
-
let status = result.status;
|
|
4181
|
-
|
|
4182
|
-
// Process redirects
|
|
4183
|
-
if (redirectStatusCodes.has(status)) {
|
|
4184
|
-
let location = result.headers.get("Location");
|
|
4185
|
-
invariant(location, "Redirects returned/thrown from loaders/actions must have a Location header");
|
|
4186
|
-
|
|
4187
|
-
// Support relative routing in internal redirects
|
|
4188
|
-
if (!ABSOLUTE_URL_REGEX.test(location)) {
|
|
4189
|
-
location = normalizeTo(new URL(request.url), matches.slice(0, matches.indexOf(match) + 1), basename, true, location, v7_relativeSplatPath);
|
|
4190
|
-
} else if (!opts.isStaticRequest) {
|
|
4191
|
-
// Strip off the protocol+origin for same-origin + same-basename absolute
|
|
4192
|
-
// redirects. If this is a static request, we can let it go back to the
|
|
4193
|
-
// browser as-is
|
|
4194
|
-
let currentUrl = new URL(request.url);
|
|
4195
|
-
let url = location.startsWith("//") ? new URL(currentUrl.protocol + location) : new URL(location);
|
|
4196
|
-
let isSameBasename = stripBasename(url.pathname, basename) != null;
|
|
4197
|
-
if (url.origin === currentUrl.origin && isSameBasename) {
|
|
4198
|
-
location = url.pathname + url.search + url.hash;
|
|
4199
|
-
}
|
|
4200
|
-
}
|
|
4201
|
-
|
|
4202
|
-
// Don't process redirects in the router during static requests requests.
|
|
4203
|
-
// Instead, throw the Response and let the server handle it with an HTTP
|
|
4204
|
-
// redirect. We also update the Location header in place in this flow so
|
|
4205
|
-
// basename and relative routing is taken into account
|
|
4206
|
-
if (opts.isStaticRequest) {
|
|
4207
|
-
result.headers.set("Location", location);
|
|
4208
|
-
throw result;
|
|
4209
|
-
}
|
|
4210
|
-
return {
|
|
4211
|
-
type: ResultType.redirect,
|
|
4212
|
-
status,
|
|
4213
|
-
location,
|
|
4214
|
-
revalidate: result.headers.get("X-Remix-Revalidate") !== null,
|
|
4215
|
-
reloadDocument: result.headers.get("X-Remix-Reload-Document") !== null
|
|
4216
|
-
};
|
|
4217
|
-
}
|
|
4218
|
-
|
|
4219
|
-
// For SSR single-route requests, we want to hand Responses back directly
|
|
4220
|
-
// without unwrapping. We do this with the QueryRouteResponse wrapper
|
|
4221
|
-
// interface so we can know whether it was returned or thrown
|
|
4222
|
-
if (opts.isRouteRequest) {
|
|
4223
|
-
let queryRouteResponse = {
|
|
4224
|
-
type: resultType === ResultType.error ? ResultType.error : ResultType.data,
|
|
4225
|
-
response: result
|
|
4226
|
-
};
|
|
4227
|
-
throw queryRouteResponse;
|
|
4228
|
-
}
|
|
4229
4363
|
let data;
|
|
4230
4364
|
try {
|
|
4231
4365
|
let contentType = result.headers.get("Content-Type");
|
|
@@ -4246,10 +4380,11 @@
|
|
|
4246
4380
|
error: e
|
|
4247
4381
|
};
|
|
4248
4382
|
}
|
|
4249
|
-
if (
|
|
4383
|
+
if (type === ResultType.error) {
|
|
4250
4384
|
return {
|
|
4251
|
-
type:
|
|
4252
|
-
error: new ErrorResponseImpl(status, result.statusText, data),
|
|
4385
|
+
type: ResultType.error,
|
|
4386
|
+
error: new ErrorResponseImpl(result.status, result.statusText, data),
|
|
4387
|
+
statusCode: result.status,
|
|
4253
4388
|
headers: result.headers
|
|
4254
4389
|
};
|
|
4255
4390
|
}
|
|
@@ -4260,10 +4395,11 @@
|
|
|
4260
4395
|
headers: result.headers
|
|
4261
4396
|
};
|
|
4262
4397
|
}
|
|
4263
|
-
if (
|
|
4398
|
+
if (type === ResultType.error) {
|
|
4264
4399
|
return {
|
|
4265
|
-
type:
|
|
4266
|
-
error: result
|
|
4400
|
+
type: ResultType.error,
|
|
4401
|
+
error: result,
|
|
4402
|
+
statusCode: isRouteErrorResponse(result) ? result.status : status
|
|
4267
4403
|
};
|
|
4268
4404
|
}
|
|
4269
4405
|
if (isDeferredData(result)) {
|
|
@@ -4277,10 +4413,35 @@
|
|
|
4277
4413
|
}
|
|
4278
4414
|
return {
|
|
4279
4415
|
type: ResultType.data,
|
|
4280
|
-
data: result
|
|
4416
|
+
data: result,
|
|
4417
|
+
statusCode: status
|
|
4281
4418
|
};
|
|
4282
4419
|
}
|
|
4283
4420
|
|
|
4421
|
+
// Support relative routing in internal redirects
|
|
4422
|
+
function normalizeRelativeRoutingRedirectResponse(response, request, routeId, matches, basename, v7_relativeSplatPath) {
|
|
4423
|
+
let location = response.headers.get("Location");
|
|
4424
|
+
invariant(location, "Redirects returned/thrown from loaders/actions must have a Location header");
|
|
4425
|
+
if (!ABSOLUTE_URL_REGEX.test(location)) {
|
|
4426
|
+
let trimmedMatches = matches.slice(0, matches.findIndex(m => m.route.id === routeId) + 1);
|
|
4427
|
+
location = normalizeTo(new URL(request.url), trimmedMatches, basename, true, location, v7_relativeSplatPath);
|
|
4428
|
+
response.headers.set("Location", location);
|
|
4429
|
+
}
|
|
4430
|
+
return response;
|
|
4431
|
+
}
|
|
4432
|
+
function normalizeRedirectLocation(location, currentUrl, basename) {
|
|
4433
|
+
if (ABSOLUTE_URL_REGEX.test(location)) {
|
|
4434
|
+
// Strip off the protocol+origin for same-origin + same-basename absolute redirects
|
|
4435
|
+
let normalizedLocation = location;
|
|
4436
|
+
let url = normalizedLocation.startsWith("//") ? new URL(currentUrl.protocol + normalizedLocation) : new URL(normalizedLocation);
|
|
4437
|
+
let isSameBasename = stripBasename(url.pathname, basename) != null;
|
|
4438
|
+
if (url.origin === currentUrl.origin && isSameBasename) {
|
|
4439
|
+
return url.pathname + url.search + url.hash;
|
|
4440
|
+
}
|
|
4441
|
+
}
|
|
4442
|
+
return location;
|
|
4443
|
+
}
|
|
4444
|
+
|
|
4284
4445
|
// Utility method for creating the Request instances for loaders/actions during
|
|
4285
4446
|
// client-side navigations and fetches. During SSR we will always have a
|
|
4286
4447
|
// Request instance from the static handler (query/queryRoute)
|
|
@@ -4331,35 +4492,39 @@
|
|
|
4331
4492
|
}
|
|
4332
4493
|
return formData;
|
|
4333
4494
|
}
|
|
4334
|
-
function processRouteLoaderData(matches, matchesToLoad, results,
|
|
4495
|
+
function processRouteLoaderData(matches, matchesToLoad, results, pendingActionResult, activeDeferreds, skipLoaderErrorBubbling) {
|
|
4335
4496
|
// Fill in loaderData/errors from our loaders
|
|
4336
4497
|
let loaderData = {};
|
|
4337
4498
|
let errors = null;
|
|
4338
4499
|
let statusCode;
|
|
4339
4500
|
let foundError = false;
|
|
4340
4501
|
let loaderHeaders = {};
|
|
4502
|
+
let pendingError = pendingActionResult && isErrorResult(pendingActionResult[1]) ? pendingActionResult[1].error : undefined;
|
|
4341
4503
|
|
|
4342
4504
|
// Process loader results into state.loaderData/state.errors
|
|
4343
4505
|
results.forEach((result, index) => {
|
|
4344
4506
|
let id = matchesToLoad[index].route.id;
|
|
4345
4507
|
invariant(!isRedirectResult(result), "Cannot handle redirect results in processLoaderData");
|
|
4346
4508
|
if (isErrorResult(result)) {
|
|
4347
|
-
// Look upwards from the matched route for the closest ancestor
|
|
4348
|
-
// error boundary, defaulting to the root match
|
|
4349
|
-
let boundaryMatch = findNearestBoundary(matches, id);
|
|
4350
4509
|
let error = result.error;
|
|
4351
4510
|
// If we have a pending action error, we report it at the highest-route
|
|
4352
4511
|
// that throws a loader error, and then clear it out to indicate that
|
|
4353
4512
|
// it was consumed
|
|
4354
|
-
if (pendingError) {
|
|
4355
|
-
error =
|
|
4513
|
+
if (pendingError !== undefined) {
|
|
4514
|
+
error = pendingError;
|
|
4356
4515
|
pendingError = undefined;
|
|
4357
4516
|
}
|
|
4358
4517
|
errors = errors || {};
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
|
|
4362
|
-
|
|
4518
|
+
if (skipLoaderErrorBubbling) {
|
|
4519
|
+
errors[id] = error;
|
|
4520
|
+
} else {
|
|
4521
|
+
// Look upwards from the matched route for the closest ancestor error
|
|
4522
|
+
// boundary, defaulting to the root match. Prefer higher error values
|
|
4523
|
+
// if lower errors bubble to the same boundary
|
|
4524
|
+
let boundaryMatch = findNearestBoundary(matches, id);
|
|
4525
|
+
if (errors[boundaryMatch.route.id] == null) {
|
|
4526
|
+
errors[boundaryMatch.route.id] = error;
|
|
4527
|
+
}
|
|
4363
4528
|
}
|
|
4364
4529
|
|
|
4365
4530
|
// Clear our any prior loaderData for the throwing route
|
|
@@ -4378,17 +4543,24 @@
|
|
|
4378
4543
|
if (isDeferredResult(result)) {
|
|
4379
4544
|
activeDeferreds.set(id, result.deferredData);
|
|
4380
4545
|
loaderData[id] = result.deferredData.data;
|
|
4546
|
+
// Error status codes always override success status codes, but if all
|
|
4547
|
+
// loaders are successful we take the deepest status code.
|
|
4548
|
+
if (result.statusCode != null && result.statusCode !== 200 && !foundError) {
|
|
4549
|
+
statusCode = result.statusCode;
|
|
4550
|
+
}
|
|
4551
|
+
if (result.headers) {
|
|
4552
|
+
loaderHeaders[id] = result.headers;
|
|
4553
|
+
}
|
|
4381
4554
|
} else {
|
|
4382
4555
|
loaderData[id] = result.data;
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
loaderHeaders[id] = result.headers;
|
|
4556
|
+
// Error status codes always override success status codes, but if all
|
|
4557
|
+
// loaders are successful we take the deepest status code.
|
|
4558
|
+
if (result.statusCode && result.statusCode !== 200 && !foundError) {
|
|
4559
|
+
statusCode = result.statusCode;
|
|
4560
|
+
}
|
|
4561
|
+
if (result.headers) {
|
|
4562
|
+
loaderHeaders[id] = result.headers;
|
|
4563
|
+
}
|
|
4392
4564
|
}
|
|
4393
4565
|
}
|
|
4394
4566
|
});
|
|
@@ -4396,9 +4568,11 @@
|
|
|
4396
4568
|
// If we didn't consume the pending action error (i.e., all loaders
|
|
4397
4569
|
// resolved), then consume it here. Also clear out any loaderData for the
|
|
4398
4570
|
// throwing route
|
|
4399
|
-
if (pendingError) {
|
|
4400
|
-
errors =
|
|
4401
|
-
|
|
4571
|
+
if (pendingError !== undefined && pendingActionResult) {
|
|
4572
|
+
errors = {
|
|
4573
|
+
[pendingActionResult[0]]: pendingError
|
|
4574
|
+
};
|
|
4575
|
+
loaderData[pendingActionResult[0]] = undefined;
|
|
4402
4576
|
}
|
|
4403
4577
|
return {
|
|
4404
4578
|
loaderData,
|
|
@@ -4407,11 +4581,12 @@
|
|
|
4407
4581
|
loaderHeaders
|
|
4408
4582
|
};
|
|
4409
4583
|
}
|
|
4410
|
-
function processLoaderData(state, matches, matchesToLoad, results,
|
|
4584
|
+
function processLoaderData(state, matches, matchesToLoad, results, pendingActionResult, revalidatingFetchers, fetcherResults, activeDeferreds) {
|
|
4411
4585
|
let {
|
|
4412
4586
|
loaderData,
|
|
4413
4587
|
errors
|
|
4414
|
-
} = processRouteLoaderData(matches, matchesToLoad, results,
|
|
4588
|
+
} = processRouteLoaderData(matches, matchesToLoad, results, pendingActionResult, activeDeferreds, false // This method is only called client side so we always want to bubble
|
|
4589
|
+
);
|
|
4415
4590
|
|
|
4416
4591
|
// Process results from our revalidating fetchers
|
|
4417
4592
|
for (let index = 0; index < revalidatingFetchers.length; index++) {
|
|
@@ -4473,6 +4648,19 @@
|
|
|
4473
4648
|
}
|
|
4474
4649
|
return mergedLoaderData;
|
|
4475
4650
|
}
|
|
4651
|
+
function getActionDataForCommit(pendingActionResult) {
|
|
4652
|
+
if (!pendingActionResult) {
|
|
4653
|
+
return {};
|
|
4654
|
+
}
|
|
4655
|
+
return isErrorResult(pendingActionResult[1]) ? {
|
|
4656
|
+
// Clear out prior actionData on errors
|
|
4657
|
+
actionData: {}
|
|
4658
|
+
} : {
|
|
4659
|
+
actionData: {
|
|
4660
|
+
[pendingActionResult[0]]: pendingActionResult[1].data
|
|
4661
|
+
}
|
|
4662
|
+
};
|
|
4663
|
+
}
|
|
4476
4664
|
|
|
4477
4665
|
// Find the nearest error boundary, looking upwards from the leaf route (or the
|
|
4478
4666
|
// route specified by routeId) for the closest ancestor error boundary,
|
|
@@ -4568,6 +4756,12 @@
|
|
|
4568
4756
|
// /page#hash -> /page
|
|
4569
4757
|
return false;
|
|
4570
4758
|
}
|
|
4759
|
+
function isHandlerResult(result) {
|
|
4760
|
+
return result != null && typeof result === "object" && "type" in result && "result" in result && (result.type === ResultType.data || result.type === ResultType.error);
|
|
4761
|
+
}
|
|
4762
|
+
function isRedirectHandlerResult(result) {
|
|
4763
|
+
return isResponse(result.result) && redirectStatusCodes.has(result.result.status);
|
|
4764
|
+
}
|
|
4571
4765
|
function isDeferredResult(result) {
|
|
4572
4766
|
return result.type === ResultType.deferred;
|
|
4573
4767
|
}
|
|
@@ -4592,9 +4786,6 @@
|
|
|
4592
4786
|
let location = result.headers.get("Location");
|
|
4593
4787
|
return status >= 300 && status <= 399 && location != null;
|
|
4594
4788
|
}
|
|
4595
|
-
function isQueryRouteResponse(obj) {
|
|
4596
|
-
return obj && isResponse(obj.response) && (obj.type === ResultType.data || obj.type === ResultType.error);
|
|
4597
|
-
}
|
|
4598
4789
|
function isValidMethod(method) {
|
|
4599
4790
|
return validRequestMethods.has(method.toLowerCase());
|
|
4600
4791
|
}
|