@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.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
|
*
|
|
@@ -1320,13 +1320,15 @@ function createRouter(init) {
|
|
|
1320
1320
|
let dataRoutes = convertRoutesToDataRoutes(init.routes, mapRouteProperties, undefined, manifest);
|
|
1321
1321
|
let inFlightDataRoutes;
|
|
1322
1322
|
let basename = init.basename || "/";
|
|
1323
|
+
let dataStrategyImpl = init.unstable_dataStrategy || defaultDataStrategy;
|
|
1323
1324
|
// Config driven behavior flags
|
|
1324
1325
|
let future = _extends({
|
|
1325
1326
|
v7_fetcherPersist: false,
|
|
1326
1327
|
v7_normalizeFormMethod: false,
|
|
1327
1328
|
v7_partialHydration: false,
|
|
1328
1329
|
v7_prependBasename: false,
|
|
1329
|
-
v7_relativeSplatPath: false
|
|
1330
|
+
v7_relativeSplatPath: false,
|
|
1331
|
+
unstable_skipActionErrorRevalidation: false
|
|
1330
1332
|
}, init.future);
|
|
1331
1333
|
// Cleanup function for history
|
|
1332
1334
|
let unlistenHistory = null;
|
|
@@ -1380,9 +1382,13 @@ function createRouter(init) {
|
|
|
1380
1382
|
let errors = init.hydrationData ? init.hydrationData.errors : null;
|
|
1381
1383
|
let isRouteInitialized = m => {
|
|
1382
1384
|
// No loader, nothing to initialize
|
|
1383
|
-
if (!m.route.loader)
|
|
1385
|
+
if (!m.route.loader) {
|
|
1386
|
+
return true;
|
|
1387
|
+
}
|
|
1384
1388
|
// Explicitly opting-in to running on hydration
|
|
1385
|
-
if (m.route.loader.hydrate === true)
|
|
1389
|
+
if (typeof m.route.loader === "function" && m.route.loader.hydrate === true) {
|
|
1390
|
+
return false;
|
|
1391
|
+
}
|
|
1386
1392
|
// Otherwise, initialized if hydrated with data or an error
|
|
1387
1393
|
return loaderData && loaderData[m.route.id] !== undefined || errors && errors[m.route.id] !== undefined;
|
|
1388
1394
|
};
|
|
@@ -1874,40 +1880,37 @@ function createRouter(init) {
|
|
|
1874
1880
|
// Create a controller/Request for this navigation
|
|
1875
1881
|
pendingNavigationController = new AbortController();
|
|
1876
1882
|
let request = createClientSideRequest(init.history, location, pendingNavigationController.signal, opts && opts.submission);
|
|
1877
|
-
let
|
|
1878
|
-
let pendingError;
|
|
1883
|
+
let pendingActionResult;
|
|
1879
1884
|
if (opts && opts.pendingError) {
|
|
1880
1885
|
// If we have a pendingError, it means the user attempted a GET submission
|
|
1881
1886
|
// with binary FormData so assign here and skip to handleLoaders. That
|
|
1882
1887
|
// way we handle calling loaders above the boundary etc. It's not really
|
|
1883
1888
|
// different from an actionError in that sense.
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1889
|
+
pendingActionResult = [findNearestBoundary(matches).route.id, {
|
|
1890
|
+
type: ResultType.error,
|
|
1891
|
+
error: opts.pendingError
|
|
1892
|
+
}];
|
|
1887
1893
|
} else if (opts && opts.submission && isMutationMethod(opts.submission.formMethod)) {
|
|
1888
1894
|
// Call action if we received an action submission
|
|
1889
|
-
let
|
|
1895
|
+
let actionResult = await handleAction(request, location, opts.submission, matches, {
|
|
1890
1896
|
replace: opts.replace,
|
|
1891
1897
|
flushSync
|
|
1892
1898
|
});
|
|
1893
|
-
if (
|
|
1899
|
+
if (actionResult.shortCircuited) {
|
|
1894
1900
|
return;
|
|
1895
1901
|
}
|
|
1896
|
-
|
|
1897
|
-
pendingError = actionOutput.pendingActionError;
|
|
1902
|
+
pendingActionResult = actionResult.pendingActionResult;
|
|
1898
1903
|
loadingNavigation = getLoadingNavigation(location, opts.submission);
|
|
1899
1904
|
flushSync = false;
|
|
1900
1905
|
// Create a GET request for the loaders
|
|
1901
|
-
request =
|
|
1902
|
-
signal: request.signal
|
|
1903
|
-
});
|
|
1906
|
+
request = createClientSideRequest(init.history, request.url, request.signal);
|
|
1904
1907
|
}
|
|
1905
1908
|
// Call loaders
|
|
1906
1909
|
let {
|
|
1907
1910
|
shortCircuited,
|
|
1908
1911
|
loaderData,
|
|
1909
1912
|
errors
|
|
1910
|
-
} = await handleLoaders(request, location, matches, loadingNavigation, opts && opts.submission, opts && opts.fetcherSubmission, opts && opts.replace, opts && opts.initialHydration === true, flushSync,
|
|
1913
|
+
} = await handleLoaders(request, location, matches, loadingNavigation, opts && opts.submission, opts && opts.fetcherSubmission, opts && opts.replace, opts && opts.initialHydration === true, flushSync, pendingActionResult);
|
|
1911
1914
|
if (shortCircuited) {
|
|
1912
1915
|
return;
|
|
1913
1916
|
}
|
|
@@ -1917,9 +1920,7 @@ function createRouter(init) {
|
|
|
1917
1920
|
pendingNavigationController = null;
|
|
1918
1921
|
completeNavigation(location, _extends({
|
|
1919
1922
|
matches
|
|
1920
|
-
},
|
|
1921
|
-
actionData: pendingActionData
|
|
1922
|
-
} : {}, {
|
|
1923
|
+
}, getActionDataForCommit(pendingActionResult), {
|
|
1923
1924
|
loaderData,
|
|
1924
1925
|
errors
|
|
1925
1926
|
}));
|
|
@@ -1951,7 +1952,8 @@ function createRouter(init) {
|
|
|
1951
1952
|
})
|
|
1952
1953
|
};
|
|
1953
1954
|
} else {
|
|
1954
|
-
|
|
1955
|
+
let results = await callDataStrategy("action", request, [actionMatch], matches);
|
|
1956
|
+
result = results[0];
|
|
1955
1957
|
if (request.signal.aborted) {
|
|
1956
1958
|
return {
|
|
1957
1959
|
shortCircuited: true
|
|
@@ -1966,9 +1968,10 @@ function createRouter(init) {
|
|
|
1966
1968
|
// If the user didn't explicity indicate replace behavior, replace if
|
|
1967
1969
|
// we redirected to the exact same location we're currently at to avoid
|
|
1968
1970
|
// double back-buttons
|
|
1969
|
-
|
|
1971
|
+
let location = normalizeRedirectLocation(result.response.headers.get("Location"), new URL(request.url), basename);
|
|
1972
|
+
replace = location === state.location.pathname + state.location.search;
|
|
1970
1973
|
}
|
|
1971
|
-
await startRedirectNavigation(
|
|
1974
|
+
await startRedirectNavigation(request, result, {
|
|
1972
1975
|
submission,
|
|
1973
1976
|
replace
|
|
1974
1977
|
});
|
|
@@ -1976,6 +1979,11 @@ function createRouter(init) {
|
|
|
1976
1979
|
shortCircuited: true
|
|
1977
1980
|
};
|
|
1978
1981
|
}
|
|
1982
|
+
if (isDeferredResult(result)) {
|
|
1983
|
+
throw getInternalRouterError(400, {
|
|
1984
|
+
type: "defer-action"
|
|
1985
|
+
});
|
|
1986
|
+
}
|
|
1979
1987
|
if (isErrorResult(result)) {
|
|
1980
1988
|
// Store off the pending error - we use it to determine which loaders
|
|
1981
1989
|
// to call and will commit it when we complete the navigation
|
|
@@ -1988,34 +1996,23 @@ function createRouter(init) {
|
|
|
1988
1996
|
pendingAction = Action.Push;
|
|
1989
1997
|
}
|
|
1990
1998
|
return {
|
|
1991
|
-
|
|
1992
|
-
pendingActionData: {},
|
|
1993
|
-
pendingActionError: {
|
|
1994
|
-
[boundaryMatch.route.id]: result.error
|
|
1995
|
-
}
|
|
1999
|
+
pendingActionResult: [boundaryMatch.route.id, result]
|
|
1996
2000
|
};
|
|
1997
2001
|
}
|
|
1998
|
-
if (isDeferredResult(result)) {
|
|
1999
|
-
throw getInternalRouterError(400, {
|
|
2000
|
-
type: "defer-action"
|
|
2001
|
-
});
|
|
2002
|
-
}
|
|
2003
2002
|
return {
|
|
2004
|
-
|
|
2005
|
-
[actionMatch.route.id]: result.data
|
|
2006
|
-
}
|
|
2003
|
+
pendingActionResult: [actionMatch.route.id, result]
|
|
2007
2004
|
};
|
|
2008
2005
|
}
|
|
2009
2006
|
// Call all applicable loaders for the given matches, handling redirects,
|
|
2010
2007
|
// errors, etc.
|
|
2011
|
-
async function handleLoaders(request, location, matches, overrideNavigation, submission, fetcherSubmission, replace, initialHydration, flushSync,
|
|
2008
|
+
async function handleLoaders(request, location, matches, overrideNavigation, submission, fetcherSubmission, replace, initialHydration, flushSync, pendingActionResult) {
|
|
2012
2009
|
// Figure out the right navigation we want to use for data loading
|
|
2013
2010
|
let loadingNavigation = overrideNavigation || getLoadingNavigation(location, submission);
|
|
2014
2011
|
// If this was a redirect from an action we don't have a "submission" but
|
|
2015
2012
|
// we have it on the loading navigation so use that if available
|
|
2016
2013
|
let activeSubmission = submission || fetcherSubmission || getSubmissionFromNavigation(loadingNavigation);
|
|
2017
2014
|
let routesToUse = inFlightDataRoutes || dataRoutes;
|
|
2018
|
-
let [matchesToLoad, revalidatingFetchers] = getMatchesToLoad(init.history, state, matches, activeSubmission, location, future.v7_partialHydration && initialHydration === true, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, deletedFetchers, fetchLoadMatches, fetchRedirectIds, routesToUse, basename,
|
|
2015
|
+
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);
|
|
2019
2016
|
// Cancel pending deferreds for no-longer-matched routes or routes we're
|
|
2020
2017
|
// about to reload. Note that if this is an action reload we would have
|
|
2021
2018
|
// already cancelled all pending deferreds so this would be a no-op
|
|
@@ -2028,10 +2025,10 @@ function createRouter(init) {
|
|
|
2028
2025
|
matches,
|
|
2029
2026
|
loaderData: {},
|
|
2030
2027
|
// Commit pending error if we're short circuiting
|
|
2031
|
-
errors:
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
}
|
|
2028
|
+
errors: pendingActionResult && isErrorResult(pendingActionResult[1]) ? {
|
|
2029
|
+
[pendingActionResult[0]]: pendingActionResult[1].error
|
|
2030
|
+
} : null
|
|
2031
|
+
}, getActionDataForCommit(pendingActionResult), updatedFetchers ? {
|
|
2035
2032
|
fetchers: new Map(state.fetchers)
|
|
2036
2033
|
} : {}), {
|
|
2037
2034
|
flushSync
|
|
@@ -2052,12 +2049,24 @@ function createRouter(init) {
|
|
|
2052
2049
|
let revalidatingFetcher = getLoadingFetcher(undefined, fetcher ? fetcher.data : undefined);
|
|
2053
2050
|
state.fetchers.set(rf.key, revalidatingFetcher);
|
|
2054
2051
|
});
|
|
2055
|
-
let actionData
|
|
2052
|
+
let actionData;
|
|
2053
|
+
if (pendingActionResult && !isErrorResult(pendingActionResult[1])) {
|
|
2054
|
+
// This is cast to `any` currently because `RouteData`uses any and it
|
|
2055
|
+
// would be a breaking change to use any.
|
|
2056
|
+
// TODO: v7 - change `RouteData` to use `unknown` instead of `any`
|
|
2057
|
+
actionData = {
|
|
2058
|
+
[pendingActionResult[0]]: pendingActionResult[1].data
|
|
2059
|
+
};
|
|
2060
|
+
} else if (state.actionData) {
|
|
2061
|
+
if (Object.keys(state.actionData).length === 0) {
|
|
2062
|
+
actionData = null;
|
|
2063
|
+
} else {
|
|
2064
|
+
actionData = state.actionData;
|
|
2065
|
+
}
|
|
2066
|
+
}
|
|
2056
2067
|
updateState(_extends({
|
|
2057
2068
|
navigation: loadingNavigation
|
|
2058
|
-
}, actionData
|
|
2059
|
-
actionData: null
|
|
2060
|
-
} : {
|
|
2069
|
+
}, actionData !== undefined ? {
|
|
2061
2070
|
actionData
|
|
2062
2071
|
} : {}, revalidatingFetchers.length > 0 ? {
|
|
2063
2072
|
fetchers: new Map(state.fetchers)
|
|
@@ -2082,7 +2091,6 @@ function createRouter(init) {
|
|
|
2082
2091
|
pendingNavigationController.signal.addEventListener("abort", abortPendingFetchRevalidations);
|
|
2083
2092
|
}
|
|
2084
2093
|
let {
|
|
2085
|
-
results,
|
|
2086
2094
|
loaderResults,
|
|
2087
2095
|
fetcherResults
|
|
2088
2096
|
} = await callLoadersAndMaybeResolveData(state.matches, matches, matchesToLoad, revalidatingFetchers, request);
|
|
@@ -2099,7 +2107,7 @@ function createRouter(init) {
|
|
|
2099
2107
|
}
|
|
2100
2108
|
revalidatingFetchers.forEach(rf => fetchControllers.delete(rf.key));
|
|
2101
2109
|
// If any loaders returned a redirect Response, start a new REPLACE navigation
|
|
2102
|
-
let redirect = findRedirect(
|
|
2110
|
+
let redirect = findRedirect([...loaderResults, ...fetcherResults]);
|
|
2103
2111
|
if (redirect) {
|
|
2104
2112
|
if (redirect.idx >= matchesToLoad.length) {
|
|
2105
2113
|
// If this redirect came from a fetcher make sure we mark it in
|
|
@@ -2108,7 +2116,7 @@ function createRouter(init) {
|
|
|
2108
2116
|
let fetcherKey = revalidatingFetchers[redirect.idx - matchesToLoad.length].key;
|
|
2109
2117
|
fetchRedirectIds.add(fetcherKey);
|
|
2110
2118
|
}
|
|
2111
|
-
await startRedirectNavigation(
|
|
2119
|
+
await startRedirectNavigation(request, redirect.result, {
|
|
2112
2120
|
replace
|
|
2113
2121
|
});
|
|
2114
2122
|
return {
|
|
@@ -2119,7 +2127,7 @@ function createRouter(init) {
|
|
|
2119
2127
|
let {
|
|
2120
2128
|
loaderData,
|
|
2121
2129
|
errors
|
|
2122
|
-
} = processLoaderData(state, matches, matchesToLoad, loaderResults,
|
|
2130
|
+
} = processLoaderData(state, matches, matchesToLoad, loaderResults, pendingActionResult, revalidatingFetchers, fetcherResults, activeDeferreds);
|
|
2123
2131
|
// Wire up subscribers to update loaderData as promises settle
|
|
2124
2132
|
activeDeferreds.forEach((deferredData, routeId) => {
|
|
2125
2133
|
deferredData.subscribe(aborted => {
|
|
@@ -2222,7 +2230,8 @@ function createRouter(init) {
|
|
|
2222
2230
|
let fetchRequest = createClientSideRequest(init.history, path, abortController.signal, submission);
|
|
2223
2231
|
fetchControllers.set(key, abortController);
|
|
2224
2232
|
let originatingLoadId = incrementingLoadId;
|
|
2225
|
-
let
|
|
2233
|
+
let actionResults = await callDataStrategy("action", fetchRequest, [match], requestMatches);
|
|
2234
|
+
let actionResult = actionResults[0];
|
|
2226
2235
|
if (fetchRequest.signal.aborted) {
|
|
2227
2236
|
// We can delete this so long as we weren't aborted by our own fetcher
|
|
2228
2237
|
// re-submit which would have put _new_ controller is in fetchControllers
|
|
@@ -2253,7 +2262,7 @@ function createRouter(init) {
|
|
|
2253
2262
|
} else {
|
|
2254
2263
|
fetchRedirectIds.add(key);
|
|
2255
2264
|
updateFetcherState(key, getLoadingFetcher(submission));
|
|
2256
|
-
return startRedirectNavigation(
|
|
2265
|
+
return startRedirectNavigation(fetchRequest, actionResult, {
|
|
2257
2266
|
fetcherSubmission: submission
|
|
2258
2267
|
});
|
|
2259
2268
|
}
|
|
@@ -2280,10 +2289,7 @@ function createRouter(init) {
|
|
|
2280
2289
|
fetchReloadIds.set(key, loadId);
|
|
2281
2290
|
let loadFetcher = getLoadingFetcher(submission, actionResult.data);
|
|
2282
2291
|
state.fetchers.set(key, loadFetcher);
|
|
2283
|
-
let [matchesToLoad, revalidatingFetchers] = getMatchesToLoad(init.history, state, matches, submission, nextLocation, false, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, deletedFetchers, fetchLoadMatches, fetchRedirectIds, routesToUse, basename,
|
|
2284
|
-
[match.route.id]: actionResult.data
|
|
2285
|
-
}, undefined // No need to send through errors since we short circuit above
|
|
2286
|
-
);
|
|
2292
|
+
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]);
|
|
2287
2293
|
// Put all revalidating fetchers into the loading state, except for the
|
|
2288
2294
|
// current fetcher which we want to keep in it's current loading state which
|
|
2289
2295
|
// contains it's action submission info + action data
|
|
@@ -2305,7 +2311,6 @@ function createRouter(init) {
|
|
|
2305
2311
|
let abortPendingFetchRevalidations = () => revalidatingFetchers.forEach(rf => abortFetcher(rf.key));
|
|
2306
2312
|
abortController.signal.addEventListener("abort", abortPendingFetchRevalidations);
|
|
2307
2313
|
let {
|
|
2308
|
-
results,
|
|
2309
2314
|
loaderResults,
|
|
2310
2315
|
fetcherResults
|
|
2311
2316
|
} = await callLoadersAndMaybeResolveData(state.matches, matches, matchesToLoad, revalidatingFetchers, revalidationRequest);
|
|
@@ -2316,7 +2321,7 @@ function createRouter(init) {
|
|
|
2316
2321
|
fetchReloadIds.delete(key);
|
|
2317
2322
|
fetchControllers.delete(key);
|
|
2318
2323
|
revalidatingFetchers.forEach(r => fetchControllers.delete(r.key));
|
|
2319
|
-
let redirect = findRedirect(
|
|
2324
|
+
let redirect = findRedirect([...loaderResults, ...fetcherResults]);
|
|
2320
2325
|
if (redirect) {
|
|
2321
2326
|
if (redirect.idx >= matchesToLoad.length) {
|
|
2322
2327
|
// If this redirect came from a fetcher make sure we mark it in
|
|
@@ -2325,7 +2330,7 @@ function createRouter(init) {
|
|
|
2325
2330
|
let fetcherKey = revalidatingFetchers[redirect.idx - matchesToLoad.length].key;
|
|
2326
2331
|
fetchRedirectIds.add(fetcherKey);
|
|
2327
2332
|
}
|
|
2328
|
-
return startRedirectNavigation(
|
|
2333
|
+
return startRedirectNavigation(revalidationRequest, redirect.result);
|
|
2329
2334
|
}
|
|
2330
2335
|
// Process and commit output from loaders
|
|
2331
2336
|
let {
|
|
@@ -2374,7 +2379,8 @@ function createRouter(init) {
|
|
|
2374
2379
|
let fetchRequest = createClientSideRequest(init.history, path, abortController.signal);
|
|
2375
2380
|
fetchControllers.set(key, abortController);
|
|
2376
2381
|
let originatingLoadId = incrementingLoadId;
|
|
2377
|
-
let
|
|
2382
|
+
let results = await callDataStrategy("loader", fetchRequest, [match], matches);
|
|
2383
|
+
let result = results[0];
|
|
2378
2384
|
// Deferred isn't supported for fetcher loads, await everything and treat it
|
|
2379
2385
|
// as a normal load. resolveDeferredData will return undefined if this
|
|
2380
2386
|
// fetcher gets aborted, so we just leave result untouched and short circuit
|
|
@@ -2405,7 +2411,7 @@ function createRouter(init) {
|
|
|
2405
2411
|
return;
|
|
2406
2412
|
} else {
|
|
2407
2413
|
fetchRedirectIds.add(key);
|
|
2408
|
-
await startRedirectNavigation(
|
|
2414
|
+
await startRedirectNavigation(fetchRequest, result);
|
|
2409
2415
|
return;
|
|
2410
2416
|
}
|
|
2411
2417
|
}
|
|
@@ -2437,26 +2443,28 @@ function createRouter(init) {
|
|
|
2437
2443
|
* actually touch history until we've processed redirects, so we just use
|
|
2438
2444
|
* the history action from the original navigation (PUSH or REPLACE).
|
|
2439
2445
|
*/
|
|
2440
|
-
async function startRedirectNavigation(
|
|
2446
|
+
async function startRedirectNavigation(request, redirect, _temp2) {
|
|
2441
2447
|
let {
|
|
2442
2448
|
submission,
|
|
2443
2449
|
fetcherSubmission,
|
|
2444
2450
|
replace
|
|
2445
2451
|
} = _temp2 === void 0 ? {} : _temp2;
|
|
2446
|
-
if (redirect.
|
|
2452
|
+
if (redirect.response.headers.has("X-Remix-Revalidate")) {
|
|
2447
2453
|
isRevalidationRequired = true;
|
|
2448
2454
|
}
|
|
2449
|
-
let
|
|
2455
|
+
let location = redirect.response.headers.get("Location");
|
|
2456
|
+
invariant(location, "Expected a Location header on the redirect Response");
|
|
2457
|
+
location = normalizeRedirectLocation(location, new URL(request.url), basename);
|
|
2458
|
+
let redirectLocation = createLocation(state.location, location, {
|
|
2450
2459
|
_isRedirect: true
|
|
2451
2460
|
});
|
|
2452
|
-
invariant(redirectLocation, "Expected a location on the redirect navigation");
|
|
2453
2461
|
if (isBrowser) {
|
|
2454
2462
|
let isDocumentReload = false;
|
|
2455
|
-
if (redirect.
|
|
2463
|
+
if (redirect.response.headers.has("X-Remix-Reload-Document")) {
|
|
2456
2464
|
// Hard reload if the response contained X-Remix-Reload-Document
|
|
2457
2465
|
isDocumentReload = true;
|
|
2458
|
-
} else if (ABSOLUTE_URL_REGEX.test(
|
|
2459
|
-
const url = init.history.createURL(
|
|
2466
|
+
} else if (ABSOLUTE_URL_REGEX.test(location)) {
|
|
2467
|
+
const url = init.history.createURL(location);
|
|
2460
2468
|
isDocumentReload =
|
|
2461
2469
|
// Hard reload if it's an absolute URL to a new origin
|
|
2462
2470
|
url.origin !== routerWindow.location.origin ||
|
|
@@ -2465,9 +2473,9 @@ function createRouter(init) {
|
|
|
2465
2473
|
}
|
|
2466
2474
|
if (isDocumentReload) {
|
|
2467
2475
|
if (replace) {
|
|
2468
|
-
routerWindow.location.replace(
|
|
2476
|
+
routerWindow.location.replace(location);
|
|
2469
2477
|
} else {
|
|
2470
|
-
routerWindow.location.assign(
|
|
2478
|
+
routerWindow.location.assign(location);
|
|
2471
2479
|
}
|
|
2472
2480
|
return;
|
|
2473
2481
|
}
|
|
@@ -2490,10 +2498,10 @@ function createRouter(init) {
|
|
|
2490
2498
|
// re-submit the GET/POST/PUT/PATCH/DELETE as a submission navigation to the
|
|
2491
2499
|
// redirected location
|
|
2492
2500
|
let activeSubmission = submission || fetcherSubmission;
|
|
2493
|
-
if (redirectPreserveMethodStatusCodes.has(redirect.status) && activeSubmission && isMutationMethod(activeSubmission.formMethod)) {
|
|
2501
|
+
if (redirectPreserveMethodStatusCodes.has(redirect.response.status) && activeSubmission && isMutationMethod(activeSubmission.formMethod)) {
|
|
2494
2502
|
await startNavigation(redirectHistoryAction, redirectLocation, {
|
|
2495
2503
|
submission: _extends({}, activeSubmission, {
|
|
2496
|
-
formAction:
|
|
2504
|
+
formAction: location
|
|
2497
2505
|
}),
|
|
2498
2506
|
// Preserve this flag across redirects
|
|
2499
2507
|
preventScrollReset: pendingPreventScrollReset
|
|
@@ -2511,28 +2519,46 @@ function createRouter(init) {
|
|
|
2511
2519
|
});
|
|
2512
2520
|
}
|
|
2513
2521
|
}
|
|
2522
|
+
// Utility wrapper for calling dataStrategy client-side without having to
|
|
2523
|
+
// pass around the manifest, mapRouteProperties, etc.
|
|
2524
|
+
async function callDataStrategy(type, request, matchesToLoad, matches) {
|
|
2525
|
+
try {
|
|
2526
|
+
let results = await callDataStrategyImpl(dataStrategyImpl, type, request, matchesToLoad, matches, manifest, mapRouteProperties);
|
|
2527
|
+
return await Promise.all(results.map((result, i) => {
|
|
2528
|
+
if (isRedirectHandlerResult(result)) {
|
|
2529
|
+
let response = result.result;
|
|
2530
|
+
return {
|
|
2531
|
+
type: ResultType.redirect,
|
|
2532
|
+
response: normalizeRelativeRoutingRedirectResponse(response, request, matchesToLoad[i].route.id, matches, basename, future.v7_relativeSplatPath)
|
|
2533
|
+
};
|
|
2534
|
+
}
|
|
2535
|
+
return convertHandlerResultToDataResult(result);
|
|
2536
|
+
}));
|
|
2537
|
+
} catch (e) {
|
|
2538
|
+
// If the outer dataStrategy method throws, just return the error for all
|
|
2539
|
+
// matches - and it'll naturally bubble to the root
|
|
2540
|
+
return matchesToLoad.map(() => ({
|
|
2541
|
+
type: ResultType.error,
|
|
2542
|
+
error: e
|
|
2543
|
+
}));
|
|
2544
|
+
}
|
|
2545
|
+
}
|
|
2514
2546
|
async function callLoadersAndMaybeResolveData(currentMatches, matches, matchesToLoad, fetchersToLoad, request) {
|
|
2515
|
-
|
|
2516
|
-
// then slice off the results into separate arrays so we can handle them
|
|
2517
|
-
// accordingly
|
|
2518
|
-
let results = await Promise.all([...matchesToLoad.map(match => callLoaderOrAction("loader", request, match, matches, manifest, mapRouteProperties, basename, future.v7_relativeSplatPath)), ...fetchersToLoad.map(f => {
|
|
2547
|
+
let [loaderResults, ...fetcherResults] = await Promise.all([matchesToLoad.length ? callDataStrategy("loader", request, matchesToLoad, matches) : [], ...fetchersToLoad.map(f => {
|
|
2519
2548
|
if (f.matches && f.match && f.controller) {
|
|
2520
|
-
|
|
2549
|
+
let fetcherRequest = createClientSideRequest(init.history, f.path, f.controller.signal);
|
|
2550
|
+
return callDataStrategy("loader", fetcherRequest, [f.match], f.matches).then(r => r[0]);
|
|
2521
2551
|
} else {
|
|
2522
|
-
|
|
2552
|
+
return Promise.resolve({
|
|
2523
2553
|
type: ResultType.error,
|
|
2524
2554
|
error: getInternalRouterError(404, {
|
|
2525
2555
|
pathname: f.path
|
|
2526
2556
|
})
|
|
2527
|
-
};
|
|
2528
|
-
return error;
|
|
2557
|
+
});
|
|
2529
2558
|
}
|
|
2530
2559
|
})]);
|
|
2531
|
-
let loaderResults = results.slice(0, matchesToLoad.length);
|
|
2532
|
-
let fetcherResults = results.slice(matchesToLoad.length);
|
|
2533
2560
|
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)]);
|
|
2534
2561
|
return {
|
|
2535
|
-
results,
|
|
2536
2562
|
loaderResults,
|
|
2537
2563
|
fetcherResults
|
|
2538
2564
|
};
|
|
@@ -2866,10 +2892,19 @@ function createStaticHandler(routes, opts) {
|
|
|
2866
2892
|
* redirect response is returned or thrown from any action/loader. We
|
|
2867
2893
|
* propagate that out and return the raw Response so the HTTP server can
|
|
2868
2894
|
* return it directly.
|
|
2895
|
+
*
|
|
2896
|
+
* - `opts.requestContext` is an optional server context that will be passed
|
|
2897
|
+
* to actions/loaders in the `context` parameter
|
|
2898
|
+
* - `opts.skipLoaderErrorBubbling` is an optional parameter that will prevent
|
|
2899
|
+
* the bubbling of errors which allows single-fetch-type implementations
|
|
2900
|
+
* where the client will handle the bubbling and we may need to return data
|
|
2901
|
+
* for the handling route
|
|
2869
2902
|
*/
|
|
2870
2903
|
async function query(request, _temp3) {
|
|
2871
2904
|
let {
|
|
2872
|
-
requestContext
|
|
2905
|
+
requestContext,
|
|
2906
|
+
skipLoaderErrorBubbling,
|
|
2907
|
+
unstable_dataStrategy
|
|
2873
2908
|
} = _temp3 === void 0 ? {} : _temp3;
|
|
2874
2909
|
let url = new URL(request.url);
|
|
2875
2910
|
let method = request.method;
|
|
@@ -2921,7 +2956,7 @@ function createStaticHandler(routes, opts) {
|
|
|
2921
2956
|
activeDeferreds: null
|
|
2922
2957
|
};
|
|
2923
2958
|
}
|
|
2924
|
-
let result = await queryImpl(request, location, matches, requestContext);
|
|
2959
|
+
let result = await queryImpl(request, location, matches, requestContext, unstable_dataStrategy || null, skipLoaderErrorBubbling === true, null);
|
|
2925
2960
|
if (isResponse(result)) {
|
|
2926
2961
|
return result;
|
|
2927
2962
|
}
|
|
@@ -2952,6 +2987,12 @@ function createStaticHandler(routes, opts) {
|
|
|
2952
2987
|
* serialize the error as they see fit while including the proper response
|
|
2953
2988
|
* code. Examples here are 404 and 405 errors that occur prior to reaching
|
|
2954
2989
|
* any user-defined loaders.
|
|
2990
|
+
*
|
|
2991
|
+
* - `opts.routeId` allows you to specify the specific route handler to call.
|
|
2992
|
+
* If not provided the handler will determine the proper route by matching
|
|
2993
|
+
* against `request.url`
|
|
2994
|
+
* - `opts.requestContext` is an optional server context that will be passed
|
|
2995
|
+
* to actions/loaders in the `context` parameter
|
|
2955
2996
|
*/
|
|
2956
2997
|
async function queryRoute(request, _temp4) {
|
|
2957
2998
|
let {
|
|
@@ -2984,7 +3025,7 @@ function createStaticHandler(routes, opts) {
|
|
|
2984
3025
|
pathname: location.pathname
|
|
2985
3026
|
});
|
|
2986
3027
|
}
|
|
2987
|
-
let result = await queryImpl(request, location, matches, requestContext, match);
|
|
3028
|
+
let result = await queryImpl(request, location, matches, requestContext, null, false, match);
|
|
2988
3029
|
if (isResponse(result)) {
|
|
2989
3030
|
return result;
|
|
2990
3031
|
}
|
|
@@ -3010,27 +3051,27 @@ function createStaticHandler(routes, opts) {
|
|
|
3010
3051
|
}
|
|
3011
3052
|
return undefined;
|
|
3012
3053
|
}
|
|
3013
|
-
async function queryImpl(request, location, matches, requestContext, routeMatch) {
|
|
3054
|
+
async function queryImpl(request, location, matches, requestContext, unstable_dataStrategy, skipLoaderErrorBubbling, routeMatch) {
|
|
3014
3055
|
invariant(request.signal, "query()/queryRoute() requests must contain an AbortController signal");
|
|
3015
3056
|
try {
|
|
3016
3057
|
if (isMutationMethod(request.method.toLowerCase())) {
|
|
3017
|
-
let result = await submit(request, matches, routeMatch || getTargetMatch(matches, location), requestContext, routeMatch != null);
|
|
3058
|
+
let result = await submit(request, matches, routeMatch || getTargetMatch(matches, location), requestContext, unstable_dataStrategy, skipLoaderErrorBubbling, routeMatch != null);
|
|
3018
3059
|
return result;
|
|
3019
3060
|
}
|
|
3020
|
-
let result = await loadRouteData(request, matches, requestContext, routeMatch);
|
|
3061
|
+
let result = await loadRouteData(request, matches, requestContext, unstable_dataStrategy, skipLoaderErrorBubbling, routeMatch);
|
|
3021
3062
|
return isResponse(result) ? result : _extends({}, result, {
|
|
3022
3063
|
actionData: null,
|
|
3023
3064
|
actionHeaders: {}
|
|
3024
3065
|
});
|
|
3025
3066
|
} catch (e) {
|
|
3026
|
-
// If the user threw/returned a Response in callLoaderOrAction
|
|
3027
|
-
//
|
|
3028
|
-
//
|
|
3029
|
-
if (
|
|
3067
|
+
// If the user threw/returned a Response in callLoaderOrAction for a
|
|
3068
|
+
// `queryRoute` call, we throw the `HandlerResult` to bail out early
|
|
3069
|
+
// and then return or throw the raw Response here accordingly
|
|
3070
|
+
if (isHandlerResult(e) && isResponse(e.result)) {
|
|
3030
3071
|
if (e.type === ResultType.error) {
|
|
3031
|
-
throw e.
|
|
3072
|
+
throw e.result;
|
|
3032
3073
|
}
|
|
3033
|
-
return e.
|
|
3074
|
+
return e.result;
|
|
3034
3075
|
}
|
|
3035
3076
|
// Redirects are always returned since they don't propagate to catch
|
|
3036
3077
|
// boundaries
|
|
@@ -3040,7 +3081,7 @@ function createStaticHandler(routes, opts) {
|
|
|
3040
3081
|
throw e;
|
|
3041
3082
|
}
|
|
3042
3083
|
}
|
|
3043
|
-
async function submit(request, matches, actionMatch, requestContext, isRouteRequest) {
|
|
3084
|
+
async function submit(request, matches, actionMatch, requestContext, unstable_dataStrategy, skipLoaderErrorBubbling, isRouteRequest) {
|
|
3044
3085
|
let result;
|
|
3045
3086
|
if (!actionMatch.route.action && !actionMatch.route.lazy) {
|
|
3046
3087
|
let error = getInternalRouterError(405, {
|
|
@@ -3056,11 +3097,8 @@ function createStaticHandler(routes, opts) {
|
|
|
3056
3097
|
error
|
|
3057
3098
|
};
|
|
3058
3099
|
} else {
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
isRouteRequest,
|
|
3062
|
-
requestContext
|
|
3063
|
-
});
|
|
3100
|
+
let results = await callDataStrategy("action", request, [actionMatch], matches, isRouteRequest, requestContext, unstable_dataStrategy);
|
|
3101
|
+
result = results[0];
|
|
3064
3102
|
if (request.signal.aborted) {
|
|
3065
3103
|
throwStaticHandlerAbortedError(request, isRouteRequest, future);
|
|
3066
3104
|
}
|
|
@@ -3071,9 +3109,9 @@ function createStaticHandler(routes, opts) {
|
|
|
3071
3109
|
// can get back on the "throw all redirect responses" train here should
|
|
3072
3110
|
// this ever happen :/
|
|
3073
3111
|
throw new Response(null, {
|
|
3074
|
-
status: result.status,
|
|
3112
|
+
status: result.response.status,
|
|
3075
3113
|
headers: {
|
|
3076
|
-
Location: result.
|
|
3114
|
+
Location: result.response.headers.get("Location")
|
|
3077
3115
|
}
|
|
3078
3116
|
});
|
|
3079
3117
|
}
|
|
@@ -3110,41 +3148,40 @@ function createStaticHandler(routes, opts) {
|
|
|
3110
3148
|
activeDeferreds: null
|
|
3111
3149
|
};
|
|
3112
3150
|
}
|
|
3151
|
+
// Create a GET request for the loaders
|
|
3152
|
+
let loaderRequest = new Request(request.url, {
|
|
3153
|
+
headers: request.headers,
|
|
3154
|
+
redirect: request.redirect,
|
|
3155
|
+
signal: request.signal
|
|
3156
|
+
});
|
|
3113
3157
|
if (isErrorResult(result)) {
|
|
3114
3158
|
// Store off the pending error - we use it to determine which loaders
|
|
3115
3159
|
// to call and will commit it when we complete the navigation
|
|
3116
|
-
let boundaryMatch = findNearestBoundary(matches, actionMatch.route.id);
|
|
3117
|
-
let context = await loadRouteData(
|
|
3118
|
-
[boundaryMatch.route.id]: result.error
|
|
3119
|
-
});
|
|
3160
|
+
let boundaryMatch = skipLoaderErrorBubbling ? actionMatch : findNearestBoundary(matches, actionMatch.route.id);
|
|
3161
|
+
let context = await loadRouteData(loaderRequest, matches, requestContext, unstable_dataStrategy, skipLoaderErrorBubbling, null, [boundaryMatch.route.id, result]);
|
|
3120
3162
|
// action status codes take precedence over loader status codes
|
|
3121
3163
|
return _extends({}, context, {
|
|
3122
|
-
statusCode: isRouteErrorResponse(result.error) ? result.error.status : 500,
|
|
3164
|
+
statusCode: isRouteErrorResponse(result.error) ? result.error.status : result.statusCode != null ? result.statusCode : 500,
|
|
3123
3165
|
actionData: null,
|
|
3124
3166
|
actionHeaders: _extends({}, result.headers ? {
|
|
3125
3167
|
[actionMatch.route.id]: result.headers
|
|
3126
3168
|
} : {})
|
|
3127
3169
|
});
|
|
3128
3170
|
}
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
headers: request.headers,
|
|
3132
|
-
redirect: request.redirect,
|
|
3133
|
-
signal: request.signal
|
|
3134
|
-
});
|
|
3135
|
-
let context = await loadRouteData(loaderRequest, matches, requestContext);
|
|
3136
|
-
return _extends({}, context, result.statusCode ? {
|
|
3137
|
-
statusCode: result.statusCode
|
|
3138
|
-
} : {}, {
|
|
3171
|
+
let context = await loadRouteData(loaderRequest, matches, requestContext, unstable_dataStrategy, skipLoaderErrorBubbling, null);
|
|
3172
|
+
return _extends({}, context, {
|
|
3139
3173
|
actionData: {
|
|
3140
3174
|
[actionMatch.route.id]: result.data
|
|
3141
|
-
}
|
|
3142
|
-
|
|
3175
|
+
}
|
|
3176
|
+
}, result.statusCode ? {
|
|
3177
|
+
statusCode: result.statusCode
|
|
3178
|
+
} : {}, {
|
|
3179
|
+
actionHeaders: result.headers ? {
|
|
3143
3180
|
[actionMatch.route.id]: result.headers
|
|
3144
|
-
} : {}
|
|
3181
|
+
} : {}
|
|
3145
3182
|
});
|
|
3146
3183
|
}
|
|
3147
|
-
async function loadRouteData(request, matches, requestContext, routeMatch,
|
|
3184
|
+
async function loadRouteData(request, matches, requestContext, unstable_dataStrategy, skipLoaderErrorBubbling, routeMatch, pendingActionResult) {
|
|
3148
3185
|
let isRouteRequest = routeMatch != null;
|
|
3149
3186
|
// Short circuit if we have no loaders to run (queryRoute())
|
|
3150
3187
|
if (isRouteRequest && !(routeMatch != null && routeMatch.route.loader) && !(routeMatch != null && routeMatch.route.lazy)) {
|
|
@@ -3154,7 +3191,7 @@ function createStaticHandler(routes, opts) {
|
|
|
3154
3191
|
routeId: routeMatch == null ? void 0 : routeMatch.route.id
|
|
3155
3192
|
});
|
|
3156
3193
|
}
|
|
3157
|
-
let requestMatches = routeMatch ? [routeMatch] : getLoaderMatchesUntilBoundary(matches,
|
|
3194
|
+
let requestMatches = routeMatch ? [routeMatch] : pendingActionResult && isErrorResult(pendingActionResult[1]) ? getLoaderMatchesUntilBoundary(matches, pendingActionResult[0]) : matches;
|
|
3158
3195
|
let matchesToLoad = requestMatches.filter(m => m.route.loader || m.route.lazy);
|
|
3159
3196
|
// Short circuit if we have no loaders to run (query())
|
|
3160
3197
|
if (matchesToLoad.length === 0) {
|
|
@@ -3164,23 +3201,21 @@ function createStaticHandler(routes, opts) {
|
|
|
3164
3201
|
loaderData: matches.reduce((acc, m) => Object.assign(acc, {
|
|
3165
3202
|
[m.route.id]: null
|
|
3166
3203
|
}), {}),
|
|
3167
|
-
errors:
|
|
3204
|
+
errors: pendingActionResult && isErrorResult(pendingActionResult[1]) ? {
|
|
3205
|
+
[pendingActionResult[0]]: pendingActionResult[1].error
|
|
3206
|
+
} : null,
|
|
3168
3207
|
statusCode: 200,
|
|
3169
3208
|
loaderHeaders: {},
|
|
3170
3209
|
activeDeferreds: null
|
|
3171
3210
|
};
|
|
3172
3211
|
}
|
|
3173
|
-
let results = await
|
|
3174
|
-
isStaticRequest: true,
|
|
3175
|
-
isRouteRequest,
|
|
3176
|
-
requestContext
|
|
3177
|
-
}))]);
|
|
3212
|
+
let results = await callDataStrategy("loader", request, matchesToLoad, matches, isRouteRequest, requestContext, unstable_dataStrategy);
|
|
3178
3213
|
if (request.signal.aborted) {
|
|
3179
3214
|
throwStaticHandlerAbortedError(request, isRouteRequest, future);
|
|
3180
3215
|
}
|
|
3181
3216
|
// Process and commit output from loaders
|
|
3182
3217
|
let activeDeferreds = new Map();
|
|
3183
|
-
let context = processRouteLoaderData(matches, matchesToLoad, results,
|
|
3218
|
+
let context = processRouteLoaderData(matches, matchesToLoad, results, pendingActionResult, activeDeferreds, skipLoaderErrorBubbling);
|
|
3184
3219
|
// Add a null for any non-loader matches for proper revalidation on the client
|
|
3185
3220
|
let executedLoaders = new Set(matchesToLoad.map(match => match.route.id));
|
|
3186
3221
|
matches.forEach(match => {
|
|
@@ -3193,6 +3228,24 @@ function createStaticHandler(routes, opts) {
|
|
|
3193
3228
|
activeDeferreds: activeDeferreds.size > 0 ? Object.fromEntries(activeDeferreds.entries()) : null
|
|
3194
3229
|
});
|
|
3195
3230
|
}
|
|
3231
|
+
// Utility wrapper for calling dataStrategy server-side without having to
|
|
3232
|
+
// pass around the manifest, mapRouteProperties, etc.
|
|
3233
|
+
async function callDataStrategy(type, request, matchesToLoad, matches, isRouteRequest, requestContext, unstable_dataStrategy) {
|
|
3234
|
+
let results = await callDataStrategyImpl(unstable_dataStrategy || defaultDataStrategy, type, request, matchesToLoad, matches, manifest, mapRouteProperties, requestContext);
|
|
3235
|
+
return await Promise.all(results.map((result, i) => {
|
|
3236
|
+
if (isRedirectHandlerResult(result)) {
|
|
3237
|
+
let response = result.result;
|
|
3238
|
+
// Throw redirects and let the server handle them with an HTTP redirect
|
|
3239
|
+
throw normalizeRelativeRoutingRedirectResponse(response, request, matchesToLoad[i].route.id, matches, basename, future.v7_relativeSplatPath);
|
|
3240
|
+
}
|
|
3241
|
+
if (isResponse(result.result) && isRouteRequest) {
|
|
3242
|
+
// For SSR single-route requests, we want to hand Responses back
|
|
3243
|
+
// directly without unwrapping
|
|
3244
|
+
throw result;
|
|
3245
|
+
}
|
|
3246
|
+
return convertHandlerResultToDataResult(result);
|
|
3247
|
+
}));
|
|
3248
|
+
}
|
|
3196
3249
|
return {
|
|
3197
3250
|
dataRoutes,
|
|
3198
3251
|
query,
|
|
@@ -3402,13 +3455,18 @@ function getLoaderMatchesUntilBoundary(matches, boundaryId) {
|
|
|
3402
3455
|
}
|
|
3403
3456
|
return boundaryMatches;
|
|
3404
3457
|
}
|
|
3405
|
-
function getMatchesToLoad(history, state, matches, submission, location, isInitialLoad, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, deletedFetchers, fetchLoadMatches, fetchRedirectIds, routesToUse, basename,
|
|
3406
|
-
let actionResult =
|
|
3458
|
+
function getMatchesToLoad(history, state, matches, submission, location, isInitialLoad, skipActionErrorRevalidation, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, deletedFetchers, fetchLoadMatches, fetchRedirectIds, routesToUse, basename, pendingActionResult) {
|
|
3459
|
+
let actionResult = pendingActionResult ? isErrorResult(pendingActionResult[1]) ? pendingActionResult[1].error : pendingActionResult[1].data : undefined;
|
|
3407
3460
|
let currentUrl = history.createURL(state.location);
|
|
3408
3461
|
let nextUrl = history.createURL(location);
|
|
3409
3462
|
// Pick navigation matches that are net-new or qualify for revalidation
|
|
3410
|
-
let boundaryId =
|
|
3411
|
-
let boundaryMatches = getLoaderMatchesUntilBoundary(matches, boundaryId);
|
|
3463
|
+
let boundaryId = pendingActionResult && isErrorResult(pendingActionResult[1]) ? pendingActionResult[0] : undefined;
|
|
3464
|
+
let boundaryMatches = boundaryId ? getLoaderMatchesUntilBoundary(matches, boundaryId) : matches;
|
|
3465
|
+
// Don't revalidate loaders by default after action 4xx/5xx responses
|
|
3466
|
+
// when the flag is enabled. They can still opt-into revalidation via
|
|
3467
|
+
// `shouldRevalidate` via `actionResult`
|
|
3468
|
+
let actionStatus = pendingActionResult ? pendingActionResult[1].statusCode : undefined;
|
|
3469
|
+
let shouldSkipRevalidation = skipActionErrorRevalidation && actionStatus && actionStatus >= 400;
|
|
3412
3470
|
let navigationMatches = boundaryMatches.filter((match, index) => {
|
|
3413
3471
|
let {
|
|
3414
3472
|
route
|
|
@@ -3421,7 +3479,7 @@ function getMatchesToLoad(history, state, matches, submission, location, isIniti
|
|
|
3421
3479
|
return false;
|
|
3422
3480
|
}
|
|
3423
3481
|
if (isInitialLoad) {
|
|
3424
|
-
if (route.loader.hydrate) {
|
|
3482
|
+
if (typeof route.loader !== "function" || route.loader.hydrate) {
|
|
3425
3483
|
return true;
|
|
3426
3484
|
}
|
|
3427
3485
|
return state.loaderData[route.id] === undefined && (
|
|
@@ -3445,11 +3503,10 @@ function getMatchesToLoad(history, state, matches, submission, location, isIniti
|
|
|
3445
3503
|
nextParams: nextRouteMatch.params
|
|
3446
3504
|
}, submission, {
|
|
3447
3505
|
actionResult,
|
|
3448
|
-
|
|
3506
|
+
unstable_actionStatus: actionStatus,
|
|
3507
|
+
defaultShouldRevalidate: shouldSkipRevalidation ? false :
|
|
3449
3508
|
// Forced revalidation due to submission, useRevalidator, or X-Remix-Revalidate
|
|
3450
|
-
isRevalidationRequired ||
|
|
3451
|
-
// Clicked the same link, resubmitted a GET form
|
|
3452
|
-
currentUrl.pathname + currentUrl.search === nextUrl.pathname + nextUrl.search ||
|
|
3509
|
+
isRevalidationRequired || currentUrl.pathname + currentUrl.search === nextUrl.pathname + nextUrl.search ||
|
|
3453
3510
|
// Search params affect all loaders
|
|
3454
3511
|
currentUrl.search !== nextUrl.search || isNewRouteInstance(currentRouteMatch, nextRouteMatch)
|
|
3455
3512
|
}));
|
|
@@ -3508,7 +3565,8 @@ function getMatchesToLoad(history, state, matches, submission, location, isIniti
|
|
|
3508
3565
|
nextParams: matches[matches.length - 1].params
|
|
3509
3566
|
}, submission, {
|
|
3510
3567
|
actionResult,
|
|
3511
|
-
|
|
3568
|
+
unstable_actionStatus: actionStatus,
|
|
3569
|
+
defaultShouldRevalidate: shouldSkipRevalidation ? false : isRevalidationRequired
|
|
3512
3570
|
}));
|
|
3513
3571
|
}
|
|
3514
3572
|
if (shouldRevalidate) {
|
|
@@ -3603,24 +3661,87 @@ async function loadLazyRouteModule(route, mapRouteProperties, manifest) {
|
|
|
3603
3661
|
lazy: undefined
|
|
3604
3662
|
}));
|
|
3605
3663
|
}
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3664
|
+
// Default implementation of `dataStrategy` which fetches all loaders in parallel
|
|
3665
|
+
function defaultDataStrategy(opts) {
|
|
3666
|
+
return Promise.all(opts.matches.map(m => m.resolve()));
|
|
3667
|
+
}
|
|
3668
|
+
async function callDataStrategyImpl(dataStrategyImpl, type, request, matchesToLoad, matches, manifest, mapRouteProperties, requestContext) {
|
|
3669
|
+
let routeIdsToLoad = matchesToLoad.reduce((acc, m) => acc.add(m.route.id), new Set());
|
|
3670
|
+
let loadedMatches = new Set();
|
|
3671
|
+
// Send all matches here to allow for a middleware-type implementation.
|
|
3672
|
+
// handler will be a no-op for unneeded routes and we filter those results
|
|
3673
|
+
// back out below.
|
|
3674
|
+
let results = await dataStrategyImpl({
|
|
3675
|
+
matches: matches.map(match => {
|
|
3676
|
+
let shouldLoad = routeIdsToLoad.has(match.route.id);
|
|
3677
|
+
// `resolve` encapsulates the route.lazy, executing the
|
|
3678
|
+
// loader/action, and mapping return values/thrown errors to a
|
|
3679
|
+
// HandlerResult. Users can pass a callback to take fine-grained control
|
|
3680
|
+
// over the execution of the loader/action
|
|
3681
|
+
let resolve = handlerOverride => {
|
|
3682
|
+
loadedMatches.add(match.route.id);
|
|
3683
|
+
return shouldLoad ? callLoaderOrAction(type, request, match, manifest, mapRouteProperties, handlerOverride, requestContext) : Promise.resolve({
|
|
3684
|
+
type: ResultType.data,
|
|
3685
|
+
result: undefined
|
|
3686
|
+
});
|
|
3687
|
+
};
|
|
3688
|
+
return _extends({}, match, {
|
|
3689
|
+
shouldLoad,
|
|
3690
|
+
resolve
|
|
3691
|
+
});
|
|
3692
|
+
}),
|
|
3693
|
+
request,
|
|
3694
|
+
params: matches[0].params,
|
|
3695
|
+
context: requestContext
|
|
3696
|
+
});
|
|
3697
|
+
// Throw if any loadRoute implementations not called since they are what
|
|
3698
|
+
// ensures a route is fully loaded
|
|
3699
|
+
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."));
|
|
3700
|
+
// Filter out any middleware-only matches for which we didn't need to run handlers
|
|
3701
|
+
return results.filter((_, i) => routeIdsToLoad.has(matches[i].route.id));
|
|
3702
|
+
}
|
|
3703
|
+
// Default logic for calling a loader/action is the user has no specified a dataStrategy
|
|
3704
|
+
async function callLoaderOrAction(type, request, match, manifest, mapRouteProperties, handlerOverride, staticContext) {
|
|
3611
3705
|
let result;
|
|
3612
3706
|
let onReject;
|
|
3613
3707
|
let runHandler = handler => {
|
|
3614
3708
|
// Setup a promise we can race against so that abort signals short circuit
|
|
3615
3709
|
let reject;
|
|
3710
|
+
// This will never resolve so safe to type it as Promise<HandlerResult> to
|
|
3711
|
+
// satisfy the function return value
|
|
3616
3712
|
let abortPromise = new Promise((_, r) => reject = r);
|
|
3617
3713
|
onReject = () => reject();
|
|
3618
3714
|
request.signal.addEventListener("abort", onReject);
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3715
|
+
let actualHandler = ctx => {
|
|
3716
|
+
if (typeof handler !== "function") {
|
|
3717
|
+
return Promise.reject(new Error("You cannot call the handler for a route which defines a boolean " + ("\"" + type + "\" [routeId: " + match.route.id + "]")));
|
|
3718
|
+
}
|
|
3719
|
+
return handler({
|
|
3720
|
+
request,
|
|
3721
|
+
params: match.params,
|
|
3722
|
+
context: staticContext
|
|
3723
|
+
}, ...(ctx !== undefined ? [ctx] : []));
|
|
3724
|
+
};
|
|
3725
|
+
let handlerPromise;
|
|
3726
|
+
if (handlerOverride) {
|
|
3727
|
+
handlerPromise = handlerOverride(ctx => actualHandler(ctx));
|
|
3728
|
+
} else {
|
|
3729
|
+
handlerPromise = (async () => {
|
|
3730
|
+
try {
|
|
3731
|
+
let val = await actualHandler();
|
|
3732
|
+
return {
|
|
3733
|
+
type: "data",
|
|
3734
|
+
result: val
|
|
3735
|
+
};
|
|
3736
|
+
} catch (e) {
|
|
3737
|
+
return {
|
|
3738
|
+
type: "error",
|
|
3739
|
+
result: e
|
|
3740
|
+
};
|
|
3741
|
+
}
|
|
3742
|
+
})();
|
|
3743
|
+
}
|
|
3744
|
+
return Promise.race([handlerPromise, abortPromise]);
|
|
3624
3745
|
};
|
|
3625
3746
|
try {
|
|
3626
3747
|
let handler = match.route[type];
|
|
@@ -3628,23 +3749,23 @@ async function callLoaderOrAction(type, request, match, matches, manifest, mapRo
|
|
|
3628
3749
|
if (handler) {
|
|
3629
3750
|
// Run statically defined handler in parallel with lazy()
|
|
3630
3751
|
let handlerError;
|
|
3631
|
-
let
|
|
3752
|
+
let [value] = await Promise.all([
|
|
3632
3753
|
// If the handler throws, don't let it immediately bubble out,
|
|
3633
3754
|
// since we need to let the lazy() execution finish so we know if this
|
|
3634
3755
|
// route has a boundary that can handle the error
|
|
3635
3756
|
runHandler(handler).catch(e => {
|
|
3636
3757
|
handlerError = e;
|
|
3637
3758
|
}), loadLazyRouteModule(match.route, mapRouteProperties, manifest)]);
|
|
3638
|
-
if (handlerError) {
|
|
3759
|
+
if (handlerError !== undefined) {
|
|
3639
3760
|
throw handlerError;
|
|
3640
3761
|
}
|
|
3641
|
-
result =
|
|
3762
|
+
result = value;
|
|
3642
3763
|
} else {
|
|
3643
3764
|
// Load lazy route module, then run any returned handler
|
|
3644
3765
|
await loadLazyRouteModule(match.route, mapRouteProperties, manifest);
|
|
3645
3766
|
handler = match.route[type];
|
|
3646
3767
|
if (handler) {
|
|
3647
|
-
// Handler still
|
|
3768
|
+
// Handler still runs even if we got interrupted to maintain consistency
|
|
3648
3769
|
// with un-abortable behavior of handler execution on non-lazy or
|
|
3649
3770
|
// previously-lazy-loaded routes
|
|
3650
3771
|
result = await runHandler(handler);
|
|
@@ -3661,7 +3782,7 @@ async function callLoaderOrAction(type, request, match, matches, manifest, mapRo
|
|
|
3661
3782
|
// hit the invariant below that errors on returning undefined.
|
|
3662
3783
|
return {
|
|
3663
3784
|
type: ResultType.data,
|
|
3664
|
-
|
|
3785
|
+
result: undefined
|
|
3665
3786
|
};
|
|
3666
3787
|
}
|
|
3667
3788
|
}
|
|
@@ -3674,61 +3795,29 @@ async function callLoaderOrAction(type, request, match, matches, manifest, mapRo
|
|
|
3674
3795
|
} else {
|
|
3675
3796
|
result = await runHandler(handler);
|
|
3676
3797
|
}
|
|
3677
|
-
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`.");
|
|
3798
|
+
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`.");
|
|
3678
3799
|
} catch (e) {
|
|
3679
|
-
|
|
3680
|
-
|
|
3800
|
+
// We should already be catching and converting normal handler executions to
|
|
3801
|
+
// HandlerResults and returning them, so anything that throws here is an
|
|
3802
|
+
// unexpected error we still need to wrap
|
|
3803
|
+
return {
|
|
3804
|
+
type: ResultType.error,
|
|
3805
|
+
result: e
|
|
3806
|
+
};
|
|
3681
3807
|
} finally {
|
|
3682
3808
|
if (onReject) {
|
|
3683
3809
|
request.signal.removeEventListener("abort", onReject);
|
|
3684
3810
|
}
|
|
3685
3811
|
}
|
|
3812
|
+
return result;
|
|
3813
|
+
}
|
|
3814
|
+
async function convertHandlerResultToDataResult(handlerResult) {
|
|
3815
|
+
let {
|
|
3816
|
+
result,
|
|
3817
|
+
type,
|
|
3818
|
+
status
|
|
3819
|
+
} = handlerResult;
|
|
3686
3820
|
if (isResponse(result)) {
|
|
3687
|
-
let status = result.status;
|
|
3688
|
-
// Process redirects
|
|
3689
|
-
if (redirectStatusCodes.has(status)) {
|
|
3690
|
-
let location = result.headers.get("Location");
|
|
3691
|
-
invariant(location, "Redirects returned/thrown from loaders/actions must have a Location header");
|
|
3692
|
-
// Support relative routing in internal redirects
|
|
3693
|
-
if (!ABSOLUTE_URL_REGEX.test(location)) {
|
|
3694
|
-
location = normalizeTo(new URL(request.url), matches.slice(0, matches.indexOf(match) + 1), basename, true, location, v7_relativeSplatPath);
|
|
3695
|
-
} else if (!opts.isStaticRequest) {
|
|
3696
|
-
// Strip off the protocol+origin for same-origin + same-basename absolute
|
|
3697
|
-
// redirects. If this is a static request, we can let it go back to the
|
|
3698
|
-
// browser as-is
|
|
3699
|
-
let currentUrl = new URL(request.url);
|
|
3700
|
-
let url = location.startsWith("//") ? new URL(currentUrl.protocol + location) : new URL(location);
|
|
3701
|
-
let isSameBasename = stripBasename(url.pathname, basename) != null;
|
|
3702
|
-
if (url.origin === currentUrl.origin && isSameBasename) {
|
|
3703
|
-
location = url.pathname + url.search + url.hash;
|
|
3704
|
-
}
|
|
3705
|
-
}
|
|
3706
|
-
// Don't process redirects in the router during static requests requests.
|
|
3707
|
-
// Instead, throw the Response and let the server handle it with an HTTP
|
|
3708
|
-
// redirect. We also update the Location header in place in this flow so
|
|
3709
|
-
// basename and relative routing is taken into account
|
|
3710
|
-
if (opts.isStaticRequest) {
|
|
3711
|
-
result.headers.set("Location", location);
|
|
3712
|
-
throw result;
|
|
3713
|
-
}
|
|
3714
|
-
return {
|
|
3715
|
-
type: ResultType.redirect,
|
|
3716
|
-
status,
|
|
3717
|
-
location,
|
|
3718
|
-
revalidate: result.headers.get("X-Remix-Revalidate") !== null,
|
|
3719
|
-
reloadDocument: result.headers.get("X-Remix-Reload-Document") !== null
|
|
3720
|
-
};
|
|
3721
|
-
}
|
|
3722
|
-
// For SSR single-route requests, we want to hand Responses back directly
|
|
3723
|
-
// without unwrapping. We do this with the QueryRouteResponse wrapper
|
|
3724
|
-
// interface so we can know whether it was returned or thrown
|
|
3725
|
-
if (opts.isRouteRequest) {
|
|
3726
|
-
let queryRouteResponse = {
|
|
3727
|
-
type: resultType === ResultType.error ? ResultType.error : ResultType.data,
|
|
3728
|
-
response: result
|
|
3729
|
-
};
|
|
3730
|
-
throw queryRouteResponse;
|
|
3731
|
-
}
|
|
3732
3821
|
let data;
|
|
3733
3822
|
try {
|
|
3734
3823
|
let contentType = result.headers.get("Content-Type");
|
|
@@ -3749,10 +3838,11 @@ async function callLoaderOrAction(type, request, match, matches, manifest, mapRo
|
|
|
3749
3838
|
error: e
|
|
3750
3839
|
};
|
|
3751
3840
|
}
|
|
3752
|
-
if (
|
|
3841
|
+
if (type === ResultType.error) {
|
|
3753
3842
|
return {
|
|
3754
|
-
type:
|
|
3755
|
-
error: new ErrorResponseImpl(status, result.statusText, data),
|
|
3843
|
+
type: ResultType.error,
|
|
3844
|
+
error: new ErrorResponseImpl(result.status, result.statusText, data),
|
|
3845
|
+
statusCode: result.status,
|
|
3756
3846
|
headers: result.headers
|
|
3757
3847
|
};
|
|
3758
3848
|
}
|
|
@@ -3763,10 +3853,11 @@ async function callLoaderOrAction(type, request, match, matches, manifest, mapRo
|
|
|
3763
3853
|
headers: result.headers
|
|
3764
3854
|
};
|
|
3765
3855
|
}
|
|
3766
|
-
if (
|
|
3856
|
+
if (type === ResultType.error) {
|
|
3767
3857
|
return {
|
|
3768
|
-
type:
|
|
3769
|
-
error: result
|
|
3858
|
+
type: ResultType.error,
|
|
3859
|
+
error: result,
|
|
3860
|
+
statusCode: isRouteErrorResponse(result) ? result.status : status
|
|
3770
3861
|
};
|
|
3771
3862
|
}
|
|
3772
3863
|
if (isDeferredData(result)) {
|
|
@@ -3780,9 +3871,33 @@ async function callLoaderOrAction(type, request, match, matches, manifest, mapRo
|
|
|
3780
3871
|
}
|
|
3781
3872
|
return {
|
|
3782
3873
|
type: ResultType.data,
|
|
3783
|
-
data: result
|
|
3874
|
+
data: result,
|
|
3875
|
+
statusCode: status
|
|
3784
3876
|
};
|
|
3785
3877
|
}
|
|
3878
|
+
// Support relative routing in internal redirects
|
|
3879
|
+
function normalizeRelativeRoutingRedirectResponse(response, request, routeId, matches, basename, v7_relativeSplatPath) {
|
|
3880
|
+
let location = response.headers.get("Location");
|
|
3881
|
+
invariant(location, "Redirects returned/thrown from loaders/actions must have a Location header");
|
|
3882
|
+
if (!ABSOLUTE_URL_REGEX.test(location)) {
|
|
3883
|
+
let trimmedMatches = matches.slice(0, matches.findIndex(m => m.route.id === routeId) + 1);
|
|
3884
|
+
location = normalizeTo(new URL(request.url), trimmedMatches, basename, true, location, v7_relativeSplatPath);
|
|
3885
|
+
response.headers.set("Location", location);
|
|
3886
|
+
}
|
|
3887
|
+
return response;
|
|
3888
|
+
}
|
|
3889
|
+
function normalizeRedirectLocation(location, currentUrl, basename) {
|
|
3890
|
+
if (ABSOLUTE_URL_REGEX.test(location)) {
|
|
3891
|
+
// Strip off the protocol+origin for same-origin + same-basename absolute redirects
|
|
3892
|
+
let normalizedLocation = location;
|
|
3893
|
+
let url = normalizedLocation.startsWith("//") ? new URL(currentUrl.protocol + normalizedLocation) : new URL(normalizedLocation);
|
|
3894
|
+
let isSameBasename = stripBasename(url.pathname, basename) != null;
|
|
3895
|
+
if (url.origin === currentUrl.origin && isSameBasename) {
|
|
3896
|
+
return url.pathname + url.search + url.hash;
|
|
3897
|
+
}
|
|
3898
|
+
}
|
|
3899
|
+
return location;
|
|
3900
|
+
}
|
|
3786
3901
|
// Utility method for creating the Request instances for loaders/actions during
|
|
3787
3902
|
// client-side navigations and fetches. During SSR we will always have a
|
|
3788
3903
|
// Request instance from the static handler (query/queryRoute)
|
|
@@ -3833,33 +3948,38 @@ function convertSearchParamsToFormData(searchParams) {
|
|
|
3833
3948
|
}
|
|
3834
3949
|
return formData;
|
|
3835
3950
|
}
|
|
3836
|
-
function processRouteLoaderData(matches, matchesToLoad, results,
|
|
3951
|
+
function processRouteLoaderData(matches, matchesToLoad, results, pendingActionResult, activeDeferreds, skipLoaderErrorBubbling) {
|
|
3837
3952
|
// Fill in loaderData/errors from our loaders
|
|
3838
3953
|
let loaderData = {};
|
|
3839
3954
|
let errors = null;
|
|
3840
3955
|
let statusCode;
|
|
3841
3956
|
let foundError = false;
|
|
3842
3957
|
let loaderHeaders = {};
|
|
3958
|
+
let pendingError = pendingActionResult && isErrorResult(pendingActionResult[1]) ? pendingActionResult[1].error : undefined;
|
|
3843
3959
|
// Process loader results into state.loaderData/state.errors
|
|
3844
3960
|
results.forEach((result, index) => {
|
|
3845
3961
|
let id = matchesToLoad[index].route.id;
|
|
3846
3962
|
invariant(!isRedirectResult(result), "Cannot handle redirect results in processLoaderData");
|
|
3847
3963
|
if (isErrorResult(result)) {
|
|
3848
|
-
// Look upwards from the matched route for the closest ancestor
|
|
3849
|
-
// error boundary, defaulting to the root match
|
|
3850
|
-
let boundaryMatch = findNearestBoundary(matches, id);
|
|
3851
3964
|
let error = result.error;
|
|
3852
3965
|
// If we have a pending action error, we report it at the highest-route
|
|
3853
3966
|
// that throws a loader error, and then clear it out to indicate that
|
|
3854
3967
|
// it was consumed
|
|
3855
|
-
if (pendingError) {
|
|
3856
|
-
error =
|
|
3968
|
+
if (pendingError !== undefined) {
|
|
3969
|
+
error = pendingError;
|
|
3857
3970
|
pendingError = undefined;
|
|
3858
3971
|
}
|
|
3859
3972
|
errors = errors || {};
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3973
|
+
if (skipLoaderErrorBubbling) {
|
|
3974
|
+
errors[id] = error;
|
|
3975
|
+
} else {
|
|
3976
|
+
// Look upwards from the matched route for the closest ancestor error
|
|
3977
|
+
// boundary, defaulting to the root match. Prefer higher error values
|
|
3978
|
+
// if lower errors bubble to the same boundary
|
|
3979
|
+
let boundaryMatch = findNearestBoundary(matches, id);
|
|
3980
|
+
if (errors[boundaryMatch.route.id] == null) {
|
|
3981
|
+
errors[boundaryMatch.route.id] = error;
|
|
3982
|
+
}
|
|
3863
3983
|
}
|
|
3864
3984
|
// Clear our any prior loaderData for the throwing route
|
|
3865
3985
|
loaderData[id] = undefined;
|
|
@@ -3876,25 +3996,35 @@ function processRouteLoaderData(matches, matchesToLoad, results, pendingError, a
|
|
|
3876
3996
|
if (isDeferredResult(result)) {
|
|
3877
3997
|
activeDeferreds.set(id, result.deferredData);
|
|
3878
3998
|
loaderData[id] = result.deferredData.data;
|
|
3999
|
+
// Error status codes always override success status codes, but if all
|
|
4000
|
+
// loaders are successful we take the deepest status code.
|
|
4001
|
+
if (result.statusCode != null && result.statusCode !== 200 && !foundError) {
|
|
4002
|
+
statusCode = result.statusCode;
|
|
4003
|
+
}
|
|
4004
|
+
if (result.headers) {
|
|
4005
|
+
loaderHeaders[id] = result.headers;
|
|
4006
|
+
}
|
|
3879
4007
|
} else {
|
|
3880
4008
|
loaderData[id] = result.data;
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
4009
|
+
// Error status codes always override success status codes, but if all
|
|
4010
|
+
// loaders are successful we take the deepest status code.
|
|
4011
|
+
if (result.statusCode && result.statusCode !== 200 && !foundError) {
|
|
4012
|
+
statusCode = result.statusCode;
|
|
4013
|
+
}
|
|
4014
|
+
if (result.headers) {
|
|
4015
|
+
loaderHeaders[id] = result.headers;
|
|
4016
|
+
}
|
|
3889
4017
|
}
|
|
3890
4018
|
}
|
|
3891
4019
|
});
|
|
3892
4020
|
// If we didn't consume the pending action error (i.e., all loaders
|
|
3893
4021
|
// resolved), then consume it here. Also clear out any loaderData for the
|
|
3894
4022
|
// throwing route
|
|
3895
|
-
if (pendingError) {
|
|
3896
|
-
errors =
|
|
3897
|
-
|
|
4023
|
+
if (pendingError !== undefined && pendingActionResult) {
|
|
4024
|
+
errors = {
|
|
4025
|
+
[pendingActionResult[0]]: pendingError
|
|
4026
|
+
};
|
|
4027
|
+
loaderData[pendingActionResult[0]] = undefined;
|
|
3898
4028
|
}
|
|
3899
4029
|
return {
|
|
3900
4030
|
loaderData,
|
|
@@ -3903,11 +4033,12 @@ function processRouteLoaderData(matches, matchesToLoad, results, pendingError, a
|
|
|
3903
4033
|
loaderHeaders
|
|
3904
4034
|
};
|
|
3905
4035
|
}
|
|
3906
|
-
function processLoaderData(state, matches, matchesToLoad, results,
|
|
4036
|
+
function processLoaderData(state, matches, matchesToLoad, results, pendingActionResult, revalidatingFetchers, fetcherResults, activeDeferreds) {
|
|
3907
4037
|
let {
|
|
3908
4038
|
loaderData,
|
|
3909
4039
|
errors
|
|
3910
|
-
} = processRouteLoaderData(matches, matchesToLoad, results,
|
|
4040
|
+
} = processRouteLoaderData(matches, matchesToLoad, results, pendingActionResult, activeDeferreds, false // This method is only called client side so we always want to bubble
|
|
4041
|
+
);
|
|
3911
4042
|
// Process results from our revalidating fetchers
|
|
3912
4043
|
for (let index = 0; index < revalidatingFetchers.length; index++) {
|
|
3913
4044
|
let {
|
|
@@ -3967,6 +4098,19 @@ function mergeLoaderData(loaderData, newLoaderData, matches, errors) {
|
|
|
3967
4098
|
}
|
|
3968
4099
|
return mergedLoaderData;
|
|
3969
4100
|
}
|
|
4101
|
+
function getActionDataForCommit(pendingActionResult) {
|
|
4102
|
+
if (!pendingActionResult) {
|
|
4103
|
+
return {};
|
|
4104
|
+
}
|
|
4105
|
+
return isErrorResult(pendingActionResult[1]) ? {
|
|
4106
|
+
// Clear out prior actionData on errors
|
|
4107
|
+
actionData: {}
|
|
4108
|
+
} : {
|
|
4109
|
+
actionData: {
|
|
4110
|
+
[pendingActionResult[0]]: pendingActionResult[1].data
|
|
4111
|
+
}
|
|
4112
|
+
};
|
|
4113
|
+
}
|
|
3970
4114
|
// Find the nearest error boundary, looking upwards from the leaf route (or the
|
|
3971
4115
|
// route specified by routeId) for the closest ancestor error boundary,
|
|
3972
4116
|
// defaulting to the root match
|
|
@@ -4059,6 +4203,12 @@ function isHashChangeOnly(a, b) {
|
|
|
4059
4203
|
// /page#hash -> /page
|
|
4060
4204
|
return false;
|
|
4061
4205
|
}
|
|
4206
|
+
function isHandlerResult(result) {
|
|
4207
|
+
return result != null && typeof result === "object" && "type" in result && "result" in result && (result.type === ResultType.data || result.type === ResultType.error);
|
|
4208
|
+
}
|
|
4209
|
+
function isRedirectHandlerResult(result) {
|
|
4210
|
+
return isResponse(result.result) && redirectStatusCodes.has(result.result.status);
|
|
4211
|
+
}
|
|
4062
4212
|
function isDeferredResult(result) {
|
|
4063
4213
|
return result.type === ResultType.deferred;
|
|
4064
4214
|
}
|
|
@@ -4083,9 +4233,6 @@ function isRedirectResponse(result) {
|
|
|
4083
4233
|
let location = result.headers.get("Location");
|
|
4084
4234
|
return status >= 300 && status <= 399 && location != null;
|
|
4085
4235
|
}
|
|
4086
|
-
function isQueryRouteResponse(obj) {
|
|
4087
|
-
return obj && isResponse(obj.response) && (obj.type === ResultType.data || obj.type === ResultType.error);
|
|
4088
|
-
}
|
|
4089
4236
|
function isValidMethod(method) {
|
|
4090
4237
|
return validRequestMethods.has(method.toLowerCase());
|
|
4091
4238
|
}
|