@remix-run/router 1.7.1 → 1.7.2-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/dist/router.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @remix-run/router v1.7.1
2
+ * @remix-run/router v1.7.2-pre.1
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -1035,7 +1035,7 @@ class DeferredData {
1035
1035
  this.pendingKeysSet.add(key);
1036
1036
  // We store a little wrapper promise that will be extended with
1037
1037
  // _data/_error props upon resolve/reject
1038
- let promise = Promise.race([value, this.abortPromise]).then(data => this.onSettle(promise, key, null, data), error => this.onSettle(promise, key, error));
1038
+ let promise = Promise.race([value, this.abortPromise]).then(data => this.onSettle(promise, key, undefined, data), error => this.onSettle(promise, key, error));
1039
1039
  // Register rejection listeners to avoid uncaught promise rejections on
1040
1040
  // errors or aborted deferred values
1041
1041
  promise.catch(() => {});
@@ -1057,7 +1057,17 @@ class DeferredData {
1057
1057
  // Nothing left to abort!
1058
1058
  this.unlistenAbortSignal();
1059
1059
  }
1060
- if (error) {
1060
+ // If the promise was resolved/rejected with undefined, we'll throw an error as you
1061
+ // should always resolve with a value or null
1062
+ if (error === undefined && data === undefined) {
1063
+ let undefinedError = new Error("Deferred data for key \"" + key + "\" resolved/rejected with `undefined`, " + "you must resolve/reject with a value or `null`.");
1064
+ Object.defineProperty(promise, "_error", {
1065
+ get: () => undefinedError
1066
+ });
1067
+ this.emit(false, key);
1068
+ return Promise.reject(undefinedError);
1069
+ }
1070
+ if (data === undefined) {
1061
1071
  Object.defineProperty(promise, "_error", {
1062
1072
  get: () => error
1063
1073
  });
@@ -1809,6 +1819,7 @@ function createRouter(init) {
1809
1819
  // about to reload. Note that if this is an action reload we would have
1810
1820
  // already cancelled all pending deferreds so this would be a no-op
1811
1821
  cancelActiveDeferreds(routeId => !(matches && matches.some(m => m.route.id === routeId)) || matchesToLoad && matchesToLoad.some(m => m.route.id === routeId));
1822
+ pendingNavigationLoadId = ++incrementingLoadId;
1812
1823
  // Short circuit if we have no loaders to run
1813
1824
  if (matchesToLoad.length === 0 && revalidatingFetchers.length === 0) {
1814
1825
  let updatedFetchers = markFetchRedirectsDone();
@@ -1847,7 +1858,6 @@ function createRouter(init) {
1847
1858
  fetchers: new Map(state.fetchers)
1848
1859
  } : {}));
1849
1860
  }
1850
- pendingNavigationLoadId = ++incrementingLoadId;
1851
1861
  revalidatingFetchers.forEach(rf => {
1852
1862
  if (fetchControllers.has(rf.key)) {
1853
1863
  abortFetcher(rf.key);
@@ -1884,7 +1894,14 @@ function createRouter(init) {
1884
1894
  // If any loaders returned a redirect Response, start a new REPLACE navigation
1885
1895
  let redirect = findRedirect(results);
1886
1896
  if (redirect) {
1887
- await startRedirectNavigation(state, redirect, {
1897
+ if (redirect.idx >= matchesToLoad.length) {
1898
+ // If this redirect came from a fetcher make sure we mark it in
1899
+ // fetchRedirectIds so it doesn't get revalidated on the next set of
1900
+ // loader executions
1901
+ let fetcherKey = revalidatingFetchers[redirect.idx - matchesToLoad.length].key;
1902
+ fetchRedirectIds.add(fetcherKey);
1903
+ }
1904
+ await startRedirectNavigation(state, redirect.result, {
1888
1905
  replace
1889
1906
  });
1890
1907
  return {
@@ -1983,6 +2000,7 @@ function createRouter(init) {
1983
2000
  let abortController = new AbortController();
1984
2001
  let fetchRequest = createClientSideRequest(init.history, path, abortController.signal, submission);
1985
2002
  fetchControllers.set(key, abortController);
2003
+ let originatingLoadId = incrementingLoadId;
1986
2004
  let actionResult = await callLoaderOrAction("action", fetchRequest, match, requestMatches, manifest, mapRouteProperties, basename);
1987
2005
  if (fetchRequest.signal.aborted) {
1988
2006
  // We can delete this so long as we weren't aborted by ou our own fetcher
@@ -1994,16 +2012,29 @@ function createRouter(init) {
1994
2012
  }
1995
2013
  if (isRedirectResult(actionResult)) {
1996
2014
  fetchControllers.delete(key);
1997
- fetchRedirectIds.add(key);
1998
- let loadingFetcher = getLoadingFetcher(submission);
1999
- state.fetchers.set(key, loadingFetcher);
2000
- updateState({
2001
- fetchers: new Map(state.fetchers)
2002
- });
2003
- return startRedirectNavigation(state, actionResult, {
2004
- submission,
2005
- isFetchActionRedirect: true
2006
- });
2015
+ if (pendingNavigationLoadId > originatingLoadId) {
2016
+ // A new navigation was kicked off after our action started, so that
2017
+ // should take precedence over this redirect navigation. We already
2018
+ // set isRevalidationRequired so all loaders for the new route should
2019
+ // fire unless opted out via shouldRevalidate
2020
+ let doneFetcher = getDoneFetcher(undefined);
2021
+ state.fetchers.set(key, doneFetcher);
2022
+ updateState({
2023
+ fetchers: new Map(state.fetchers)
2024
+ });
2025
+ return;
2026
+ } else {
2027
+ fetchRedirectIds.add(key);
2028
+ let loadingFetcher = getLoadingFetcher(submission);
2029
+ state.fetchers.set(key, loadingFetcher);
2030
+ updateState({
2031
+ fetchers: new Map(state.fetchers)
2032
+ });
2033
+ return startRedirectNavigation(state, actionResult, {
2034
+ submission,
2035
+ isFetchActionRedirect: true
2036
+ });
2037
+ }
2007
2038
  }
2008
2039
  // Process any non-redirect errors thrown
2009
2040
  if (isErrorResult(actionResult)) {
@@ -2064,7 +2095,14 @@ function createRouter(init) {
2064
2095
  revalidatingFetchers.forEach(r => fetchControllers.delete(r.key));
2065
2096
  let redirect = findRedirect(results);
2066
2097
  if (redirect) {
2067
- return startRedirectNavigation(state, redirect);
2098
+ if (redirect.idx >= matchesToLoad.length) {
2099
+ // If this redirect came from a fetcher make sure we mark it in
2100
+ // fetchRedirectIds so it doesn't get revalidated on the next set of
2101
+ // loader executions
2102
+ let fetcherKey = revalidatingFetchers[redirect.idx - matchesToLoad.length].key;
2103
+ fetchRedirectIds.add(fetcherKey);
2104
+ }
2105
+ return startRedirectNavigation(state, redirect.result);
2068
2106
  }
2069
2107
  // Process and commit output from loaders
2070
2108
  let {
@@ -2116,6 +2154,7 @@ function createRouter(init) {
2116
2154
  let abortController = new AbortController();
2117
2155
  let fetchRequest = createClientSideRequest(init.history, path, abortController.signal);
2118
2156
  fetchControllers.set(key, abortController);
2157
+ let originatingLoadId = incrementingLoadId;
2119
2158
  let result = await callLoaderOrAction("loader", fetchRequest, match, matches, manifest, mapRouteProperties, basename);
2120
2159
  // Deferred isn't supported for fetcher loads, await everything and treat it
2121
2160
  // as a normal load. resolveDeferredData will return undefined if this
@@ -2134,9 +2173,20 @@ function createRouter(init) {
2134
2173
  }
2135
2174
  // If the loader threw a redirect Response, start a new REPLACE navigation
2136
2175
  if (isRedirectResult(result)) {
2137
- fetchRedirectIds.add(key);
2138
- await startRedirectNavigation(state, result);
2139
- return;
2176
+ if (pendingNavigationLoadId > originatingLoadId) {
2177
+ // A new navigation was kicked off after our loader started, so that
2178
+ // should take precedence over this redirect navigation
2179
+ let doneFetcher = getDoneFetcher(undefined);
2180
+ state.fetchers.set(key, doneFetcher);
2181
+ updateState({
2182
+ fetchers: new Map(state.fetchers)
2183
+ });
2184
+ return;
2185
+ } else {
2186
+ fetchRedirectIds.add(key);
2187
+ await startRedirectNavigation(state, result);
2188
+ return;
2189
+ }
2140
2190
  }
2141
2191
  // Process any non-redirect errors thrown
2142
2192
  if (isErrorResult(result)) {
@@ -3129,7 +3179,9 @@ function getMatchesToLoad(history, state, matches, submission, location, isReval
3129
3179
  }
3130
3180
  let fetcherMatches = matchRoutes(routesToUse, f.path, basename);
3131
3181
  // If the fetcher path no longer matches, push it in with null matches so
3132
- // we can trigger a 404 in callLoadersAndMaybeResolveData
3182
+ // we can trigger a 404 in callLoadersAndMaybeResolveData. Note this is
3183
+ // currently only a use-case for Remix HMR where the route tree can change
3184
+ // at runtime and remove a route previously loaded via a fetcher
3133
3185
  if (!fetcherMatches) {
3134
3186
  revalidatingFetchers.push({
3135
3187
  key,
@@ -3142,30 +3194,35 @@ function getMatchesToLoad(history, state, matches, submission, location, isReval
3142
3194
  return;
3143
3195
  }
3144
3196
  // Revalidating fetchers are decoupled from the route matches since they
3145
- // load from a static href. They only set `defaultShouldRevalidate` on
3146
- // explicit revalidation due to submission, useRevalidator, or X-Remix-Revalidate
3147
- //
3148
- // They automatically revalidate without even calling shouldRevalidate if:
3149
- // - They were cancelled
3150
- // - They're in the middle of their first load and therefore this is still
3151
- // an initial load and not a revalidation
3152
- //
3153
- // If neither of those is true, then they _always_ check shouldRevalidate
3197
+ // load from a static href. They revalidate based on explicit revalidation
3198
+ // (submission, useRevalidator, or X-Remix-Revalidate)
3154
3199
  let fetcher = state.fetchers.get(key);
3155
- let isPerformingInitialLoad = fetcher && fetcher.state !== "idle" && fetcher.data === undefined &&
3156
- // If a fetcher.load redirected then it'll be "loading" without any data
3157
- // so ensure we're not processing the redirect from this fetcher
3158
- !fetchRedirectIds.has(key);
3159
3200
  let fetcherMatch = getTargetMatch(fetcherMatches, f.path);
3160
- let shouldRevalidate = cancelledFetcherLoads.includes(key) || isPerformingInitialLoad || shouldRevalidateLoader(fetcherMatch, _extends({
3161
- currentUrl,
3162
- currentParams: state.matches[state.matches.length - 1].params,
3163
- nextUrl,
3164
- nextParams: matches[matches.length - 1].params
3165
- }, submission, {
3166
- actionResult,
3167
- defaultShouldRevalidate: isRevalidationRequired
3168
- }));
3201
+ let shouldRevalidate = false;
3202
+ if (fetchRedirectIds.has(key)) {
3203
+ // Never trigger a revalidation of an actively redirecting fetcher
3204
+ shouldRevalidate = false;
3205
+ } else if (cancelledFetcherLoads.includes(key)) {
3206
+ // Always revalidate if the fetcher was cancelled
3207
+ shouldRevalidate = true;
3208
+ } else if (fetcher && fetcher.state !== "idle" && fetcher.data === undefined) {
3209
+ // If the fetcher hasn't ever completed loading yet, then this isn't a
3210
+ // revalidation, it would just be a brand new load if an explicit
3211
+ // revalidation is required
3212
+ shouldRevalidate = isRevalidationRequired;
3213
+ } else {
3214
+ // Otherwise fall back on any user-defined shouldRevalidate, defaulting
3215
+ // to explicit revalidations only
3216
+ shouldRevalidate = shouldRevalidateLoader(fetcherMatch, _extends({
3217
+ currentUrl,
3218
+ currentParams: state.matches[state.matches.length - 1].params,
3219
+ nextUrl,
3220
+ nextParams: matches[matches.length - 1].params
3221
+ }, submission, {
3222
+ actionResult,
3223
+ defaultShouldRevalidate: isRevalidationRequired
3224
+ }));
3225
+ }
3169
3226
  if (shouldRevalidate) {
3170
3227
  revalidatingFetchers.push({
3171
3228
  key,
@@ -3661,7 +3718,10 @@ function findRedirect(results) {
3661
3718
  for (let i = results.length - 1; i >= 0; i--) {
3662
3719
  let result = results[i];
3663
3720
  if (isRedirectResult(result)) {
3664
- return result;
3721
+ return {
3722
+ result,
3723
+ idx: i
3724
+ };
3665
3725
  }
3666
3726
  }
3667
3727
  }