@remix-run/router 1.15.3 → 1.16.0-pre.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/dist/index.d.ts +1 -1
- package/dist/router.cjs.js +441 -250
- package/dist/router.cjs.js.map +1 -1
- package/dist/router.d.ts +7 -1
- package/dist/router.js +427 -238
- package/dist/router.js.map +1 -1
- package/dist/router.umd.js +441 -250
- package/dist/router.umd.js.map +1 -1
- package/dist/router.umd.min.js +2 -2
- package/dist/router.umd.min.js.map +1 -1
- package/dist/utils.d.ts +28 -10
- package/index.ts +4 -0
- package/package.json +2 -2
- package/router.ts +747 -359
- package/utils.ts +46 -14
package/dist/router.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @remix-run/router v1.
|
|
2
|
+
* @remix-run/router v1.16.0-pre.0
|
|
3
3
|
*
|
|
4
4
|
* Copyright (c) Remix Software Inc.
|
|
5
5
|
*
|
|
@@ -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,25 @@ 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.loadRouteIds` is an optional array of routeIds to run only a subset of
|
|
2897
|
+
* loaders during a query() call
|
|
2898
|
+
* - `opts.requestContext` is an optional server context that will be passed
|
|
2899
|
+
* to actions/loaders in the `context` parameter
|
|
2900
|
+
* - `opts.skipLoaderErrorBubbling` is an optional parameter that will prevent
|
|
2901
|
+
* the bubbling of errors which allows single-fetch-type implementations
|
|
2902
|
+
* where the client will handle the bubbling and we may need to return data
|
|
2903
|
+
* for the handling route
|
|
2904
|
+
* - `opts.skipLoaders` is an optional parameter that will prevent loaders
|
|
2905
|
+
* from running after an action
|
|
2869
2906
|
*/
|
|
2870
2907
|
async function query(request, _temp3) {
|
|
2871
2908
|
let {
|
|
2872
|
-
|
|
2909
|
+
loadRouteIds,
|
|
2910
|
+
requestContext,
|
|
2911
|
+
skipLoaderErrorBubbling,
|
|
2912
|
+
skipLoaders,
|
|
2913
|
+
unstable_dataStrategy
|
|
2873
2914
|
} = _temp3 === void 0 ? {} : _temp3;
|
|
2874
2915
|
let url = new URL(request.url);
|
|
2875
2916
|
let method = request.method;
|
|
@@ -2921,7 +2962,7 @@ function createStaticHandler(routes, opts) {
|
|
|
2921
2962
|
activeDeferreds: null
|
|
2922
2963
|
};
|
|
2923
2964
|
}
|
|
2924
|
-
let result = await queryImpl(request, location, matches, requestContext);
|
|
2965
|
+
let result = await queryImpl(request, location, matches, requestContext, unstable_dataStrategy || null, loadRouteIds || null, skipLoaderErrorBubbling === true, skipLoaders === true, null);
|
|
2925
2966
|
if (isResponse(result)) {
|
|
2926
2967
|
return result;
|
|
2927
2968
|
}
|
|
@@ -2952,6 +2993,12 @@ function createStaticHandler(routes, opts) {
|
|
|
2952
2993
|
* serialize the error as they see fit while including the proper response
|
|
2953
2994
|
* code. Examples here are 404 and 405 errors that occur prior to reaching
|
|
2954
2995
|
* any user-defined loaders.
|
|
2996
|
+
*
|
|
2997
|
+
* - `opts.routeId` allows you to specify the specific route handler to call.
|
|
2998
|
+
* If not provided the handler will determine the proper route by matching
|
|
2999
|
+
* against `request.url`
|
|
3000
|
+
* - `opts.requestContext` is an optional server context that will be passed
|
|
3001
|
+
* to actions/loaders in the `context` parameter
|
|
2955
3002
|
*/
|
|
2956
3003
|
async function queryRoute(request, _temp4) {
|
|
2957
3004
|
let {
|
|
@@ -2984,7 +3031,7 @@ function createStaticHandler(routes, opts) {
|
|
|
2984
3031
|
pathname: location.pathname
|
|
2985
3032
|
});
|
|
2986
3033
|
}
|
|
2987
|
-
let result = await queryImpl(request, location, matches, requestContext, match);
|
|
3034
|
+
let result = await queryImpl(request, location, matches, requestContext, null, null, false, false, match);
|
|
2988
3035
|
if (isResponse(result)) {
|
|
2989
3036
|
return result;
|
|
2990
3037
|
}
|
|
@@ -3010,27 +3057,27 @@ function createStaticHandler(routes, opts) {
|
|
|
3010
3057
|
}
|
|
3011
3058
|
return undefined;
|
|
3012
3059
|
}
|
|
3013
|
-
async function queryImpl(request, location, matches, requestContext, routeMatch) {
|
|
3060
|
+
async function queryImpl(request, location, matches, requestContext, unstable_dataStrategy, loadRouteIds, skipLoaderErrorBubbling, skipLoaders, routeMatch) {
|
|
3014
3061
|
invariant(request.signal, "query()/queryRoute() requests must contain an AbortController signal");
|
|
3015
3062
|
try {
|
|
3016
3063
|
if (isMutationMethod(request.method.toLowerCase())) {
|
|
3017
|
-
let result = await submit(request, matches, routeMatch || getTargetMatch(matches, location), requestContext, routeMatch != null);
|
|
3064
|
+
let result = await submit(request, matches, routeMatch || getTargetMatch(matches, location), requestContext, unstable_dataStrategy, loadRouteIds, skipLoaderErrorBubbling, skipLoaders, routeMatch != null);
|
|
3018
3065
|
return result;
|
|
3019
3066
|
}
|
|
3020
|
-
let result = await loadRouteData(request, matches, requestContext, routeMatch);
|
|
3067
|
+
let result = await loadRouteData(request, matches, requestContext, unstable_dataStrategy, loadRouteIds, skipLoaderErrorBubbling, routeMatch);
|
|
3021
3068
|
return isResponse(result) ? result : _extends({}, result, {
|
|
3022
3069
|
actionData: null,
|
|
3023
3070
|
actionHeaders: {}
|
|
3024
3071
|
});
|
|
3025
3072
|
} catch (e) {
|
|
3026
|
-
// If the user threw/returned a Response in callLoaderOrAction
|
|
3027
|
-
//
|
|
3028
|
-
//
|
|
3029
|
-
if (
|
|
3073
|
+
// If the user threw/returned a Response in callLoaderOrAction for a
|
|
3074
|
+
// `queryRoute` call, we throw the `HandlerResult` to bail out early
|
|
3075
|
+
// and then return or throw the raw Response here accordingly
|
|
3076
|
+
if (isHandlerResult(e) && isResponse(e.result)) {
|
|
3030
3077
|
if (e.type === ResultType.error) {
|
|
3031
|
-
throw e.
|
|
3078
|
+
throw e.result;
|
|
3032
3079
|
}
|
|
3033
|
-
return e.
|
|
3080
|
+
return e.result;
|
|
3034
3081
|
}
|
|
3035
3082
|
// Redirects are always returned since they don't propagate to catch
|
|
3036
3083
|
// boundaries
|
|
@@ -3040,7 +3087,7 @@ function createStaticHandler(routes, opts) {
|
|
|
3040
3087
|
throw e;
|
|
3041
3088
|
}
|
|
3042
3089
|
}
|
|
3043
|
-
async function submit(request, matches, actionMatch, requestContext, isRouteRequest) {
|
|
3090
|
+
async function submit(request, matches, actionMatch, requestContext, unstable_dataStrategy, loadRouteIds, skipLoaderErrorBubbling, skipLoaders, isRouteRequest) {
|
|
3044
3091
|
let result;
|
|
3045
3092
|
if (!actionMatch.route.action && !actionMatch.route.lazy) {
|
|
3046
3093
|
let error = getInternalRouterError(405, {
|
|
@@ -3056,11 +3103,8 @@ function createStaticHandler(routes, opts) {
|
|
|
3056
3103
|
error
|
|
3057
3104
|
};
|
|
3058
3105
|
} else {
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
isRouteRequest,
|
|
3062
|
-
requestContext
|
|
3063
|
-
});
|
|
3106
|
+
let results = await callDataStrategy("action", request, [actionMatch], matches, isRouteRequest, requestContext, unstable_dataStrategy);
|
|
3107
|
+
result = results[0];
|
|
3064
3108
|
if (request.signal.aborted) {
|
|
3065
3109
|
throwStaticHandlerAbortedError(request, isRouteRequest, future);
|
|
3066
3110
|
}
|
|
@@ -3071,9 +3115,9 @@ function createStaticHandler(routes, opts) {
|
|
|
3071
3115
|
// can get back on the "throw all redirect responses" train here should
|
|
3072
3116
|
// this ever happen :/
|
|
3073
3117
|
throw new Response(null, {
|
|
3074
|
-
status: result.status,
|
|
3118
|
+
status: result.response.status,
|
|
3075
3119
|
headers: {
|
|
3076
|
-
Location: result.
|
|
3120
|
+
Location: result.response.headers.get("Location")
|
|
3077
3121
|
}
|
|
3078
3122
|
});
|
|
3079
3123
|
}
|
|
@@ -3110,41 +3154,73 @@ function createStaticHandler(routes, opts) {
|
|
|
3110
3154
|
activeDeferreds: null
|
|
3111
3155
|
};
|
|
3112
3156
|
}
|
|
3157
|
+
// Create a GET request for the loaders
|
|
3158
|
+
let loaderRequest = new Request(request.url, {
|
|
3159
|
+
headers: request.headers,
|
|
3160
|
+
redirect: request.redirect,
|
|
3161
|
+
signal: request.signal
|
|
3162
|
+
});
|
|
3113
3163
|
if (isErrorResult(result)) {
|
|
3114
3164
|
// Store off the pending error - we use it to determine which loaders
|
|
3115
3165
|
// to call and will commit it when we complete the navigation
|
|
3116
|
-
let boundaryMatch = findNearestBoundary(matches, actionMatch.route.id);
|
|
3117
|
-
let
|
|
3118
|
-
|
|
3119
|
-
|
|
3166
|
+
let boundaryMatch = skipLoaderErrorBubbling ? actionMatch : findNearestBoundary(matches, actionMatch.route.id);
|
|
3167
|
+
let statusCode = isRouteErrorResponse(result.error) ? result.error.status : result.statusCode != null ? result.statusCode : 500;
|
|
3168
|
+
let actionHeaders = _extends({}, result.headers ? {
|
|
3169
|
+
[actionMatch.route.id]: result.headers
|
|
3170
|
+
} : {});
|
|
3171
|
+
if (skipLoaders) {
|
|
3172
|
+
return {
|
|
3173
|
+
matches,
|
|
3174
|
+
loaderData: {},
|
|
3175
|
+
actionData: {},
|
|
3176
|
+
errors: {
|
|
3177
|
+
[boundaryMatch.route.id]: result.error
|
|
3178
|
+
},
|
|
3179
|
+
statusCode,
|
|
3180
|
+
loaderHeaders: {},
|
|
3181
|
+
actionHeaders,
|
|
3182
|
+
activeDeferreds: null
|
|
3183
|
+
};
|
|
3184
|
+
}
|
|
3185
|
+
let context = await loadRouteData(loaderRequest, matches, requestContext, unstable_dataStrategy, loadRouteIds, skipLoaderErrorBubbling, null, [boundaryMatch.route.id, result]);
|
|
3120
3186
|
// action status codes take precedence over loader status codes
|
|
3121
3187
|
return _extends({}, context, {
|
|
3122
|
-
statusCode
|
|
3188
|
+
statusCode,
|
|
3123
3189
|
actionData: null,
|
|
3124
|
-
actionHeaders
|
|
3125
|
-
[actionMatch.route.id]: result.headers
|
|
3126
|
-
} : {})
|
|
3190
|
+
actionHeaders
|
|
3127
3191
|
});
|
|
3128
3192
|
}
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3193
|
+
let actionHeaders = result.headers ? {
|
|
3194
|
+
[actionMatch.route.id]: result.headers
|
|
3195
|
+
} : {};
|
|
3196
|
+
if (skipLoaders) {
|
|
3197
|
+
return {
|
|
3198
|
+
matches,
|
|
3199
|
+
loaderData: {},
|
|
3200
|
+
actionData: {
|
|
3201
|
+
[actionMatch.route.id]: result.data
|
|
3202
|
+
},
|
|
3203
|
+
errors: null,
|
|
3204
|
+
statusCode: result.statusCode || 200,
|
|
3205
|
+
loaderHeaders: {},
|
|
3206
|
+
actionHeaders,
|
|
3207
|
+
activeDeferreds: null
|
|
3208
|
+
};
|
|
3209
|
+
}
|
|
3210
|
+
let context = await loadRouteData(loaderRequest, matches, requestContext, unstable_dataStrategy, loadRouteIds, skipLoaderErrorBubbling, null);
|
|
3211
|
+
return _extends({}, context, {
|
|
3139
3212
|
actionData: {
|
|
3140
3213
|
[actionMatch.route.id]: result.data
|
|
3141
|
-
}
|
|
3142
|
-
|
|
3214
|
+
}
|
|
3215
|
+
}, result.statusCode ? {
|
|
3216
|
+
statusCode: result.statusCode
|
|
3217
|
+
} : {}, {
|
|
3218
|
+
actionHeaders: result.headers ? {
|
|
3143
3219
|
[actionMatch.route.id]: result.headers
|
|
3144
|
-
} : {}
|
|
3220
|
+
} : {}
|
|
3145
3221
|
});
|
|
3146
3222
|
}
|
|
3147
|
-
async function loadRouteData(request, matches, requestContext, routeMatch,
|
|
3223
|
+
async function loadRouteData(request, matches, requestContext, unstable_dataStrategy, loadRouteIds, skipLoaderErrorBubbling, routeMatch, pendingActionResult) {
|
|
3148
3224
|
let isRouteRequest = routeMatch != null;
|
|
3149
3225
|
// Short circuit if we have no loaders to run (queryRoute())
|
|
3150
3226
|
if (isRouteRequest && !(routeMatch != null && routeMatch.route.loader) && !(routeMatch != null && routeMatch.route.lazy)) {
|
|
@@ -3154,8 +3230,11 @@ function createStaticHandler(routes, opts) {
|
|
|
3154
3230
|
routeId: routeMatch == null ? void 0 : routeMatch.route.id
|
|
3155
3231
|
});
|
|
3156
3232
|
}
|
|
3157
|
-
let requestMatches = routeMatch ? [routeMatch] : getLoaderMatchesUntilBoundary(matches,
|
|
3233
|
+
let requestMatches = routeMatch ? [routeMatch] : pendingActionResult && isErrorResult(pendingActionResult[1]) ? getLoaderMatchesUntilBoundary(matches, pendingActionResult[0]) : matches;
|
|
3158
3234
|
let matchesToLoad = requestMatches.filter(m => m.route.loader || m.route.lazy);
|
|
3235
|
+
if (loadRouteIds) {
|
|
3236
|
+
matchesToLoad = matchesToLoad.filter(m => loadRouteIds.includes(m.route.id));
|
|
3237
|
+
}
|
|
3159
3238
|
// Short circuit if we have no loaders to run (query())
|
|
3160
3239
|
if (matchesToLoad.length === 0) {
|
|
3161
3240
|
return {
|
|
@@ -3164,23 +3243,21 @@ function createStaticHandler(routes, opts) {
|
|
|
3164
3243
|
loaderData: matches.reduce((acc, m) => Object.assign(acc, {
|
|
3165
3244
|
[m.route.id]: null
|
|
3166
3245
|
}), {}),
|
|
3167
|
-
errors:
|
|
3246
|
+
errors: pendingActionResult && isErrorResult(pendingActionResult[1]) ? {
|
|
3247
|
+
[pendingActionResult[0]]: pendingActionResult[1].error
|
|
3248
|
+
} : null,
|
|
3168
3249
|
statusCode: 200,
|
|
3169
3250
|
loaderHeaders: {},
|
|
3170
3251
|
activeDeferreds: null
|
|
3171
3252
|
};
|
|
3172
3253
|
}
|
|
3173
|
-
let results = await
|
|
3174
|
-
isStaticRequest: true,
|
|
3175
|
-
isRouteRequest,
|
|
3176
|
-
requestContext
|
|
3177
|
-
}))]);
|
|
3254
|
+
let results = await callDataStrategy("loader", request, matchesToLoad, matches, isRouteRequest, requestContext, unstable_dataStrategy);
|
|
3178
3255
|
if (request.signal.aborted) {
|
|
3179
3256
|
throwStaticHandlerAbortedError(request, isRouteRequest, future);
|
|
3180
3257
|
}
|
|
3181
3258
|
// Process and commit output from loaders
|
|
3182
3259
|
let activeDeferreds = new Map();
|
|
3183
|
-
let context = processRouteLoaderData(matches, matchesToLoad, results,
|
|
3260
|
+
let context = processRouteLoaderData(matches, matchesToLoad, results, pendingActionResult, activeDeferreds, skipLoaderErrorBubbling);
|
|
3184
3261
|
// Add a null for any non-loader matches for proper revalidation on the client
|
|
3185
3262
|
let executedLoaders = new Set(matchesToLoad.map(match => match.route.id));
|
|
3186
3263
|
matches.forEach(match => {
|
|
@@ -3193,6 +3270,24 @@ function createStaticHandler(routes, opts) {
|
|
|
3193
3270
|
activeDeferreds: activeDeferreds.size > 0 ? Object.fromEntries(activeDeferreds.entries()) : null
|
|
3194
3271
|
});
|
|
3195
3272
|
}
|
|
3273
|
+
// Utility wrapper for calling dataStrategy server-side without having to
|
|
3274
|
+
// pass around the manifest, mapRouteProperties, etc.
|
|
3275
|
+
async function callDataStrategy(type, request, matchesToLoad, matches, isRouteRequest, requestContext, unstable_dataStrategy) {
|
|
3276
|
+
let results = await callDataStrategyImpl(unstable_dataStrategy || defaultDataStrategy, type, request, matchesToLoad, matches, manifest, mapRouteProperties, requestContext);
|
|
3277
|
+
return await Promise.all(results.map((result, i) => {
|
|
3278
|
+
if (isRedirectHandlerResult(result)) {
|
|
3279
|
+
let response = result.result;
|
|
3280
|
+
// Throw redirects and let the server handle them with an HTTP redirect
|
|
3281
|
+
throw normalizeRelativeRoutingRedirectResponse(response, request, matchesToLoad[i].route.id, matches, basename, future.v7_relativeSplatPath);
|
|
3282
|
+
}
|
|
3283
|
+
if (isResponse(result.result) && isRouteRequest) {
|
|
3284
|
+
// For SSR single-route requests, we want to hand Responses back
|
|
3285
|
+
// directly without unwrapping
|
|
3286
|
+
throw result;
|
|
3287
|
+
}
|
|
3288
|
+
return convertHandlerResultToDataResult(result);
|
|
3289
|
+
}));
|
|
3290
|
+
}
|
|
3196
3291
|
return {
|
|
3197
3292
|
dataRoutes,
|
|
3198
3293
|
query,
|
|
@@ -3402,13 +3497,18 @@ function getLoaderMatchesUntilBoundary(matches, boundaryId) {
|
|
|
3402
3497
|
}
|
|
3403
3498
|
return boundaryMatches;
|
|
3404
3499
|
}
|
|
3405
|
-
function getMatchesToLoad(history, state, matches, submission, location, isInitialLoad, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, deletedFetchers, fetchLoadMatches, fetchRedirectIds, routesToUse, basename,
|
|
3406
|
-
let actionResult =
|
|
3500
|
+
function getMatchesToLoad(history, state, matches, submission, location, isInitialLoad, skipActionErrorRevalidation, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, deletedFetchers, fetchLoadMatches, fetchRedirectIds, routesToUse, basename, pendingActionResult) {
|
|
3501
|
+
let actionResult = pendingActionResult ? isErrorResult(pendingActionResult[1]) ? pendingActionResult[1].error : pendingActionResult[1].data : undefined;
|
|
3407
3502
|
let currentUrl = history.createURL(state.location);
|
|
3408
3503
|
let nextUrl = history.createURL(location);
|
|
3409
3504
|
// Pick navigation matches that are net-new or qualify for revalidation
|
|
3410
|
-
let boundaryId =
|
|
3411
|
-
let boundaryMatches = getLoaderMatchesUntilBoundary(matches, boundaryId);
|
|
3505
|
+
let boundaryId = pendingActionResult && isErrorResult(pendingActionResult[1]) ? pendingActionResult[0] : undefined;
|
|
3506
|
+
let boundaryMatches = boundaryId ? getLoaderMatchesUntilBoundary(matches, boundaryId) : matches;
|
|
3507
|
+
// Don't revalidate loaders by default after action 4xx/5xx responses
|
|
3508
|
+
// when the flag is enabled. They can still opt-into revalidation via
|
|
3509
|
+
// `shouldRevalidate` via `actionResult`
|
|
3510
|
+
let actionStatus = pendingActionResult ? pendingActionResult[1].statusCode : undefined;
|
|
3511
|
+
let shouldSkipRevalidation = skipActionErrorRevalidation && actionStatus && actionStatus >= 400;
|
|
3412
3512
|
let navigationMatches = boundaryMatches.filter((match, index) => {
|
|
3413
3513
|
let {
|
|
3414
3514
|
route
|
|
@@ -3421,7 +3521,7 @@ function getMatchesToLoad(history, state, matches, submission, location, isIniti
|
|
|
3421
3521
|
return false;
|
|
3422
3522
|
}
|
|
3423
3523
|
if (isInitialLoad) {
|
|
3424
|
-
if (route.loader.hydrate) {
|
|
3524
|
+
if (typeof route.loader !== "function" || route.loader.hydrate) {
|
|
3425
3525
|
return true;
|
|
3426
3526
|
}
|
|
3427
3527
|
return state.loaderData[route.id] === undefined && (
|
|
@@ -3445,11 +3545,10 @@ function getMatchesToLoad(history, state, matches, submission, location, isIniti
|
|
|
3445
3545
|
nextParams: nextRouteMatch.params
|
|
3446
3546
|
}, submission, {
|
|
3447
3547
|
actionResult,
|
|
3448
|
-
|
|
3548
|
+
unstable_actionStatus: actionStatus,
|
|
3549
|
+
defaultShouldRevalidate: shouldSkipRevalidation ? false :
|
|
3449
3550
|
// 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 ||
|
|
3551
|
+
isRevalidationRequired || currentUrl.pathname + currentUrl.search === nextUrl.pathname + nextUrl.search ||
|
|
3453
3552
|
// Search params affect all loaders
|
|
3454
3553
|
currentUrl.search !== nextUrl.search || isNewRouteInstance(currentRouteMatch, nextRouteMatch)
|
|
3455
3554
|
}));
|
|
@@ -3508,7 +3607,8 @@ function getMatchesToLoad(history, state, matches, submission, location, isIniti
|
|
|
3508
3607
|
nextParams: matches[matches.length - 1].params
|
|
3509
3608
|
}, submission, {
|
|
3510
3609
|
actionResult,
|
|
3511
|
-
|
|
3610
|
+
unstable_actionStatus: actionStatus,
|
|
3611
|
+
defaultShouldRevalidate: shouldSkipRevalidation ? false : isRevalidationRequired
|
|
3512
3612
|
}));
|
|
3513
3613
|
}
|
|
3514
3614
|
if (shouldRevalidate) {
|
|
@@ -3603,24 +3703,87 @@ async function loadLazyRouteModule(route, mapRouteProperties, manifest) {
|
|
|
3603
3703
|
lazy: undefined
|
|
3604
3704
|
}));
|
|
3605
3705
|
}
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3706
|
+
// Default implementation of `dataStrategy` which fetches all loaders in parallel
|
|
3707
|
+
function defaultDataStrategy(opts) {
|
|
3708
|
+
return Promise.all(opts.matches.map(m => m.resolve()));
|
|
3709
|
+
}
|
|
3710
|
+
async function callDataStrategyImpl(dataStrategyImpl, type, request, matchesToLoad, matches, manifest, mapRouteProperties, requestContext) {
|
|
3711
|
+
let routeIdsToLoad = matchesToLoad.reduce((acc, m) => acc.add(m.route.id), new Set());
|
|
3712
|
+
let loadedMatches = new Set();
|
|
3713
|
+
// Send all matches here to allow for a middleware-type implementation.
|
|
3714
|
+
// handler will be a no-op for unneeded routes and we filter those results
|
|
3715
|
+
// back out below.
|
|
3716
|
+
let results = await dataStrategyImpl({
|
|
3717
|
+
matches: matches.map(match => {
|
|
3718
|
+
let shouldLoad = routeIdsToLoad.has(match.route.id);
|
|
3719
|
+
// `resolve` encapsulates the route.lazy, executing the
|
|
3720
|
+
// loader/action, and mapping return values/thrown errors to a
|
|
3721
|
+
// HandlerResult. Users can pass a callback to take fine-grained control
|
|
3722
|
+
// over the execution of the loader/action
|
|
3723
|
+
let resolve = handlerOverride => {
|
|
3724
|
+
loadedMatches.add(match.route.id);
|
|
3725
|
+
return shouldLoad ? callLoaderOrAction(type, request, match, manifest, mapRouteProperties, handlerOverride, requestContext) : Promise.resolve({
|
|
3726
|
+
type: ResultType.data,
|
|
3727
|
+
result: undefined
|
|
3728
|
+
});
|
|
3729
|
+
};
|
|
3730
|
+
return _extends({}, match, {
|
|
3731
|
+
shouldLoad,
|
|
3732
|
+
resolve
|
|
3733
|
+
});
|
|
3734
|
+
}),
|
|
3735
|
+
request,
|
|
3736
|
+
params: matches[0].params,
|
|
3737
|
+
context: requestContext
|
|
3738
|
+
});
|
|
3739
|
+
// Throw if any loadRoute implementations not called since they are what
|
|
3740
|
+
// ensures a route is fully loaded
|
|
3741
|
+
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."));
|
|
3742
|
+
// Filter out any middleware-only matches for which we didn't need to run handlers
|
|
3743
|
+
return results.filter((_, i) => routeIdsToLoad.has(matches[i].route.id));
|
|
3744
|
+
}
|
|
3745
|
+
// Default logic for calling a loader/action is the user has no specified a dataStrategy
|
|
3746
|
+
async function callLoaderOrAction(type, request, match, manifest, mapRouteProperties, handlerOverride, staticContext) {
|
|
3611
3747
|
let result;
|
|
3612
3748
|
let onReject;
|
|
3613
3749
|
let runHandler = handler => {
|
|
3614
3750
|
// Setup a promise we can race against so that abort signals short circuit
|
|
3615
3751
|
let reject;
|
|
3752
|
+
// This will never resolve so safe to type it as Promise<HandlerResult> to
|
|
3753
|
+
// satisfy the function return value
|
|
3616
3754
|
let abortPromise = new Promise((_, r) => reject = r);
|
|
3617
3755
|
onReject = () => reject();
|
|
3618
3756
|
request.signal.addEventListener("abort", onReject);
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3757
|
+
let actualHandler = ctx => {
|
|
3758
|
+
if (typeof handler !== "function") {
|
|
3759
|
+
return Promise.reject(new Error("You cannot call the handler for a route which defines a boolean " + ("\"" + type + "\" [routeId: " + match.route.id + "]")));
|
|
3760
|
+
}
|
|
3761
|
+
return handler({
|
|
3762
|
+
request,
|
|
3763
|
+
params: match.params,
|
|
3764
|
+
context: staticContext
|
|
3765
|
+
}, ...(ctx !== undefined ? [ctx] : []));
|
|
3766
|
+
};
|
|
3767
|
+
let handlerPromise;
|
|
3768
|
+
if (handlerOverride) {
|
|
3769
|
+
handlerPromise = handlerOverride(ctx => actualHandler(ctx));
|
|
3770
|
+
} else {
|
|
3771
|
+
handlerPromise = (async () => {
|
|
3772
|
+
try {
|
|
3773
|
+
let val = await actualHandler();
|
|
3774
|
+
return {
|
|
3775
|
+
type: "data",
|
|
3776
|
+
result: val
|
|
3777
|
+
};
|
|
3778
|
+
} catch (e) {
|
|
3779
|
+
return {
|
|
3780
|
+
type: "error",
|
|
3781
|
+
result: e
|
|
3782
|
+
};
|
|
3783
|
+
}
|
|
3784
|
+
})();
|
|
3785
|
+
}
|
|
3786
|
+
return Promise.race([handlerPromise, abortPromise]);
|
|
3624
3787
|
};
|
|
3625
3788
|
try {
|
|
3626
3789
|
let handler = match.route[type];
|
|
@@ -3628,23 +3791,23 @@ async function callLoaderOrAction(type, request, match, matches, manifest, mapRo
|
|
|
3628
3791
|
if (handler) {
|
|
3629
3792
|
// Run statically defined handler in parallel with lazy()
|
|
3630
3793
|
let handlerError;
|
|
3631
|
-
let
|
|
3794
|
+
let [value] = await Promise.all([
|
|
3632
3795
|
// If the handler throws, don't let it immediately bubble out,
|
|
3633
3796
|
// since we need to let the lazy() execution finish so we know if this
|
|
3634
3797
|
// route has a boundary that can handle the error
|
|
3635
3798
|
runHandler(handler).catch(e => {
|
|
3636
3799
|
handlerError = e;
|
|
3637
3800
|
}), loadLazyRouteModule(match.route, mapRouteProperties, manifest)]);
|
|
3638
|
-
if (handlerError) {
|
|
3801
|
+
if (handlerError !== undefined) {
|
|
3639
3802
|
throw handlerError;
|
|
3640
3803
|
}
|
|
3641
|
-
result =
|
|
3804
|
+
result = value;
|
|
3642
3805
|
} else {
|
|
3643
3806
|
// Load lazy route module, then run any returned handler
|
|
3644
3807
|
await loadLazyRouteModule(match.route, mapRouteProperties, manifest);
|
|
3645
3808
|
handler = match.route[type];
|
|
3646
3809
|
if (handler) {
|
|
3647
|
-
// Handler still
|
|
3810
|
+
// Handler still runs even if we got interrupted to maintain consistency
|
|
3648
3811
|
// with un-abortable behavior of handler execution on non-lazy or
|
|
3649
3812
|
// previously-lazy-loaded routes
|
|
3650
3813
|
result = await runHandler(handler);
|
|
@@ -3661,7 +3824,7 @@ async function callLoaderOrAction(type, request, match, matches, manifest, mapRo
|
|
|
3661
3824
|
// hit the invariant below that errors on returning undefined.
|
|
3662
3825
|
return {
|
|
3663
3826
|
type: ResultType.data,
|
|
3664
|
-
|
|
3827
|
+
result: undefined
|
|
3665
3828
|
};
|
|
3666
3829
|
}
|
|
3667
3830
|
}
|
|
@@ -3674,61 +3837,29 @@ async function callLoaderOrAction(type, request, match, matches, manifest, mapRo
|
|
|
3674
3837
|
} else {
|
|
3675
3838
|
result = await runHandler(handler);
|
|
3676
3839
|
}
|
|
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`.");
|
|
3840
|
+
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
3841
|
} catch (e) {
|
|
3679
|
-
|
|
3680
|
-
|
|
3842
|
+
// We should already be catching and converting normal handler executions to
|
|
3843
|
+
// HandlerResults and returning them, so anything that throws here is an
|
|
3844
|
+
// unexpected error we still need to wrap
|
|
3845
|
+
return {
|
|
3846
|
+
type: ResultType.error,
|
|
3847
|
+
result: e
|
|
3848
|
+
};
|
|
3681
3849
|
} finally {
|
|
3682
3850
|
if (onReject) {
|
|
3683
3851
|
request.signal.removeEventListener("abort", onReject);
|
|
3684
3852
|
}
|
|
3685
3853
|
}
|
|
3854
|
+
return result;
|
|
3855
|
+
}
|
|
3856
|
+
async function convertHandlerResultToDataResult(handlerResult) {
|
|
3857
|
+
let {
|
|
3858
|
+
result,
|
|
3859
|
+
type,
|
|
3860
|
+
status
|
|
3861
|
+
} = handlerResult;
|
|
3686
3862
|
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
3863
|
let data;
|
|
3733
3864
|
try {
|
|
3734
3865
|
let contentType = result.headers.get("Content-Type");
|
|
@@ -3749,10 +3880,11 @@ async function callLoaderOrAction(type, request, match, matches, manifest, mapRo
|
|
|
3749
3880
|
error: e
|
|
3750
3881
|
};
|
|
3751
3882
|
}
|
|
3752
|
-
if (
|
|
3883
|
+
if (type === ResultType.error) {
|
|
3753
3884
|
return {
|
|
3754
|
-
type:
|
|
3755
|
-
error: new ErrorResponseImpl(status, result.statusText, data),
|
|
3885
|
+
type: ResultType.error,
|
|
3886
|
+
error: new ErrorResponseImpl(result.status, result.statusText, data),
|
|
3887
|
+
statusCode: result.status,
|
|
3756
3888
|
headers: result.headers
|
|
3757
3889
|
};
|
|
3758
3890
|
}
|
|
@@ -3763,10 +3895,11 @@ async function callLoaderOrAction(type, request, match, matches, manifest, mapRo
|
|
|
3763
3895
|
headers: result.headers
|
|
3764
3896
|
};
|
|
3765
3897
|
}
|
|
3766
|
-
if (
|
|
3898
|
+
if (type === ResultType.error) {
|
|
3767
3899
|
return {
|
|
3768
|
-
type:
|
|
3769
|
-
error: result
|
|
3900
|
+
type: ResultType.error,
|
|
3901
|
+
error: result,
|
|
3902
|
+
statusCode: isRouteErrorResponse(result) ? result.status : status
|
|
3770
3903
|
};
|
|
3771
3904
|
}
|
|
3772
3905
|
if (isDeferredData(result)) {
|
|
@@ -3780,9 +3913,33 @@ async function callLoaderOrAction(type, request, match, matches, manifest, mapRo
|
|
|
3780
3913
|
}
|
|
3781
3914
|
return {
|
|
3782
3915
|
type: ResultType.data,
|
|
3783
|
-
data: result
|
|
3916
|
+
data: result,
|
|
3917
|
+
statusCode: status
|
|
3784
3918
|
};
|
|
3785
3919
|
}
|
|
3920
|
+
// Support relative routing in internal redirects
|
|
3921
|
+
function normalizeRelativeRoutingRedirectResponse(response, request, routeId, matches, basename, v7_relativeSplatPath) {
|
|
3922
|
+
let location = response.headers.get("Location");
|
|
3923
|
+
invariant(location, "Redirects returned/thrown from loaders/actions must have a Location header");
|
|
3924
|
+
if (!ABSOLUTE_URL_REGEX.test(location)) {
|
|
3925
|
+
let trimmedMatches = matches.slice(0, matches.findIndex(m => m.route.id === routeId) + 1);
|
|
3926
|
+
location = normalizeTo(new URL(request.url), trimmedMatches, basename, true, location, v7_relativeSplatPath);
|
|
3927
|
+
response.headers.set("Location", location);
|
|
3928
|
+
}
|
|
3929
|
+
return response;
|
|
3930
|
+
}
|
|
3931
|
+
function normalizeRedirectLocation(location, currentUrl, basename) {
|
|
3932
|
+
if (ABSOLUTE_URL_REGEX.test(location)) {
|
|
3933
|
+
// Strip off the protocol+origin for same-origin + same-basename absolute redirects
|
|
3934
|
+
let normalizedLocation = location;
|
|
3935
|
+
let url = normalizedLocation.startsWith("//") ? new URL(currentUrl.protocol + normalizedLocation) : new URL(normalizedLocation);
|
|
3936
|
+
let isSameBasename = stripBasename(url.pathname, basename) != null;
|
|
3937
|
+
if (url.origin === currentUrl.origin && isSameBasename) {
|
|
3938
|
+
return url.pathname + url.search + url.hash;
|
|
3939
|
+
}
|
|
3940
|
+
}
|
|
3941
|
+
return location;
|
|
3942
|
+
}
|
|
3786
3943
|
// Utility method for creating the Request instances for loaders/actions during
|
|
3787
3944
|
// client-side navigations and fetches. During SSR we will always have a
|
|
3788
3945
|
// Request instance from the static handler (query/queryRoute)
|
|
@@ -3833,33 +3990,38 @@ function convertSearchParamsToFormData(searchParams) {
|
|
|
3833
3990
|
}
|
|
3834
3991
|
return formData;
|
|
3835
3992
|
}
|
|
3836
|
-
function processRouteLoaderData(matches, matchesToLoad, results,
|
|
3993
|
+
function processRouteLoaderData(matches, matchesToLoad, results, pendingActionResult, activeDeferreds, skipLoaderErrorBubbling) {
|
|
3837
3994
|
// Fill in loaderData/errors from our loaders
|
|
3838
3995
|
let loaderData = {};
|
|
3839
3996
|
let errors = null;
|
|
3840
3997
|
let statusCode;
|
|
3841
3998
|
let foundError = false;
|
|
3842
3999
|
let loaderHeaders = {};
|
|
4000
|
+
let pendingError = pendingActionResult && isErrorResult(pendingActionResult[1]) ? pendingActionResult[1].error : undefined;
|
|
3843
4001
|
// Process loader results into state.loaderData/state.errors
|
|
3844
4002
|
results.forEach((result, index) => {
|
|
3845
4003
|
let id = matchesToLoad[index].route.id;
|
|
3846
4004
|
invariant(!isRedirectResult(result), "Cannot handle redirect results in processLoaderData");
|
|
3847
4005
|
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
4006
|
let error = result.error;
|
|
3852
4007
|
// If we have a pending action error, we report it at the highest-route
|
|
3853
4008
|
// that throws a loader error, and then clear it out to indicate that
|
|
3854
4009
|
// it was consumed
|
|
3855
|
-
if (pendingError) {
|
|
3856
|
-
error =
|
|
4010
|
+
if (pendingError !== undefined) {
|
|
4011
|
+
error = pendingError;
|
|
3857
4012
|
pendingError = undefined;
|
|
3858
4013
|
}
|
|
3859
4014
|
errors = errors || {};
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
4015
|
+
if (skipLoaderErrorBubbling) {
|
|
4016
|
+
errors[id] = error;
|
|
4017
|
+
} else {
|
|
4018
|
+
// Look upwards from the matched route for the closest ancestor error
|
|
4019
|
+
// boundary, defaulting to the root match. Prefer higher error values
|
|
4020
|
+
// if lower errors bubble to the same boundary
|
|
4021
|
+
let boundaryMatch = findNearestBoundary(matches, id);
|
|
4022
|
+
if (errors[boundaryMatch.route.id] == null) {
|
|
4023
|
+
errors[boundaryMatch.route.id] = error;
|
|
4024
|
+
}
|
|
3863
4025
|
}
|
|
3864
4026
|
// Clear our any prior loaderData for the throwing route
|
|
3865
4027
|
loaderData[id] = undefined;
|
|
@@ -3876,25 +4038,35 @@ function processRouteLoaderData(matches, matchesToLoad, results, pendingError, a
|
|
|
3876
4038
|
if (isDeferredResult(result)) {
|
|
3877
4039
|
activeDeferreds.set(id, result.deferredData);
|
|
3878
4040
|
loaderData[id] = result.deferredData.data;
|
|
4041
|
+
// Error status codes always override success status codes, but if all
|
|
4042
|
+
// loaders are successful we take the deepest status code.
|
|
4043
|
+
if (result.statusCode != null && result.statusCode !== 200 && !foundError) {
|
|
4044
|
+
statusCode = result.statusCode;
|
|
4045
|
+
}
|
|
4046
|
+
if (result.headers) {
|
|
4047
|
+
loaderHeaders[id] = result.headers;
|
|
4048
|
+
}
|
|
3879
4049
|
} else {
|
|
3880
4050
|
loaderData[id] = result.data;
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
4051
|
+
// Error status codes always override success status codes, but if all
|
|
4052
|
+
// loaders are successful we take the deepest status code.
|
|
4053
|
+
if (result.statusCode && result.statusCode !== 200 && !foundError) {
|
|
4054
|
+
statusCode = result.statusCode;
|
|
4055
|
+
}
|
|
4056
|
+
if (result.headers) {
|
|
4057
|
+
loaderHeaders[id] = result.headers;
|
|
4058
|
+
}
|
|
3889
4059
|
}
|
|
3890
4060
|
}
|
|
3891
4061
|
});
|
|
3892
4062
|
// If we didn't consume the pending action error (i.e., all loaders
|
|
3893
4063
|
// resolved), then consume it here. Also clear out any loaderData for the
|
|
3894
4064
|
// throwing route
|
|
3895
|
-
if (pendingError) {
|
|
3896
|
-
errors =
|
|
3897
|
-
|
|
4065
|
+
if (pendingError !== undefined && pendingActionResult) {
|
|
4066
|
+
errors = {
|
|
4067
|
+
[pendingActionResult[0]]: pendingError
|
|
4068
|
+
};
|
|
4069
|
+
loaderData[pendingActionResult[0]] = undefined;
|
|
3898
4070
|
}
|
|
3899
4071
|
return {
|
|
3900
4072
|
loaderData,
|
|
@@ -3903,11 +4075,12 @@ function processRouteLoaderData(matches, matchesToLoad, results, pendingError, a
|
|
|
3903
4075
|
loaderHeaders
|
|
3904
4076
|
};
|
|
3905
4077
|
}
|
|
3906
|
-
function processLoaderData(state, matches, matchesToLoad, results,
|
|
4078
|
+
function processLoaderData(state, matches, matchesToLoad, results, pendingActionResult, revalidatingFetchers, fetcherResults, activeDeferreds) {
|
|
3907
4079
|
let {
|
|
3908
4080
|
loaderData,
|
|
3909
4081
|
errors
|
|
3910
|
-
} = processRouteLoaderData(matches, matchesToLoad, results,
|
|
4082
|
+
} = processRouteLoaderData(matches, matchesToLoad, results, pendingActionResult, activeDeferreds, false // This method is only called client side so we always want to bubble
|
|
4083
|
+
);
|
|
3911
4084
|
// Process results from our revalidating fetchers
|
|
3912
4085
|
for (let index = 0; index < revalidatingFetchers.length; index++) {
|
|
3913
4086
|
let {
|
|
@@ -3967,6 +4140,19 @@ function mergeLoaderData(loaderData, newLoaderData, matches, errors) {
|
|
|
3967
4140
|
}
|
|
3968
4141
|
return mergedLoaderData;
|
|
3969
4142
|
}
|
|
4143
|
+
function getActionDataForCommit(pendingActionResult) {
|
|
4144
|
+
if (!pendingActionResult) {
|
|
4145
|
+
return {};
|
|
4146
|
+
}
|
|
4147
|
+
return isErrorResult(pendingActionResult[1]) ? {
|
|
4148
|
+
// Clear out prior actionData on errors
|
|
4149
|
+
actionData: {}
|
|
4150
|
+
} : {
|
|
4151
|
+
actionData: {
|
|
4152
|
+
[pendingActionResult[0]]: pendingActionResult[1].data
|
|
4153
|
+
}
|
|
4154
|
+
};
|
|
4155
|
+
}
|
|
3970
4156
|
// Find the nearest error boundary, looking upwards from the leaf route (or the
|
|
3971
4157
|
// route specified by routeId) for the closest ancestor error boundary,
|
|
3972
4158
|
// defaulting to the root match
|
|
@@ -4059,6 +4245,12 @@ function isHashChangeOnly(a, b) {
|
|
|
4059
4245
|
// /page#hash -> /page
|
|
4060
4246
|
return false;
|
|
4061
4247
|
}
|
|
4248
|
+
function isHandlerResult(result) {
|
|
4249
|
+
return result != null && typeof result === "object" && "type" in result && "result" in result && (result.type === ResultType.data || result.type === ResultType.error);
|
|
4250
|
+
}
|
|
4251
|
+
function isRedirectHandlerResult(result) {
|
|
4252
|
+
return isResponse(result.result) && redirectStatusCodes.has(result.result.status);
|
|
4253
|
+
}
|
|
4062
4254
|
function isDeferredResult(result) {
|
|
4063
4255
|
return result.type === ResultType.deferred;
|
|
4064
4256
|
}
|
|
@@ -4083,9 +4275,6 @@ function isRedirectResponse(result) {
|
|
|
4083
4275
|
let location = result.headers.get("Location");
|
|
4084
4276
|
return status >= 300 && status <= 399 && location != null;
|
|
4085
4277
|
}
|
|
4086
|
-
function isQueryRouteResponse(obj) {
|
|
4087
|
-
return obj && isResponse(obj.response) && (obj.type === ResultType.data || obj.type === ResultType.error);
|
|
4088
|
-
}
|
|
4089
4278
|
function isValidMethod(method) {
|
|
4090
4279
|
return validRequestMethods.has(method.toLowerCase());
|
|
4091
4280
|
}
|