@remix-run/router 1.11.0 → 1.12.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.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @remix-run/router v1.11.0
2
+ * @remix-run/router v1.12.0
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -1223,16 +1223,30 @@
1223
1223
  // `to` values that do not provide a pathname. `to` can simply be a search or
1224
1224
  // hash string, in which case we should assume that the navigation is relative
1225
1225
  // to the current location's pathname and *not* the route pathname.
1226
- if (isPathRelative || toPathname == null) {
1226
+ if (toPathname == null) {
1227
1227
  from = locationPathname;
1228
+ } else if (isPathRelative) {
1229
+ let fromSegments = routePathnames[routePathnames.length - 1].replace(/^\//, "").split("/");
1230
+ if (toPathname.startsWith("..")) {
1231
+ let toSegments = toPathname.split("/");
1232
+
1233
+ // With relative="path", each leading .. segment means "go up one URL segment"
1234
+ while (toSegments[0] === "..") {
1235
+ toSegments.shift();
1236
+ fromSegments.pop();
1237
+ }
1238
+ to.pathname = toSegments.join("/");
1239
+ }
1240
+ from = "/" + fromSegments.join("/");
1228
1241
  } else {
1229
1242
  let routePathnameIndex = routePathnames.length - 1;
1230
1243
  if (toPathname.startsWith("..")) {
1231
1244
  let toSegments = toPathname.split("/");
1232
1245
 
1233
- // Each leading .. segment means "go up one route" instead of "go up one
1234
- // URL segment". This is a key difference from how <a href> works and a
1235
- // major reason we call this a "to" value instead of a "href".
1246
+ // With relative="route" (the default), each leading .. segment means
1247
+ // "go up one route" instead of "go up one URL segment". This is a key
1248
+ // difference from how <a href> works and a major reason we call this a
1249
+ // "to" value instead of a "href".
1236
1250
  while (toSegments[0] === "..") {
1237
1251
  toSegments.shift();
1238
1252
  routePathnameIndex -= 1;
@@ -1902,7 +1916,10 @@
1902
1916
  }
1903
1917
 
1904
1918
  // Update our state and notify the calling context of the change
1905
- function updateState(newState, viewTransitionOpts) {
1919
+ function updateState(newState, opts) {
1920
+ if (opts === void 0) {
1921
+ opts = {};
1922
+ }
1906
1923
  state = _extends({}, state, newState);
1907
1924
 
1908
1925
  // Prep fetcher cleanup so we can tell the UI which fetcher data entries
@@ -1923,9 +1940,14 @@
1923
1940
  }
1924
1941
  });
1925
1942
  }
1926
- subscribers.forEach(subscriber => subscriber(state, {
1943
+
1944
+ // Iterate over a local copy so that if flushSync is used and we end up
1945
+ // removing and adding a new subscriber due to the useCallback dependencies,
1946
+ // we don't get ourselves into a loop calling the new subscriber immediately
1947
+ [...subscribers].forEach(subscriber => subscriber(state, {
1927
1948
  deletedFetchers: deletedFetchersKeys,
1928
- unstable_viewTransitionOpts: viewTransitionOpts
1949
+ unstable_viewTransitionOpts: opts.viewTransitionOpts,
1950
+ unstable_flushSync: opts.flushSync === true
1929
1951
  }));
1930
1952
 
1931
1953
  // Remove idle fetchers from state since we only care about in-flight fetchers.
@@ -1940,8 +1962,11 @@
1940
1962
  // - Location is a required param
1941
1963
  // - Navigation will always be set to IDLE_NAVIGATION
1942
1964
  // - Can pass any other state in newState
1943
- function completeNavigation(location, newState) {
1965
+ function completeNavigation(location, newState, _temp) {
1944
1966
  var _location$state, _location$state2;
1967
+ let {
1968
+ flushSync
1969
+ } = _temp === void 0 ? {} : _temp;
1945
1970
  // Deduce if we're in a loading/actionReload state:
1946
1971
  // - We have committed actionData in the store
1947
1972
  // - The current navigation was a mutation submission
@@ -2032,7 +2057,10 @@
2032
2057
  restoreScrollPosition: getSavedScrollPosition(location, newState.matches || state.matches),
2033
2058
  preventScrollReset,
2034
2059
  blockers
2035
- }), viewTransitionOpts);
2060
+ }), {
2061
+ viewTransitionOpts,
2062
+ flushSync: flushSync === true
2063
+ });
2036
2064
 
2037
2065
  // Reset stateful navigation vars
2038
2066
  pendingAction = Action.Pop;
@@ -2078,6 +2106,7 @@
2078
2106
  historyAction = Action.Replace;
2079
2107
  }
2080
2108
  let preventScrollReset = opts && "preventScrollReset" in opts ? opts.preventScrollReset === true : undefined;
2109
+ let flushSync = (opts && opts.unstable_flushSync) === true;
2081
2110
  let blockerKey = shouldBlockNavigation({
2082
2111
  currentLocation,
2083
2112
  nextLocation,
@@ -2115,7 +2144,8 @@
2115
2144
  pendingError: error,
2116
2145
  preventScrollReset,
2117
2146
  replace: opts && opts.replace,
2118
- enableViewTransition: opts && opts.unstable_viewTransition
2147
+ enableViewTransition: opts && opts.unstable_viewTransition,
2148
+ flushSync
2119
2149
  });
2120
2150
  }
2121
2151
 
@@ -2172,6 +2202,7 @@
2172
2202
  let routesToUse = inFlightDataRoutes || dataRoutes;
2173
2203
  let loadingNavigation = opts && opts.overrideNavigation;
2174
2204
  let matches = matchRoutes(routesToUse, location, basename);
2205
+ let flushSync = (opts && opts.flushSync) === true;
2175
2206
 
2176
2207
  // Short circuit with a 404 on the root error boundary if we match nothing
2177
2208
  if (!matches) {
@@ -2190,6 +2221,8 @@
2190
2221
  errors: {
2191
2222
  [route.id]: error
2192
2223
  }
2224
+ }, {
2225
+ flushSync
2193
2226
  });
2194
2227
  return;
2195
2228
  }
@@ -2203,6 +2236,8 @@
2203
2236
  if (state.initialized && !isRevalidationRequired && isHashChangeOnly(state.location, location) && !(opts && opts.submission && isMutationMethod(opts.submission.formMethod))) {
2204
2237
  completeNavigation(location, {
2205
2238
  matches
2239
+ }, {
2240
+ flushSync
2206
2241
  });
2207
2242
  return;
2208
2243
  }
@@ -2223,7 +2258,8 @@
2223
2258
  } else if (opts && opts.submission && isMutationMethod(opts.submission.formMethod)) {
2224
2259
  // Call action if we received an action submission
2225
2260
  let actionOutput = await handleAction(request, location, opts.submission, matches, {
2226
- replace: opts.replace
2261
+ replace: opts.replace,
2262
+ flushSync
2227
2263
  });
2228
2264
  if (actionOutput.shortCircuited) {
2229
2265
  return;
@@ -2231,6 +2267,7 @@
2231
2267
  pendingActionData = actionOutput.pendingActionData;
2232
2268
  pendingError = actionOutput.pendingActionError;
2233
2269
  loadingNavigation = getLoadingNavigation(location, opts.submission);
2270
+ flushSync = false;
2234
2271
 
2235
2272
  // Create a GET request for the loaders
2236
2273
  request = new Request(request.url, {
@@ -2243,7 +2280,7 @@
2243
2280
  shortCircuited,
2244
2281
  loaderData,
2245
2282
  errors
2246
- } = await handleLoaders(request, location, matches, loadingNavigation, opts && opts.submission, opts && opts.fetcherSubmission, opts && opts.replace, pendingActionData, pendingError);
2283
+ } = await handleLoaders(request, location, matches, loadingNavigation, opts && opts.submission, opts && opts.fetcherSubmission, opts && opts.replace, flushSync, pendingActionData, pendingError);
2247
2284
  if (shortCircuited) {
2248
2285
  return;
2249
2286
  }
@@ -2274,6 +2311,8 @@
2274
2311
  let navigation = getSubmittingNavigation(location, submission);
2275
2312
  updateState({
2276
2313
  navigation
2314
+ }, {
2315
+ flushSync: opts.flushSync === true
2277
2316
  });
2278
2317
 
2279
2318
  // Call our action and get the result
@@ -2348,7 +2387,7 @@
2348
2387
 
2349
2388
  // Call all applicable loaders for the given matches, handling redirects,
2350
2389
  // errors, etc.
2351
- async function handleLoaders(request, location, matches, overrideNavigation, submission, fetcherSubmission, replace, pendingActionData, pendingError) {
2390
+ async function handleLoaders(request, location, matches, overrideNavigation, submission, fetcherSubmission, replace, flushSync, pendingActionData, pendingError) {
2352
2391
  // Figure out the right navigation we want to use for data loading
2353
2392
  let loadingNavigation = overrideNavigation || getLoadingNavigation(location, submission);
2354
2393
 
@@ -2376,7 +2415,9 @@
2376
2415
  actionData: pendingActionData
2377
2416
  } : {}, updatedFetchers ? {
2378
2417
  fetchers: new Map(state.fetchers)
2379
- } : {}));
2418
+ } : {}), {
2419
+ flushSync
2420
+ });
2380
2421
  return {
2381
2422
  shortCircuited: true
2382
2423
  };
@@ -2401,7 +2442,9 @@
2401
2442
  actionData
2402
2443
  } : {}, revalidatingFetchers.length > 0 ? {
2403
2444
  fetchers: new Map(state.fetchers)
2404
- } : {}));
2445
+ } : {}), {
2446
+ flushSync
2447
+ });
2405
2448
  }
2406
2449
  revalidatingFetchers.forEach(rf => {
2407
2450
  if (fetchControllers.has(rf.key)) {
@@ -2484,17 +2527,6 @@
2484
2527
  fetchers: new Map(state.fetchers)
2485
2528
  } : {});
2486
2529
  }
2487
- function getFetcher(key) {
2488
- if (future.v7_fetcherPersist) {
2489
- activeFetchers.set(key, (activeFetchers.get(key) || 0) + 1);
2490
- // If this fetcher was previously marked for deletion, unmark it since we
2491
- // have a new instance
2492
- if (deletedFetchers.has(key)) {
2493
- deletedFetchers.delete(key);
2494
- }
2495
- }
2496
- return state.fetchers.get(key) || IDLE_FETCHER;
2497
- }
2498
2530
 
2499
2531
  // Trigger a fetcher load/submit for the given fetcher key
2500
2532
  function fetch(key, routeId, href, opts) {
@@ -2502,13 +2534,16 @@
2502
2534
  throw new Error("router.fetch() was called during the server render, but it shouldn't be. " + "You are likely calling a useFetcher() method in the body of your component. " + "Try moving it to a useEffect or a callback.");
2503
2535
  }
2504
2536
  if (fetchControllers.has(key)) abortFetcher(key);
2537
+ let flushSync = (opts && opts.unstable_flushSync) === true;
2505
2538
  let routesToUse = inFlightDataRoutes || dataRoutes;
2506
2539
  let normalizedPath = normalizeTo(state.location, state.matches, basename, future.v7_prependBasename, href, routeId, opts == null ? void 0 : opts.relative);
2507
2540
  let matches = matchRoutes(routesToUse, normalizedPath, basename);
2508
2541
  if (!matches) {
2509
2542
  setFetcherError(key, routeId, getInternalRouterError(404, {
2510
2543
  pathname: normalizedPath
2511
- }));
2544
+ }), {
2545
+ flushSync
2546
+ });
2512
2547
  return;
2513
2548
  }
2514
2549
  let {
@@ -2517,13 +2552,15 @@
2517
2552
  error
2518
2553
  } = normalizeNavigateOptions(future.v7_normalizeFormMethod, true, normalizedPath, opts);
2519
2554
  if (error) {
2520
- setFetcherError(key, routeId, error);
2555
+ setFetcherError(key, routeId, error, {
2556
+ flushSync
2557
+ });
2521
2558
  return;
2522
2559
  }
2523
2560
  let match = getTargetMatch(matches, path);
2524
2561
  pendingPreventScrollReset = (opts && opts.preventScrollReset) === true;
2525
2562
  if (submission && isMutationMethod(submission.formMethod)) {
2526
- handleFetcherAction(key, routeId, path, match, matches, submission);
2563
+ handleFetcherAction(key, routeId, path, match, matches, flushSync, submission);
2527
2564
  return;
2528
2565
  }
2529
2566
 
@@ -2533,12 +2570,12 @@
2533
2570
  routeId,
2534
2571
  path
2535
2572
  });
2536
- handleFetcherLoader(key, routeId, path, match, matches, submission);
2573
+ handleFetcherLoader(key, routeId, path, match, matches, flushSync, submission);
2537
2574
  }
2538
2575
 
2539
2576
  // Call the action for the matched fetcher.submit(), and then handle redirects,
2540
2577
  // errors, and revalidation
2541
- async function handleFetcherAction(key, routeId, path, match, requestMatches, submission) {
2578
+ async function handleFetcherAction(key, routeId, path, match, requestMatches, flushSync, submission) {
2542
2579
  interruptActiveLoads();
2543
2580
  fetchLoadMatches.delete(key);
2544
2581
  if (!match.route.action && !match.route.lazy) {
@@ -2547,16 +2584,16 @@
2547
2584
  pathname: path,
2548
2585
  routeId: routeId
2549
2586
  });
2550
- setFetcherError(key, routeId, error);
2587
+ setFetcherError(key, routeId, error, {
2588
+ flushSync
2589
+ });
2551
2590
  return;
2552
2591
  }
2553
2592
 
2554
2593
  // Put this fetcher into it's submitting state
2555
2594
  let existingFetcher = state.fetchers.get(key);
2556
- let fetcher = getSubmittingFetcher(submission, existingFetcher);
2557
- state.fetchers.set(key, fetcher);
2558
- updateState({
2559
- fetchers: new Map(state.fetchers)
2595
+ updateFetcherState(key, getSubmittingFetcher(submission, existingFetcher), {
2596
+ flushSync
2560
2597
  });
2561
2598
 
2562
2599
  // Call the action for the fetcher
@@ -2574,10 +2611,7 @@
2574
2611
  return;
2575
2612
  }
2576
2613
  if (deletedFetchers.has(key)) {
2577
- state.fetchers.set(key, getDoneFetcher(undefined));
2578
- updateState({
2579
- fetchers: new Map(state.fetchers)
2580
- });
2614
+ updateFetcherState(key, getDoneFetcher(undefined));
2581
2615
  return;
2582
2616
  }
2583
2617
  if (isRedirectResult(actionResult)) {
@@ -2587,19 +2621,11 @@
2587
2621
  // should take precedence over this redirect navigation. We already
2588
2622
  // set isRevalidationRequired so all loaders for the new route should
2589
2623
  // fire unless opted out via shouldRevalidate
2590
- let doneFetcher = getDoneFetcher(undefined);
2591
- state.fetchers.set(key, doneFetcher);
2592
- updateState({
2593
- fetchers: new Map(state.fetchers)
2594
- });
2624
+ updateFetcherState(key, getDoneFetcher(undefined));
2595
2625
  return;
2596
2626
  } else {
2597
2627
  fetchRedirectIds.add(key);
2598
- let loadingFetcher = getLoadingFetcher(submission);
2599
- state.fetchers.set(key, loadingFetcher);
2600
- updateState({
2601
- fetchers: new Map(state.fetchers)
2602
- });
2628
+ updateFetcherState(key, getLoadingFetcher(submission));
2603
2629
  return startRedirectNavigation(state, actionResult, {
2604
2630
  fetcherSubmission: submission
2605
2631
  });
@@ -2717,13 +2743,10 @@
2717
2743
  }
2718
2744
 
2719
2745
  // Call the matched loader for fetcher.load(), handling redirects, errors, etc.
2720
- async function handleFetcherLoader(key, routeId, path, match, matches, submission) {
2746
+ async function handleFetcherLoader(key, routeId, path, match, matches, flushSync, submission) {
2721
2747
  let existingFetcher = state.fetchers.get(key);
2722
- // Put this fetcher into it's loading state
2723
- let loadingFetcher = getLoadingFetcher(submission, existingFetcher ? existingFetcher.data : undefined);
2724
- state.fetchers.set(key, loadingFetcher);
2725
- updateState({
2726
- fetchers: new Map(state.fetchers)
2748
+ updateFetcherState(key, getLoadingFetcher(submission, existingFetcher ? existingFetcher.data : undefined), {
2749
+ flushSync
2727
2750
  });
2728
2751
 
2729
2752
  // Call the loader for this fetcher route match
@@ -2750,10 +2773,7 @@
2750
2773
  return;
2751
2774
  }
2752
2775
  if (deletedFetchers.has(key)) {
2753
- state.fetchers.set(key, getDoneFetcher(undefined));
2754
- updateState({
2755
- fetchers: new Map(state.fetchers)
2756
- });
2776
+ updateFetcherState(key, getDoneFetcher(undefined));
2757
2777
  return;
2758
2778
  }
2759
2779
 
@@ -2762,11 +2782,7 @@
2762
2782
  if (pendingNavigationLoadId > originatingLoadId) {
2763
2783
  // A new navigation was kicked off after our loader started, so that
2764
2784
  // should take precedence over this redirect navigation
2765
- let doneFetcher = getDoneFetcher(undefined);
2766
- state.fetchers.set(key, doneFetcher);
2767
- updateState({
2768
- fetchers: new Map(state.fetchers)
2769
- });
2785
+ updateFetcherState(key, getDoneFetcher(undefined));
2770
2786
  return;
2771
2787
  } else {
2772
2788
  fetchRedirectIds.add(key);
@@ -2783,11 +2799,7 @@
2783
2799
  invariant(!isDeferredResult(result), "Unhandled fetcher deferred data");
2784
2800
 
2785
2801
  // Put the fetcher back into an idle state
2786
- let doneFetcher = getDoneFetcher(result.data);
2787
- state.fetchers.set(key, doneFetcher);
2788
- updateState({
2789
- fetchers: new Map(state.fetchers)
2790
- });
2802
+ updateFetcherState(key, getDoneFetcher(result.data));
2791
2803
  }
2792
2804
 
2793
2805
  /**
@@ -2809,12 +2821,12 @@
2809
2821
  * actually touch history until we've processed redirects, so we just use
2810
2822
  * the history action from the original navigation (PUSH or REPLACE).
2811
2823
  */
2812
- async function startRedirectNavigation(state, redirect, _temp) {
2824
+ async function startRedirectNavigation(state, redirect, _temp2) {
2813
2825
  let {
2814
2826
  submission,
2815
2827
  fetcherSubmission,
2816
2828
  replace
2817
- } = _temp === void 0 ? {} : _temp;
2829
+ } = _temp2 === void 0 ? {} : _temp2;
2818
2830
  if (redirect.revalidate) {
2819
2831
  isRevalidationRequired = true;
2820
2832
  }
@@ -2928,7 +2940,21 @@
2928
2940
  }
2929
2941
  });
2930
2942
  }
2931
- function setFetcherError(key, routeId, error) {
2943
+ function updateFetcherState(key, fetcher, opts) {
2944
+ if (opts === void 0) {
2945
+ opts = {};
2946
+ }
2947
+ state.fetchers.set(key, fetcher);
2948
+ updateState({
2949
+ fetchers: new Map(state.fetchers)
2950
+ }, {
2951
+ flushSync: (opts && opts.flushSync) === true
2952
+ });
2953
+ }
2954
+ function setFetcherError(key, routeId, error, opts) {
2955
+ if (opts === void 0) {
2956
+ opts = {};
2957
+ }
2932
2958
  let boundaryMatch = findNearestBoundary(state.matches, routeId);
2933
2959
  deleteFetcher(key);
2934
2960
  updateState({
@@ -2936,8 +2962,21 @@
2936
2962
  [boundaryMatch.route.id]: error
2937
2963
  },
2938
2964
  fetchers: new Map(state.fetchers)
2965
+ }, {
2966
+ flushSync: (opts && opts.flushSync) === true
2939
2967
  });
2940
2968
  }
2969
+ function getFetcher(key) {
2970
+ if (future.v7_fetcherPersist) {
2971
+ activeFetchers.set(key, (activeFetchers.get(key) || 0) + 1);
2972
+ // If this fetcher was previously marked for deletion, unmark it since we
2973
+ // have a new instance
2974
+ if (deletedFetchers.has(key)) {
2975
+ deletedFetchers.delete(key);
2976
+ }
2977
+ }
2978
+ return state.fetchers.get(key) || IDLE_FETCHER;
2979
+ }
2941
2980
  function deleteFetcher(key) {
2942
2981
  let fetcher = state.fetchers.get(key);
2943
2982
  // Don't abort the controller if this is a deletion of a fetcher.submit()
@@ -3218,10 +3257,10 @@
3218
3257
  * propagate that out and return the raw Response so the HTTP server can
3219
3258
  * return it directly.
3220
3259
  */
3221
- async function query(request, _temp2) {
3260
+ async function query(request, _temp3) {
3222
3261
  let {
3223
3262
  requestContext
3224
- } = _temp2 === void 0 ? {} : _temp2;
3263
+ } = _temp3 === void 0 ? {} : _temp3;
3225
3264
  let url = new URL(request.url);
3226
3265
  let method = request.method;
3227
3266
  let location = createLocation("", createPath(url), null, "default");
@@ -3307,11 +3346,11 @@
3307
3346
  * code. Examples here are 404 and 405 errors that occur prior to reaching
3308
3347
  * any user-defined loaders.
3309
3348
  */
3310
- async function queryRoute(request, _temp3) {
3349
+ async function queryRoute(request, _temp4) {
3311
3350
  let {
3312
3351
  routeId,
3313
3352
  requestContext
3314
- } = _temp3 === void 0 ? {} : _temp3;
3353
+ } = _temp4 === void 0 ? {} : _temp4;
3315
3354
  let url = new URL(request.url);
3316
3355
  let method = request.method;
3317
3356
  let location = createLocation("", createPath(url), null, "default");
@@ -3589,11 +3628,9 @@
3589
3628
  function normalizeTo(location, matches, basename, prependBasename, to, fromRouteId, relative) {
3590
3629
  let contextualMatches;
3591
3630
  let activeRouteMatch;
3592
- if (fromRouteId != null && relative !== "path") {
3631
+ if (fromRouteId) {
3593
3632
  // Grab matches up to the calling route so our route-relative logic is
3594
- // relative to the correct source route. When using relative:path,
3595
- // fromRouteId is ignored since that is always relative to the current
3596
- // location path
3633
+ // relative to the correct source route
3597
3634
  contextualMatches = [];
3598
3635
  for (let match of matches) {
3599
3636
  contextualMatches.push(match);
@@ -4360,13 +4397,13 @@
4360
4397
  route
4361
4398
  };
4362
4399
  }
4363
- function getInternalRouterError(status, _temp4) {
4400
+ function getInternalRouterError(status, _temp5) {
4364
4401
  let {
4365
4402
  pathname,
4366
4403
  routeId,
4367
4404
  method,
4368
4405
  type
4369
- } = _temp4 === void 0 ? {} : _temp4;
4406
+ } = _temp5 === void 0 ? {} : _temp5;
4370
4407
  let statusText = "Unknown Server Error";
4371
4408
  let errorMessage = "Unknown @remix-run/router error";
4372
4409
  if (status === 400) {