@remix-run/router 1.15.3 → 1.16.0-pre.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -0
- package/dist/index.d.ts +1 -1
- package/dist/router.cjs.js +396 -247
- package/dist/router.cjs.js.map +1 -1
- package/dist/router.d.ts +5 -1
- package/dist/router.js +382 -235
- package/dist/router.js.map +1 -1
- package/dist/router.umd.js +396 -247
- package/dist/router.umd.js.map +1 -1
- package/dist/router.umd.min.js +2 -2
- package/dist/router.umd.min.js.map +1 -1
- package/dist/utils.d.ts +28 -10
- package/index.ts +4 -0
- package/package.json +2 -2
- package/router.ts +677 -354
- package/utils.ts +46 -14
package/dist/router.umd.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @remix-run/router v1.
|
|
2
|
+
* @remix-run/router v1.16.0-pre.1
|
|
3
3
|
*
|
|
4
4
|
* Copyright (c) Remix Software Inc.
|
|
5
5
|
*
|
|
@@ -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,19 @@
|
|
|
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.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
|
|
3327
3360
|
*/
|
|
3328
3361
|
async function query(request, _temp3) {
|
|
3329
3362
|
let {
|
|
3330
|
-
requestContext
|
|
3363
|
+
requestContext,
|
|
3364
|
+
skipLoaderErrorBubbling,
|
|
3365
|
+
unstable_dataStrategy
|
|
3331
3366
|
} = _temp3 === void 0 ? {} : _temp3;
|
|
3332
3367
|
let url = new URL(request.url);
|
|
3333
3368
|
let method = request.method;
|
|
@@ -3380,7 +3415,7 @@
|
|
|
3380
3415
|
activeDeferreds: null
|
|
3381
3416
|
};
|
|
3382
3417
|
}
|
|
3383
|
-
let result = await queryImpl(request, location, matches, requestContext);
|
|
3418
|
+
let result = await queryImpl(request, location, matches, requestContext, unstable_dataStrategy || null, skipLoaderErrorBubbling === true, null);
|
|
3384
3419
|
if (isResponse(result)) {
|
|
3385
3420
|
return result;
|
|
3386
3421
|
}
|
|
@@ -3413,6 +3448,12 @@
|
|
|
3413
3448
|
* serialize the error as they see fit while including the proper response
|
|
3414
3449
|
* code. Examples here are 404 and 405 errors that occur prior to reaching
|
|
3415
3450
|
* any user-defined loaders.
|
|
3451
|
+
*
|
|
3452
|
+
* - `opts.routeId` allows you to specify the specific route handler to call.
|
|
3453
|
+
* If not provided the handler will determine the proper route by matching
|
|
3454
|
+
* against `request.url`
|
|
3455
|
+
* - `opts.requestContext` is an optional server context that will be passed
|
|
3456
|
+
* to actions/loaders in the `context` parameter
|
|
3416
3457
|
*/
|
|
3417
3458
|
async function queryRoute(request, _temp4) {
|
|
3418
3459
|
let {
|
|
@@ -3446,7 +3487,7 @@
|
|
|
3446
3487
|
pathname: location.pathname
|
|
3447
3488
|
});
|
|
3448
3489
|
}
|
|
3449
|
-
let result = await queryImpl(request, location, matches, requestContext, match);
|
|
3490
|
+
let result = await queryImpl(request, location, matches, requestContext, null, false, match);
|
|
3450
3491
|
if (isResponse(result)) {
|
|
3451
3492
|
return result;
|
|
3452
3493
|
}
|
|
@@ -3473,27 +3514,27 @@
|
|
|
3473
3514
|
}
|
|
3474
3515
|
return undefined;
|
|
3475
3516
|
}
|
|
3476
|
-
async function queryImpl(request, location, matches, requestContext, routeMatch) {
|
|
3517
|
+
async function queryImpl(request, location, matches, requestContext, unstable_dataStrategy, skipLoaderErrorBubbling, routeMatch) {
|
|
3477
3518
|
invariant(request.signal, "query()/queryRoute() requests must contain an AbortController signal");
|
|
3478
3519
|
try {
|
|
3479
3520
|
if (isMutationMethod(request.method.toLowerCase())) {
|
|
3480
|
-
let result = await submit(request, matches, routeMatch || getTargetMatch(matches, location), requestContext, routeMatch != null);
|
|
3521
|
+
let result = await submit(request, matches, routeMatch || getTargetMatch(matches, location), requestContext, unstable_dataStrategy, skipLoaderErrorBubbling, routeMatch != null);
|
|
3481
3522
|
return result;
|
|
3482
3523
|
}
|
|
3483
|
-
let result = await loadRouteData(request, matches, requestContext, routeMatch);
|
|
3524
|
+
let result = await loadRouteData(request, matches, requestContext, unstable_dataStrategy, skipLoaderErrorBubbling, routeMatch);
|
|
3484
3525
|
return isResponse(result) ? result : _extends({}, result, {
|
|
3485
3526
|
actionData: null,
|
|
3486
3527
|
actionHeaders: {}
|
|
3487
3528
|
});
|
|
3488
3529
|
} catch (e) {
|
|
3489
|
-
// If the user threw/returned a Response in callLoaderOrAction
|
|
3490
|
-
//
|
|
3491
|
-
//
|
|
3492
|
-
if (
|
|
3530
|
+
// If the user threw/returned a Response in callLoaderOrAction for a
|
|
3531
|
+
// `queryRoute` call, we throw the `HandlerResult` to bail out early
|
|
3532
|
+
// and then return or throw the raw Response here accordingly
|
|
3533
|
+
if (isHandlerResult(e) && isResponse(e.result)) {
|
|
3493
3534
|
if (e.type === ResultType.error) {
|
|
3494
|
-
throw e.
|
|
3535
|
+
throw e.result;
|
|
3495
3536
|
}
|
|
3496
|
-
return e.
|
|
3537
|
+
return e.result;
|
|
3497
3538
|
}
|
|
3498
3539
|
// Redirects are always returned since they don't propagate to catch
|
|
3499
3540
|
// boundaries
|
|
@@ -3503,7 +3544,7 @@
|
|
|
3503
3544
|
throw e;
|
|
3504
3545
|
}
|
|
3505
3546
|
}
|
|
3506
|
-
async function submit(request, matches, actionMatch, requestContext, isRouteRequest) {
|
|
3547
|
+
async function submit(request, matches, actionMatch, requestContext, unstable_dataStrategy, skipLoaderErrorBubbling, isRouteRequest) {
|
|
3507
3548
|
let result;
|
|
3508
3549
|
if (!actionMatch.route.action && !actionMatch.route.lazy) {
|
|
3509
3550
|
let error = getInternalRouterError(405, {
|
|
@@ -3519,11 +3560,8 @@
|
|
|
3519
3560
|
error
|
|
3520
3561
|
};
|
|
3521
3562
|
} else {
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
isRouteRequest,
|
|
3525
|
-
requestContext
|
|
3526
|
-
});
|
|
3563
|
+
let results = await callDataStrategy("action", request, [actionMatch], matches, isRouteRequest, requestContext, unstable_dataStrategy);
|
|
3564
|
+
result = results[0];
|
|
3527
3565
|
if (request.signal.aborted) {
|
|
3528
3566
|
throwStaticHandlerAbortedError(request, isRouteRequest, future);
|
|
3529
3567
|
}
|
|
@@ -3534,9 +3572,9 @@
|
|
|
3534
3572
|
// can get back on the "throw all redirect responses" train here should
|
|
3535
3573
|
// this ever happen :/
|
|
3536
3574
|
throw new Response(null, {
|
|
3537
|
-
status: result.status,
|
|
3575
|
+
status: result.response.status,
|
|
3538
3576
|
headers: {
|
|
3539
|
-
Location: result.
|
|
3577
|
+
Location: result.response.headers.get("Location")
|
|
3540
3578
|
}
|
|
3541
3579
|
});
|
|
3542
3580
|
}
|
|
@@ -3573,43 +3611,42 @@
|
|
|
3573
3611
|
activeDeferreds: null
|
|
3574
3612
|
};
|
|
3575
3613
|
}
|
|
3614
|
+
|
|
3615
|
+
// Create a GET request for the loaders
|
|
3616
|
+
let loaderRequest = new Request(request.url, {
|
|
3617
|
+
headers: request.headers,
|
|
3618
|
+
redirect: request.redirect,
|
|
3619
|
+
signal: request.signal
|
|
3620
|
+
});
|
|
3576
3621
|
if (isErrorResult(result)) {
|
|
3577
3622
|
// Store off the pending error - we use it to determine which loaders
|
|
3578
3623
|
// to call and will commit it when we complete the navigation
|
|
3579
|
-
let boundaryMatch = findNearestBoundary(matches, actionMatch.route.id);
|
|
3580
|
-
let context = await loadRouteData(
|
|
3581
|
-
[boundaryMatch.route.id]: result.error
|
|
3582
|
-
});
|
|
3624
|
+
let boundaryMatch = skipLoaderErrorBubbling ? actionMatch : findNearestBoundary(matches, actionMatch.route.id);
|
|
3625
|
+
let context = await loadRouteData(loaderRequest, matches, requestContext, unstable_dataStrategy, skipLoaderErrorBubbling, null, [boundaryMatch.route.id, result]);
|
|
3583
3626
|
|
|
3584
3627
|
// action status codes take precedence over loader status codes
|
|
3585
3628
|
return _extends({}, context, {
|
|
3586
|
-
statusCode: isRouteErrorResponse(result.error) ? result.error.status : 500,
|
|
3629
|
+
statusCode: isRouteErrorResponse(result.error) ? result.error.status : result.statusCode != null ? result.statusCode : 500,
|
|
3587
3630
|
actionData: null,
|
|
3588
3631
|
actionHeaders: _extends({}, result.headers ? {
|
|
3589
3632
|
[actionMatch.route.id]: result.headers
|
|
3590
3633
|
} : {})
|
|
3591
3634
|
});
|
|
3592
3635
|
}
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
let loaderRequest = new Request(request.url, {
|
|
3596
|
-
headers: request.headers,
|
|
3597
|
-
redirect: request.redirect,
|
|
3598
|
-
signal: request.signal
|
|
3599
|
-
});
|
|
3600
|
-
let context = await loadRouteData(loaderRequest, matches, requestContext);
|
|
3601
|
-
return _extends({}, context, result.statusCode ? {
|
|
3602
|
-
statusCode: result.statusCode
|
|
3603
|
-
} : {}, {
|
|
3636
|
+
let context = await loadRouteData(loaderRequest, matches, requestContext, unstable_dataStrategy, skipLoaderErrorBubbling, null);
|
|
3637
|
+
return _extends({}, context, {
|
|
3604
3638
|
actionData: {
|
|
3605
3639
|
[actionMatch.route.id]: result.data
|
|
3606
|
-
}
|
|
3607
|
-
|
|
3640
|
+
}
|
|
3641
|
+
}, result.statusCode ? {
|
|
3642
|
+
statusCode: result.statusCode
|
|
3643
|
+
} : {}, {
|
|
3644
|
+
actionHeaders: result.headers ? {
|
|
3608
3645
|
[actionMatch.route.id]: result.headers
|
|
3609
|
-
} : {}
|
|
3646
|
+
} : {}
|
|
3610
3647
|
});
|
|
3611
3648
|
}
|
|
3612
|
-
async function loadRouteData(request, matches, requestContext, routeMatch,
|
|
3649
|
+
async function loadRouteData(request, matches, requestContext, unstable_dataStrategy, skipLoaderErrorBubbling, routeMatch, pendingActionResult) {
|
|
3613
3650
|
let isRouteRequest = routeMatch != null;
|
|
3614
3651
|
|
|
3615
3652
|
// Short circuit if we have no loaders to run (queryRoute())
|
|
@@ -3620,7 +3657,7 @@
|
|
|
3620
3657
|
routeId: routeMatch == null ? void 0 : routeMatch.route.id
|
|
3621
3658
|
});
|
|
3622
3659
|
}
|
|
3623
|
-
let requestMatches = routeMatch ? [routeMatch] : getLoaderMatchesUntilBoundary(matches,
|
|
3660
|
+
let requestMatches = routeMatch ? [routeMatch] : pendingActionResult && isErrorResult(pendingActionResult[1]) ? getLoaderMatchesUntilBoundary(matches, pendingActionResult[0]) : matches;
|
|
3624
3661
|
let matchesToLoad = requestMatches.filter(m => m.route.loader || m.route.lazy);
|
|
3625
3662
|
|
|
3626
3663
|
// Short circuit if we have no loaders to run (query())
|
|
@@ -3631,24 +3668,22 @@
|
|
|
3631
3668
|
loaderData: matches.reduce((acc, m) => Object.assign(acc, {
|
|
3632
3669
|
[m.route.id]: null
|
|
3633
3670
|
}), {}),
|
|
3634
|
-
errors:
|
|
3671
|
+
errors: pendingActionResult && isErrorResult(pendingActionResult[1]) ? {
|
|
3672
|
+
[pendingActionResult[0]]: pendingActionResult[1].error
|
|
3673
|
+
} : null,
|
|
3635
3674
|
statusCode: 200,
|
|
3636
3675
|
loaderHeaders: {},
|
|
3637
3676
|
activeDeferreds: null
|
|
3638
3677
|
};
|
|
3639
3678
|
}
|
|
3640
|
-
let results = await
|
|
3641
|
-
isStaticRequest: true,
|
|
3642
|
-
isRouteRequest,
|
|
3643
|
-
requestContext
|
|
3644
|
-
}))]);
|
|
3679
|
+
let results = await callDataStrategy("loader", request, matchesToLoad, matches, isRouteRequest, requestContext, unstable_dataStrategy);
|
|
3645
3680
|
if (request.signal.aborted) {
|
|
3646
3681
|
throwStaticHandlerAbortedError(request, isRouteRequest, future);
|
|
3647
3682
|
}
|
|
3648
3683
|
|
|
3649
3684
|
// Process and commit output from loaders
|
|
3650
3685
|
let activeDeferreds = new Map();
|
|
3651
|
-
let context = processRouteLoaderData(matches, matchesToLoad, results,
|
|
3686
|
+
let context = processRouteLoaderData(matches, matchesToLoad, results, pendingActionResult, activeDeferreds, skipLoaderErrorBubbling);
|
|
3652
3687
|
|
|
3653
3688
|
// Add a null for any non-loader matches for proper revalidation on the client
|
|
3654
3689
|
let executedLoaders = new Set(matchesToLoad.map(match => match.route.id));
|
|
@@ -3662,6 +3697,25 @@
|
|
|
3662
3697
|
activeDeferreds: activeDeferreds.size > 0 ? Object.fromEntries(activeDeferreds.entries()) : null
|
|
3663
3698
|
});
|
|
3664
3699
|
}
|
|
3700
|
+
|
|
3701
|
+
// Utility wrapper for calling dataStrategy server-side without having to
|
|
3702
|
+
// pass around the manifest, mapRouteProperties, etc.
|
|
3703
|
+
async function callDataStrategy(type, request, matchesToLoad, matches, isRouteRequest, requestContext, unstable_dataStrategy) {
|
|
3704
|
+
let results = await callDataStrategyImpl(unstable_dataStrategy || defaultDataStrategy, type, request, matchesToLoad, matches, manifest, mapRouteProperties, requestContext);
|
|
3705
|
+
return await Promise.all(results.map((result, i) => {
|
|
3706
|
+
if (isRedirectHandlerResult(result)) {
|
|
3707
|
+
let response = result.result;
|
|
3708
|
+
// Throw redirects and let the server handle them with an HTTP redirect
|
|
3709
|
+
throw normalizeRelativeRoutingRedirectResponse(response, request, matchesToLoad[i].route.id, matches, basename, future.v7_relativeSplatPath);
|
|
3710
|
+
}
|
|
3711
|
+
if (isResponse(result.result) && isRouteRequest) {
|
|
3712
|
+
// For SSR single-route requests, we want to hand Responses back
|
|
3713
|
+
// directly without unwrapping
|
|
3714
|
+
throw result;
|
|
3715
|
+
}
|
|
3716
|
+
return convertHandlerResultToDataResult(result);
|
|
3717
|
+
}));
|
|
3718
|
+
}
|
|
3665
3719
|
return {
|
|
3666
3720
|
dataRoutes,
|
|
3667
3721
|
query,
|
|
@@ -3882,14 +3936,20 @@
|
|
|
3882
3936
|
}
|
|
3883
3937
|
return boundaryMatches;
|
|
3884
3938
|
}
|
|
3885
|
-
function getMatchesToLoad(history, state, matches, submission, location, isInitialLoad, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, deletedFetchers, fetchLoadMatches, fetchRedirectIds, routesToUse, basename,
|
|
3886
|
-
let actionResult =
|
|
3939
|
+
function getMatchesToLoad(history, state, matches, submission, location, isInitialLoad, skipActionErrorRevalidation, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, deletedFetchers, fetchLoadMatches, fetchRedirectIds, routesToUse, basename, pendingActionResult) {
|
|
3940
|
+
let actionResult = pendingActionResult ? isErrorResult(pendingActionResult[1]) ? pendingActionResult[1].error : pendingActionResult[1].data : undefined;
|
|
3887
3941
|
let currentUrl = history.createURL(state.location);
|
|
3888
3942
|
let nextUrl = history.createURL(location);
|
|
3889
3943
|
|
|
3890
3944
|
// Pick navigation matches that are net-new or qualify for revalidation
|
|
3891
|
-
let boundaryId =
|
|
3892
|
-
let boundaryMatches = getLoaderMatchesUntilBoundary(matches, boundaryId);
|
|
3945
|
+
let boundaryId = pendingActionResult && isErrorResult(pendingActionResult[1]) ? pendingActionResult[0] : undefined;
|
|
3946
|
+
let boundaryMatches = boundaryId ? getLoaderMatchesUntilBoundary(matches, boundaryId) : matches;
|
|
3947
|
+
|
|
3948
|
+
// Don't revalidate loaders by default after action 4xx/5xx responses
|
|
3949
|
+
// when the flag is enabled. They can still opt-into revalidation via
|
|
3950
|
+
// `shouldRevalidate` via `actionResult`
|
|
3951
|
+
let actionStatus = pendingActionResult ? pendingActionResult[1].statusCode : undefined;
|
|
3952
|
+
let shouldSkipRevalidation = skipActionErrorRevalidation && actionStatus && actionStatus >= 400;
|
|
3893
3953
|
let navigationMatches = boundaryMatches.filter((match, index) => {
|
|
3894
3954
|
let {
|
|
3895
3955
|
route
|
|
@@ -3902,7 +3962,7 @@
|
|
|
3902
3962
|
return false;
|
|
3903
3963
|
}
|
|
3904
3964
|
if (isInitialLoad) {
|
|
3905
|
-
if (route.loader.hydrate) {
|
|
3965
|
+
if (typeof route.loader !== "function" || route.loader.hydrate) {
|
|
3906
3966
|
return true;
|
|
3907
3967
|
}
|
|
3908
3968
|
return state.loaderData[route.id] === undefined && (
|
|
@@ -3928,11 +3988,10 @@
|
|
|
3928
3988
|
nextParams: nextRouteMatch.params
|
|
3929
3989
|
}, submission, {
|
|
3930
3990
|
actionResult,
|
|
3931
|
-
|
|
3991
|
+
unstable_actionStatus: actionStatus,
|
|
3992
|
+
defaultShouldRevalidate: shouldSkipRevalidation ? false :
|
|
3932
3993
|
// 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 ||
|
|
3994
|
+
isRevalidationRequired || currentUrl.pathname + currentUrl.search === nextUrl.pathname + nextUrl.search ||
|
|
3936
3995
|
// Search params affect all loaders
|
|
3937
3996
|
currentUrl.search !== nextUrl.search || isNewRouteInstance(currentRouteMatch, nextRouteMatch)
|
|
3938
3997
|
}));
|
|
@@ -3994,7 +4053,8 @@
|
|
|
3994
4053
|
nextParams: matches[matches.length - 1].params
|
|
3995
4054
|
}, submission, {
|
|
3996
4055
|
actionResult,
|
|
3997
|
-
|
|
4056
|
+
unstable_actionStatus: actionStatus,
|
|
4057
|
+
defaultShouldRevalidate: shouldSkipRevalidation ? false : isRevalidationRequired
|
|
3998
4058
|
}));
|
|
3999
4059
|
}
|
|
4000
4060
|
if (shouldRevalidate) {
|
|
@@ -4096,24 +4156,92 @@
|
|
|
4096
4156
|
lazy: undefined
|
|
4097
4157
|
}));
|
|
4098
4158
|
}
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4159
|
+
|
|
4160
|
+
// Default implementation of `dataStrategy` which fetches all loaders in parallel
|
|
4161
|
+
function defaultDataStrategy(opts) {
|
|
4162
|
+
return Promise.all(opts.matches.map(m => m.resolve()));
|
|
4163
|
+
}
|
|
4164
|
+
async function callDataStrategyImpl(dataStrategyImpl, type, request, matchesToLoad, matches, manifest, mapRouteProperties, requestContext) {
|
|
4165
|
+
let routeIdsToLoad = matchesToLoad.reduce((acc, m) => acc.add(m.route.id), new Set());
|
|
4166
|
+
let loadedMatches = new Set();
|
|
4167
|
+
|
|
4168
|
+
// Send all matches here to allow for a middleware-type implementation.
|
|
4169
|
+
// handler will be a no-op for unneeded routes and we filter those results
|
|
4170
|
+
// back out below.
|
|
4171
|
+
let results = await dataStrategyImpl({
|
|
4172
|
+
matches: matches.map(match => {
|
|
4173
|
+
let shouldLoad = routeIdsToLoad.has(match.route.id);
|
|
4174
|
+
// `resolve` encapsulates the route.lazy, executing the
|
|
4175
|
+
// loader/action, and mapping return values/thrown errors to a
|
|
4176
|
+
// HandlerResult. Users can pass a callback to take fine-grained control
|
|
4177
|
+
// over the execution of the loader/action
|
|
4178
|
+
let resolve = handlerOverride => {
|
|
4179
|
+
loadedMatches.add(match.route.id);
|
|
4180
|
+
return shouldLoad ? callLoaderOrAction(type, request, match, manifest, mapRouteProperties, handlerOverride, requestContext) : Promise.resolve({
|
|
4181
|
+
type: ResultType.data,
|
|
4182
|
+
result: undefined
|
|
4183
|
+
});
|
|
4184
|
+
};
|
|
4185
|
+
return _extends({}, match, {
|
|
4186
|
+
shouldLoad,
|
|
4187
|
+
resolve
|
|
4188
|
+
});
|
|
4189
|
+
}),
|
|
4190
|
+
request,
|
|
4191
|
+
params: matches[0].params,
|
|
4192
|
+
context: requestContext
|
|
4193
|
+
});
|
|
4194
|
+
|
|
4195
|
+
// Throw if any loadRoute implementations not called since they are what
|
|
4196
|
+
// ensures a route is fully loaded
|
|
4197
|
+
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."));
|
|
4198
|
+
|
|
4199
|
+
// Filter out any middleware-only matches for which we didn't need to run handlers
|
|
4200
|
+
return results.filter((_, i) => routeIdsToLoad.has(matches[i].route.id));
|
|
4201
|
+
}
|
|
4202
|
+
|
|
4203
|
+
// Default logic for calling a loader/action is the user has no specified a dataStrategy
|
|
4204
|
+
async function callLoaderOrAction(type, request, match, manifest, mapRouteProperties, handlerOverride, staticContext) {
|
|
4104
4205
|
let result;
|
|
4105
4206
|
let onReject;
|
|
4106
4207
|
let runHandler = handler => {
|
|
4107
4208
|
// Setup a promise we can race against so that abort signals short circuit
|
|
4108
4209
|
let reject;
|
|
4210
|
+
// This will never resolve so safe to type it as Promise<HandlerResult> to
|
|
4211
|
+
// satisfy the function return value
|
|
4109
4212
|
let abortPromise = new Promise((_, r) => reject = r);
|
|
4110
4213
|
onReject = () => reject();
|
|
4111
4214
|
request.signal.addEventListener("abort", onReject);
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4215
|
+
let actualHandler = ctx => {
|
|
4216
|
+
if (typeof handler !== "function") {
|
|
4217
|
+
return Promise.reject(new Error("You cannot call the handler for a route which defines a boolean " + ("\"" + type + "\" [routeId: " + match.route.id + "]")));
|
|
4218
|
+
}
|
|
4219
|
+
return handler({
|
|
4220
|
+
request,
|
|
4221
|
+
params: match.params,
|
|
4222
|
+
context: staticContext
|
|
4223
|
+
}, ...(ctx !== undefined ? [ctx] : []));
|
|
4224
|
+
};
|
|
4225
|
+
let handlerPromise;
|
|
4226
|
+
if (handlerOverride) {
|
|
4227
|
+
handlerPromise = handlerOverride(ctx => actualHandler(ctx));
|
|
4228
|
+
} else {
|
|
4229
|
+
handlerPromise = (async () => {
|
|
4230
|
+
try {
|
|
4231
|
+
let val = await actualHandler();
|
|
4232
|
+
return {
|
|
4233
|
+
type: "data",
|
|
4234
|
+
result: val
|
|
4235
|
+
};
|
|
4236
|
+
} catch (e) {
|
|
4237
|
+
return {
|
|
4238
|
+
type: "error",
|
|
4239
|
+
result: e
|
|
4240
|
+
};
|
|
4241
|
+
}
|
|
4242
|
+
})();
|
|
4243
|
+
}
|
|
4244
|
+
return Promise.race([handlerPromise, abortPromise]);
|
|
4117
4245
|
};
|
|
4118
4246
|
try {
|
|
4119
4247
|
let handler = match.route[type];
|
|
@@ -4121,23 +4249,23 @@
|
|
|
4121
4249
|
if (handler) {
|
|
4122
4250
|
// Run statically defined handler in parallel with lazy()
|
|
4123
4251
|
let handlerError;
|
|
4124
|
-
let
|
|
4252
|
+
let [value] = await Promise.all([
|
|
4125
4253
|
// If the handler throws, don't let it immediately bubble out,
|
|
4126
4254
|
// since we need to let the lazy() execution finish so we know if this
|
|
4127
4255
|
// route has a boundary that can handle the error
|
|
4128
4256
|
runHandler(handler).catch(e => {
|
|
4129
4257
|
handlerError = e;
|
|
4130
4258
|
}), loadLazyRouteModule(match.route, mapRouteProperties, manifest)]);
|
|
4131
|
-
if (handlerError) {
|
|
4259
|
+
if (handlerError !== undefined) {
|
|
4132
4260
|
throw handlerError;
|
|
4133
4261
|
}
|
|
4134
|
-
result =
|
|
4262
|
+
result = value;
|
|
4135
4263
|
} else {
|
|
4136
4264
|
// Load lazy route module, then run any returned handler
|
|
4137
4265
|
await loadLazyRouteModule(match.route, mapRouteProperties, manifest);
|
|
4138
4266
|
handler = match.route[type];
|
|
4139
4267
|
if (handler) {
|
|
4140
|
-
// Handler still
|
|
4268
|
+
// Handler still runs even if we got interrupted to maintain consistency
|
|
4141
4269
|
// with un-abortable behavior of handler execution on non-lazy or
|
|
4142
4270
|
// previously-lazy-loaded routes
|
|
4143
4271
|
result = await runHandler(handler);
|
|
@@ -4154,7 +4282,7 @@
|
|
|
4154
4282
|
// hit the invariant below that errors on returning undefined.
|
|
4155
4283
|
return {
|
|
4156
4284
|
type: ResultType.data,
|
|
4157
|
-
|
|
4285
|
+
result: undefined
|
|
4158
4286
|
};
|
|
4159
4287
|
}
|
|
4160
4288
|
}
|
|
@@ -4167,65 +4295,29 @@
|
|
|
4167
4295
|
} else {
|
|
4168
4296
|
result = await runHandler(handler);
|
|
4169
4297
|
}
|
|
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`.");
|
|
4298
|
+
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
4299
|
} catch (e) {
|
|
4172
|
-
|
|
4173
|
-
|
|
4300
|
+
// We should already be catching and converting normal handler executions to
|
|
4301
|
+
// HandlerResults and returning them, so anything that throws here is an
|
|
4302
|
+
// unexpected error we still need to wrap
|
|
4303
|
+
return {
|
|
4304
|
+
type: ResultType.error,
|
|
4305
|
+
result: e
|
|
4306
|
+
};
|
|
4174
4307
|
} finally {
|
|
4175
4308
|
if (onReject) {
|
|
4176
4309
|
request.signal.removeEventListener("abort", onReject);
|
|
4177
4310
|
}
|
|
4178
4311
|
}
|
|
4312
|
+
return result;
|
|
4313
|
+
}
|
|
4314
|
+
async function convertHandlerResultToDataResult(handlerResult) {
|
|
4315
|
+
let {
|
|
4316
|
+
result,
|
|
4317
|
+
type,
|
|
4318
|
+
status
|
|
4319
|
+
} = handlerResult;
|
|
4179
4320
|
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
4321
|
let data;
|
|
4230
4322
|
try {
|
|
4231
4323
|
let contentType = result.headers.get("Content-Type");
|
|
@@ -4246,10 +4338,11 @@
|
|
|
4246
4338
|
error: e
|
|
4247
4339
|
};
|
|
4248
4340
|
}
|
|
4249
|
-
if (
|
|
4341
|
+
if (type === ResultType.error) {
|
|
4250
4342
|
return {
|
|
4251
|
-
type:
|
|
4252
|
-
error: new ErrorResponseImpl(status, result.statusText, data),
|
|
4343
|
+
type: ResultType.error,
|
|
4344
|
+
error: new ErrorResponseImpl(result.status, result.statusText, data),
|
|
4345
|
+
statusCode: result.status,
|
|
4253
4346
|
headers: result.headers
|
|
4254
4347
|
};
|
|
4255
4348
|
}
|
|
@@ -4260,10 +4353,11 @@
|
|
|
4260
4353
|
headers: result.headers
|
|
4261
4354
|
};
|
|
4262
4355
|
}
|
|
4263
|
-
if (
|
|
4356
|
+
if (type === ResultType.error) {
|
|
4264
4357
|
return {
|
|
4265
|
-
type:
|
|
4266
|
-
error: result
|
|
4358
|
+
type: ResultType.error,
|
|
4359
|
+
error: result,
|
|
4360
|
+
statusCode: isRouteErrorResponse(result) ? result.status : status
|
|
4267
4361
|
};
|
|
4268
4362
|
}
|
|
4269
4363
|
if (isDeferredData(result)) {
|
|
@@ -4277,10 +4371,35 @@
|
|
|
4277
4371
|
}
|
|
4278
4372
|
return {
|
|
4279
4373
|
type: ResultType.data,
|
|
4280
|
-
data: result
|
|
4374
|
+
data: result,
|
|
4375
|
+
statusCode: status
|
|
4281
4376
|
};
|
|
4282
4377
|
}
|
|
4283
4378
|
|
|
4379
|
+
// Support relative routing in internal redirects
|
|
4380
|
+
function normalizeRelativeRoutingRedirectResponse(response, request, routeId, matches, basename, v7_relativeSplatPath) {
|
|
4381
|
+
let location = response.headers.get("Location");
|
|
4382
|
+
invariant(location, "Redirects returned/thrown from loaders/actions must have a Location header");
|
|
4383
|
+
if (!ABSOLUTE_URL_REGEX.test(location)) {
|
|
4384
|
+
let trimmedMatches = matches.slice(0, matches.findIndex(m => m.route.id === routeId) + 1);
|
|
4385
|
+
location = normalizeTo(new URL(request.url), trimmedMatches, basename, true, location, v7_relativeSplatPath);
|
|
4386
|
+
response.headers.set("Location", location);
|
|
4387
|
+
}
|
|
4388
|
+
return response;
|
|
4389
|
+
}
|
|
4390
|
+
function normalizeRedirectLocation(location, currentUrl, basename) {
|
|
4391
|
+
if (ABSOLUTE_URL_REGEX.test(location)) {
|
|
4392
|
+
// Strip off the protocol+origin for same-origin + same-basename absolute redirects
|
|
4393
|
+
let normalizedLocation = location;
|
|
4394
|
+
let url = normalizedLocation.startsWith("//") ? new URL(currentUrl.protocol + normalizedLocation) : new URL(normalizedLocation);
|
|
4395
|
+
let isSameBasename = stripBasename(url.pathname, basename) != null;
|
|
4396
|
+
if (url.origin === currentUrl.origin && isSameBasename) {
|
|
4397
|
+
return url.pathname + url.search + url.hash;
|
|
4398
|
+
}
|
|
4399
|
+
}
|
|
4400
|
+
return location;
|
|
4401
|
+
}
|
|
4402
|
+
|
|
4284
4403
|
// Utility method for creating the Request instances for loaders/actions during
|
|
4285
4404
|
// client-side navigations and fetches. During SSR we will always have a
|
|
4286
4405
|
// Request instance from the static handler (query/queryRoute)
|
|
@@ -4331,35 +4450,39 @@
|
|
|
4331
4450
|
}
|
|
4332
4451
|
return formData;
|
|
4333
4452
|
}
|
|
4334
|
-
function processRouteLoaderData(matches, matchesToLoad, results,
|
|
4453
|
+
function processRouteLoaderData(matches, matchesToLoad, results, pendingActionResult, activeDeferreds, skipLoaderErrorBubbling) {
|
|
4335
4454
|
// Fill in loaderData/errors from our loaders
|
|
4336
4455
|
let loaderData = {};
|
|
4337
4456
|
let errors = null;
|
|
4338
4457
|
let statusCode;
|
|
4339
4458
|
let foundError = false;
|
|
4340
4459
|
let loaderHeaders = {};
|
|
4460
|
+
let pendingError = pendingActionResult && isErrorResult(pendingActionResult[1]) ? pendingActionResult[1].error : undefined;
|
|
4341
4461
|
|
|
4342
4462
|
// Process loader results into state.loaderData/state.errors
|
|
4343
4463
|
results.forEach((result, index) => {
|
|
4344
4464
|
let id = matchesToLoad[index].route.id;
|
|
4345
4465
|
invariant(!isRedirectResult(result), "Cannot handle redirect results in processLoaderData");
|
|
4346
4466
|
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
4467
|
let error = result.error;
|
|
4351
4468
|
// If we have a pending action error, we report it at the highest-route
|
|
4352
4469
|
// that throws a loader error, and then clear it out to indicate that
|
|
4353
4470
|
// it was consumed
|
|
4354
|
-
if (pendingError) {
|
|
4355
|
-
error =
|
|
4471
|
+
if (pendingError !== undefined) {
|
|
4472
|
+
error = pendingError;
|
|
4356
4473
|
pendingError = undefined;
|
|
4357
4474
|
}
|
|
4358
4475
|
errors = errors || {};
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
|
|
4362
|
-
|
|
4476
|
+
if (skipLoaderErrorBubbling) {
|
|
4477
|
+
errors[id] = error;
|
|
4478
|
+
} else {
|
|
4479
|
+
// Look upwards from the matched route for the closest ancestor error
|
|
4480
|
+
// boundary, defaulting to the root match. Prefer higher error values
|
|
4481
|
+
// if lower errors bubble to the same boundary
|
|
4482
|
+
let boundaryMatch = findNearestBoundary(matches, id);
|
|
4483
|
+
if (errors[boundaryMatch.route.id] == null) {
|
|
4484
|
+
errors[boundaryMatch.route.id] = error;
|
|
4485
|
+
}
|
|
4363
4486
|
}
|
|
4364
4487
|
|
|
4365
4488
|
// Clear our any prior loaderData for the throwing route
|
|
@@ -4378,17 +4501,24 @@
|
|
|
4378
4501
|
if (isDeferredResult(result)) {
|
|
4379
4502
|
activeDeferreds.set(id, result.deferredData);
|
|
4380
4503
|
loaderData[id] = result.deferredData.data;
|
|
4504
|
+
// Error status codes always override success status codes, but if all
|
|
4505
|
+
// loaders are successful we take the deepest status code.
|
|
4506
|
+
if (result.statusCode != null && result.statusCode !== 200 && !foundError) {
|
|
4507
|
+
statusCode = result.statusCode;
|
|
4508
|
+
}
|
|
4509
|
+
if (result.headers) {
|
|
4510
|
+
loaderHeaders[id] = result.headers;
|
|
4511
|
+
}
|
|
4381
4512
|
} else {
|
|
4382
4513
|
loaderData[id] = result.data;
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
loaderHeaders[id] = result.headers;
|
|
4514
|
+
// Error status codes always override success status codes, but if all
|
|
4515
|
+
// loaders are successful we take the deepest status code.
|
|
4516
|
+
if (result.statusCode && result.statusCode !== 200 && !foundError) {
|
|
4517
|
+
statusCode = result.statusCode;
|
|
4518
|
+
}
|
|
4519
|
+
if (result.headers) {
|
|
4520
|
+
loaderHeaders[id] = result.headers;
|
|
4521
|
+
}
|
|
4392
4522
|
}
|
|
4393
4523
|
}
|
|
4394
4524
|
});
|
|
@@ -4396,9 +4526,11 @@
|
|
|
4396
4526
|
// If we didn't consume the pending action error (i.e., all loaders
|
|
4397
4527
|
// resolved), then consume it here. Also clear out any loaderData for the
|
|
4398
4528
|
// throwing route
|
|
4399
|
-
if (pendingError) {
|
|
4400
|
-
errors =
|
|
4401
|
-
|
|
4529
|
+
if (pendingError !== undefined && pendingActionResult) {
|
|
4530
|
+
errors = {
|
|
4531
|
+
[pendingActionResult[0]]: pendingError
|
|
4532
|
+
};
|
|
4533
|
+
loaderData[pendingActionResult[0]] = undefined;
|
|
4402
4534
|
}
|
|
4403
4535
|
return {
|
|
4404
4536
|
loaderData,
|
|
@@ -4407,11 +4539,12 @@
|
|
|
4407
4539
|
loaderHeaders
|
|
4408
4540
|
};
|
|
4409
4541
|
}
|
|
4410
|
-
function processLoaderData(state, matches, matchesToLoad, results,
|
|
4542
|
+
function processLoaderData(state, matches, matchesToLoad, results, pendingActionResult, revalidatingFetchers, fetcherResults, activeDeferreds) {
|
|
4411
4543
|
let {
|
|
4412
4544
|
loaderData,
|
|
4413
4545
|
errors
|
|
4414
|
-
} = processRouteLoaderData(matches, matchesToLoad, results,
|
|
4546
|
+
} = processRouteLoaderData(matches, matchesToLoad, results, pendingActionResult, activeDeferreds, false // This method is only called client side so we always want to bubble
|
|
4547
|
+
);
|
|
4415
4548
|
|
|
4416
4549
|
// Process results from our revalidating fetchers
|
|
4417
4550
|
for (let index = 0; index < revalidatingFetchers.length; index++) {
|
|
@@ -4473,6 +4606,19 @@
|
|
|
4473
4606
|
}
|
|
4474
4607
|
return mergedLoaderData;
|
|
4475
4608
|
}
|
|
4609
|
+
function getActionDataForCommit(pendingActionResult) {
|
|
4610
|
+
if (!pendingActionResult) {
|
|
4611
|
+
return {};
|
|
4612
|
+
}
|
|
4613
|
+
return isErrorResult(pendingActionResult[1]) ? {
|
|
4614
|
+
// Clear out prior actionData on errors
|
|
4615
|
+
actionData: {}
|
|
4616
|
+
} : {
|
|
4617
|
+
actionData: {
|
|
4618
|
+
[pendingActionResult[0]]: pendingActionResult[1].data
|
|
4619
|
+
}
|
|
4620
|
+
};
|
|
4621
|
+
}
|
|
4476
4622
|
|
|
4477
4623
|
// Find the nearest error boundary, looking upwards from the leaf route (or the
|
|
4478
4624
|
// route specified by routeId) for the closest ancestor error boundary,
|
|
@@ -4568,6 +4714,12 @@
|
|
|
4568
4714
|
// /page#hash -> /page
|
|
4569
4715
|
return false;
|
|
4570
4716
|
}
|
|
4717
|
+
function isHandlerResult(result) {
|
|
4718
|
+
return result != null && typeof result === "object" && "type" in result && "result" in result && (result.type === ResultType.data || result.type === ResultType.error);
|
|
4719
|
+
}
|
|
4720
|
+
function isRedirectHandlerResult(result) {
|
|
4721
|
+
return isResponse(result.result) && redirectStatusCodes.has(result.result.status);
|
|
4722
|
+
}
|
|
4571
4723
|
function isDeferredResult(result) {
|
|
4572
4724
|
return result.type === ResultType.deferred;
|
|
4573
4725
|
}
|
|
@@ -4592,9 +4744,6 @@
|
|
|
4592
4744
|
let location = result.headers.get("Location");
|
|
4593
4745
|
return status >= 300 && status <= 399 && location != null;
|
|
4594
4746
|
}
|
|
4595
|
-
function isQueryRouteResponse(obj) {
|
|
4596
|
-
return obj && isResponse(obj.response) && (obj.type === ResultType.data || obj.type === ResultType.error);
|
|
4597
|
-
}
|
|
4598
4747
|
function isValidMethod(method) {
|
|
4599
4748
|
return validRequestMethods.has(method.toLowerCase());
|
|
4600
4749
|
}
|