@remix-run/router 0.0.0-experimental-9463fb5e → 0.0.0-experimental-d90c8fb3
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 +199 -8
- package/dist/router.cjs.js +55 -64
- package/dist/router.cjs.js.map +1 -1
- package/dist/router.d.ts +0 -1
- package/dist/router.js +55 -62
- package/dist/router.js.map +1 -1
- package/dist/router.umd.js +55 -64
- 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/package.json +1 -1
- package/router.ts +54 -61
- package/utils.ts +23 -34
package/CHANGELOG.md
CHANGED
|
@@ -1,10 +1,206 @@
|
|
|
1
1
|
# `@remix-run/router`
|
|
2
2
|
|
|
3
|
+
## 1.14.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Fix bug where dashes were not picked up in dynamic parameter names ([#11160](https://github.com/remix-run/react-router/pull/11160))
|
|
8
|
+
- Do not attempt to deserialize empty JSON responses ([#11164](https://github.com/remix-run/react-router/pull/11164))
|
|
9
|
+
|
|
10
|
+
## 1.14.1
|
|
11
|
+
|
|
12
|
+
### Patch Changes
|
|
13
|
+
|
|
14
|
+
- Fix bug with `route.lazy` not working correctly on initial SPA load when `v7_partialHydration` is specified ([#11121](https://github.com/remix-run/react-router/pull/11121))
|
|
15
|
+
- Fix bug preventing revalidation from occurring for persisted fetchers unmounted during the `submitting` phase ([#11102](https://github.com/remix-run/react-router/pull/11102))
|
|
16
|
+
- De-dup relative path logic in `resolveTo` ([#11097](https://github.com/remix-run/react-router/pull/11097))
|
|
17
|
+
|
|
18
|
+
## 1.14.0
|
|
19
|
+
|
|
20
|
+
### Minor Changes
|
|
21
|
+
|
|
22
|
+
- Added a new `future.v7_partialHydration` future flag that enables partial hydration of a data router when Server-Side Rendering. This allows you to provide `hydrationData.loaderData` that has values for _some_ initially matched route loaders, but not all. When this flag is enabled, the router will call `loader` functions for routes that do not have hydration loader data during `router.initialize()`, and it will render down to the deepest provided `HydrateFallback` (up to the first route without hydration data) while it executes the unhydrated routes. ([#11033](https://github.com/remix-run/react-router/pull/11033))
|
|
23
|
+
|
|
24
|
+
For example, the following router has a `root` and `index` route, but only provided `hydrationData.loaderData` for the `root` route. Because the `index` route has a `loader`, we need to run that during initialization. With `future.v7_partialHydration` specified, `<RouterProvider>` will render the `RootComponent` (because it has data) and then the `IndexFallback` (since it does not have data). Once `indexLoader` finishes, application will update and display `IndexComponent`.
|
|
25
|
+
|
|
26
|
+
```jsx
|
|
27
|
+
let router = createBrowserRouter(
|
|
28
|
+
[
|
|
29
|
+
{
|
|
30
|
+
id: "root",
|
|
31
|
+
path: "/",
|
|
32
|
+
loader: rootLoader,
|
|
33
|
+
Component: RootComponent,
|
|
34
|
+
Fallback: RootFallback,
|
|
35
|
+
children: [
|
|
36
|
+
{
|
|
37
|
+
id: "index",
|
|
38
|
+
index: true,
|
|
39
|
+
loader: indexLoader,
|
|
40
|
+
Component: IndexComponent,
|
|
41
|
+
HydrateFallback: IndexFallback,
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
{
|
|
47
|
+
future: {
|
|
48
|
+
v7_partialHydration: true,
|
|
49
|
+
},
|
|
50
|
+
hydrationData: {
|
|
51
|
+
loaderData: {
|
|
52
|
+
root: { message: "Hydrated from Root!" },
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
}
|
|
56
|
+
);
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
If the above example did not have an `IndexFallback`, then `RouterProvider` would instead render the `RootFallback` while it executed the `indexLoader`.
|
|
60
|
+
|
|
61
|
+
**Note:** When `future.v7_partialHydration` is provided, the `<RouterProvider fallbackElement>` prop is ignored since you can move it to a `Fallback` on your top-most route. The `fallbackElement` prop will be removed in React Router v7 when `v7_partialHydration` behavior becomes the standard behavior.
|
|
62
|
+
|
|
63
|
+
- Add a new `future.v7_relativeSplatPath` flag to implement a breaking bug fix to relative routing when inside a splat route. ([#11087](https://github.com/remix-run/react-router/pull/11087))
|
|
64
|
+
|
|
65
|
+
This fix was originally added in [#10983](https://github.com/remix-run/react-router/issues/10983) and was later reverted in [#11078](https://github.com/remix-run/react-router/pull/11078) because it was determined that a large number of existing applications were relying on the buggy behavior (see [#11052](https://github.com/remix-run/react-router/issues/11052))
|
|
66
|
+
|
|
67
|
+
**The Bug**
|
|
68
|
+
The buggy behavior is that without this flag, the default behavior when resolving relative paths is to _ignore_ any splat (`*`) portion of the current route path.
|
|
69
|
+
|
|
70
|
+
**The Background**
|
|
71
|
+
This decision was originally made thinking that it would make the concept of nested different sections of your apps in `<Routes>` easier if relative routing would _replace_ the current splat:
|
|
72
|
+
|
|
73
|
+
```jsx
|
|
74
|
+
<BrowserRouter>
|
|
75
|
+
<Routes>
|
|
76
|
+
<Route path="/" element={<Home />} />
|
|
77
|
+
<Route path="dashboard/*" element={<Dashboard />} />
|
|
78
|
+
</Routes>
|
|
79
|
+
</BrowserRouter>
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Any paths like `/dashboard`, `/dashboard/team`, `/dashboard/projects` will match the `Dashboard` route. The dashboard component itself can then render nested `<Routes>`:
|
|
83
|
+
|
|
84
|
+
```jsx
|
|
85
|
+
function Dashboard() {
|
|
86
|
+
return (
|
|
87
|
+
<div>
|
|
88
|
+
<h2>Dashboard</h2>
|
|
89
|
+
<nav>
|
|
90
|
+
<Link to="/">Dashboard Home</Link>
|
|
91
|
+
<Link to="team">Team</Link>
|
|
92
|
+
<Link to="projects">Projects</Link>
|
|
93
|
+
</nav>
|
|
94
|
+
|
|
95
|
+
<Routes>
|
|
96
|
+
<Route path="/" element={<DashboardHome />} />
|
|
97
|
+
<Route path="team" element={<DashboardTeam />} />
|
|
98
|
+
<Route path="projects" element={<DashboardProjects />} />
|
|
99
|
+
</Routes>
|
|
100
|
+
</div>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Now, all links and route paths are relative to the router above them. This makes code splitting and compartmentalizing your app really easy. You could render the `Dashboard` as its own independent app, or embed it into your large app without making any changes to it.
|
|
106
|
+
|
|
107
|
+
**The Problem**
|
|
108
|
+
|
|
109
|
+
The problem is that this concept of ignoring part of a path breaks a lot of other assumptions in React Router - namely that `"."` always means the current location pathname for that route. When we ignore the splat portion, we start getting invalid paths when using `"."`:
|
|
110
|
+
|
|
111
|
+
```jsx
|
|
112
|
+
// If we are on URL /dashboard/team, and we want to link to /dashboard/team:
|
|
113
|
+
function DashboardTeam() {
|
|
114
|
+
// ❌ This is broken and results in <a href="/dashboard">
|
|
115
|
+
return <Link to=".">A broken link to the Current URL</Link>;
|
|
116
|
+
|
|
117
|
+
// ✅ This is fixed but super unintuitive since we're already at /dashboard/team!
|
|
118
|
+
return <Link to="./team">A broken link to the Current URL</Link>;
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
We've also introduced an issue that we can no longer move our `DashboardTeam` component around our route hierarchy easily - since it behaves differently if we're underneath a non-splat route, such as `/dashboard/:widget`. Now, our `"."` links will, properly point to ourself _inclusive of the dynamic param value_ so behavior will break from it's corresponding usage in a `/dashboard/*` route.
|
|
123
|
+
|
|
124
|
+
Even worse, consider a nested splat route configuration:
|
|
125
|
+
|
|
126
|
+
```jsx
|
|
127
|
+
<BrowserRouter>
|
|
128
|
+
<Routes>
|
|
129
|
+
<Route path="dashboard">
|
|
130
|
+
<Route path="*" element={<Dashboard />} />
|
|
131
|
+
</Route>
|
|
132
|
+
</Routes>
|
|
133
|
+
</BrowserRouter>
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Now, a `<Link to=".">` and a `<Link to="..">` inside the `Dashboard` component go to the same place! That is definitely not correct!
|
|
137
|
+
|
|
138
|
+
Another common issue arose in Data Routers (and Remix) where any `<Form>` should post to it's own route `action` if you the user doesn't specify a form action:
|
|
139
|
+
|
|
140
|
+
```jsx
|
|
141
|
+
let router = createBrowserRouter({
|
|
142
|
+
path: "/dashboard",
|
|
143
|
+
children: [
|
|
144
|
+
{
|
|
145
|
+
path: "*",
|
|
146
|
+
action: dashboardAction,
|
|
147
|
+
Component() {
|
|
148
|
+
// ❌ This form is broken! It throws a 405 error when it submits because
|
|
149
|
+
// it tries to submit to /dashboard (without the splat value) and the parent
|
|
150
|
+
// `/dashboard` route doesn't have an action
|
|
151
|
+
return <Form method="post">...</Form>;
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
],
|
|
155
|
+
});
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
This is just a compounded issue from the above because the default location for a `Form` to submit to is itself (`"."`) - and if we ignore the splat portion, that now resolves to the parent route.
|
|
159
|
+
|
|
160
|
+
**The Solution**
|
|
161
|
+
If you are leveraging this behavior, it's recommended to enable the future flag, move your splat to it's own route, and leverage `../` for any links to "sibling" pages:
|
|
162
|
+
|
|
163
|
+
```jsx
|
|
164
|
+
<BrowserRouter>
|
|
165
|
+
<Routes>
|
|
166
|
+
<Route path="dashboard">
|
|
167
|
+
<Route index path="*" element={<Dashboard />} />
|
|
168
|
+
</Route>
|
|
169
|
+
</Routes>
|
|
170
|
+
</BrowserRouter>
|
|
171
|
+
|
|
172
|
+
function Dashboard() {
|
|
173
|
+
return (
|
|
174
|
+
<div>
|
|
175
|
+
<h2>Dashboard</h2>
|
|
176
|
+
<nav>
|
|
177
|
+
<Link to="..">Dashboard Home</Link>
|
|
178
|
+
<Link to="../team">Team</Link>
|
|
179
|
+
<Link to="../projects">Projects</Link>
|
|
180
|
+
</nav>
|
|
181
|
+
|
|
182
|
+
<Routes>
|
|
183
|
+
<Route path="/" element={<DashboardHome />} />
|
|
184
|
+
<Route path="team" element={<DashboardTeam />} />
|
|
185
|
+
<Route path="projects" element={<DashboardProjects />} />
|
|
186
|
+
</Router>
|
|
187
|
+
</div>
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
This way, `.` means "the full current pathname for my route" in all cases (including static, dynamic, and splat routes) and `..` always means "my parents pathname".
|
|
193
|
+
|
|
194
|
+
### Patch Changes
|
|
195
|
+
|
|
196
|
+
- Catch and bubble errors thrown when trying to unwrap responses from `loader`/`action` functions ([#11061](https://github.com/remix-run/react-router/pull/11061))
|
|
197
|
+
- Fix `relative="path"` issue when rendering `Link`/`NavLink` outside of matched routes ([#11062](https://github.com/remix-run/react-router/pull/11062))
|
|
198
|
+
|
|
3
199
|
## 1.13.1
|
|
4
200
|
|
|
5
201
|
### Patch Changes
|
|
6
202
|
|
|
7
|
-
- Revert the `useResolvedPath` fix for splat routes due to a large number of applications that were relying on the buggy behavior (see https://github.com/remix-run/react-router/issues/11052#issuecomment-1836589329). We plan to re-introduce this fix behind a future flag in the next minor version. ([#11078](https://github.com/remix-run/react-router/pull/11078))
|
|
203
|
+
- Revert the `useResolvedPath` fix for splat routes due to a large number of applications that were relying on the buggy behavior (see <https://github.com/remix-run/react-router/issues/11052#issuecomment-1836589329>). We plan to re-introduce this fix behind a future flag in the next minor version. ([#11078](https://github.com/remix-run/react-router/pull/11078))
|
|
8
204
|
|
|
9
205
|
## 1.13.0
|
|
10
206
|
|
|
@@ -484,11 +680,6 @@ function Comp() {
|
|
|
484
680
|
|
|
485
681
|
This is the first stable release of `@remix-run/router`, which provides all the underlying routing and data loading/mutation logic for `react-router`. You should _not_ be using this package directly unless you are authoring a routing library similar to `react-router`.
|
|
486
682
|
|
|
487
|
-
For an overview of the features provided by `react-router`, we recommend you go check out the [docs]
|
|
488
|
-
|
|
489
|
-
For an overview of the features provided by `@remix-run/router`, please check out the [`README`][remix-router-readme].
|
|
683
|
+
For an overview of the features provided by `react-router`, we recommend you go check out the [docs](https://reactrouter.com), especially the [feature overview](https://reactrouter.com/start/overview) and the [tutorial](https://reactrouter.com/start/tutorial).
|
|
490
684
|
|
|
491
|
-
[
|
|
492
|
-
[rr-feature-overview]: https://reactrouter.com/start/overview
|
|
493
|
-
[rr-tutorial]: https://reactrouter.com/start/tutorial
|
|
494
|
-
[remix-router-readme]: https://github.com/remix-run/react-router/blob/main/packages/router/README.md
|
|
685
|
+
For an overview of the features provided by `@remix-run/router`, please check out the [`README`](./README.md).
|
package/dist/router.cjs.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @remix-run/router v0.0.0-experimental-
|
|
2
|
+
* @remix-run/router v0.0.0-experimental-d90c8fb3
|
|
3
3
|
*
|
|
4
4
|
* Copyright (c) Remix Software Inc.
|
|
5
5
|
*
|
|
@@ -752,14 +752,14 @@ function matchRoutes(routes, locationArg, basename) {
|
|
|
752
752
|
rankRouteBranches(branches);
|
|
753
753
|
let matches = null;
|
|
754
754
|
for (let i = 0; matches == null && i < branches.length; ++i) {
|
|
755
|
-
matches = matchRouteBranch(branches[i],
|
|
756
755
|
// Incoming pathnames are generally encoded from either window.location
|
|
757
756
|
// or from router.navigate, but we want to match against the unencoded
|
|
758
757
|
// paths in the route definitions. Memory router locations won't be
|
|
759
758
|
// encoded here but there also shouldn't be anything to decode so this
|
|
760
759
|
// should be a safe operation. This avoids needing matchRoutes to be
|
|
761
760
|
// history-aware.
|
|
762
|
-
|
|
761
|
+
let decoded = decodePath(pathname);
|
|
762
|
+
matches = matchRouteBranch(branches[i], decoded);
|
|
763
763
|
}
|
|
764
764
|
return matches;
|
|
765
765
|
}
|
|
@@ -889,7 +889,7 @@ function rankRouteBranches(branches) {
|
|
|
889
889
|
branches.sort((a, b) => a.score !== b.score ? b.score - a.score // Higher score first
|
|
890
890
|
: compareIndexes(a.routesMeta.map(meta => meta.childrenIndex), b.routesMeta.map(meta => meta.childrenIndex)));
|
|
891
891
|
}
|
|
892
|
-
const paramRe =
|
|
892
|
+
const paramRe = /^:[\w-]+$/;
|
|
893
893
|
const dynamicSegmentValue = 3;
|
|
894
894
|
const indexRouteValue = 2;
|
|
895
895
|
const emptySegmentValue = 1;
|
|
@@ -979,7 +979,7 @@ function generatePath(originalPath, params) {
|
|
|
979
979
|
// Apply the splat
|
|
980
980
|
return stringify(params[star]);
|
|
981
981
|
}
|
|
982
|
-
const keyMatch = segment.match(/^:(\w+)(\??)$/);
|
|
982
|
+
const keyMatch = segment.match(/^:([\w-]+)(\??)$/);
|
|
983
983
|
if (keyMatch) {
|
|
984
984
|
const [, key, optional] = keyMatch;
|
|
985
985
|
let param = params[key];
|
|
@@ -1038,7 +1038,7 @@ function matchPath(pattern, pathname) {
|
|
|
1038
1038
|
if (isOptional && !value) {
|
|
1039
1039
|
memo[paramName] = undefined;
|
|
1040
1040
|
} else {
|
|
1041
|
-
memo[paramName] =
|
|
1041
|
+
memo[paramName] = (value || "").replace(/%2F/g, "/");
|
|
1042
1042
|
}
|
|
1043
1043
|
return memo;
|
|
1044
1044
|
}, {});
|
|
@@ -1061,7 +1061,7 @@ function compilePath(path, caseSensitive, end) {
|
|
|
1061
1061
|
let regexpSource = "^" + path.replace(/\/*\*?$/, "") // Ignore trailing / and /*, we'll handle it below
|
|
1062
1062
|
.replace(/^\/*/, "/") // Make sure it has a leading /
|
|
1063
1063
|
.replace(/[\\.*+^${}|()[\]]/g, "\\$&") // Escape special regex chars
|
|
1064
|
-
.replace(/\/:(\w+)(\?)?/g, (_, paramName, isOptional) => {
|
|
1064
|
+
.replace(/\/:([\w-]+)(\?)?/g, (_, paramName, isOptional) => {
|
|
1065
1065
|
params.push({
|
|
1066
1066
|
paramName,
|
|
1067
1067
|
isOptional: isOptional != null
|
|
@@ -1090,22 +1090,14 @@ function compilePath(path, caseSensitive, end) {
|
|
|
1090
1090
|
let matcher = new RegExp(regexpSource, caseSensitive ? undefined : "i");
|
|
1091
1091
|
return [matcher, params];
|
|
1092
1092
|
}
|
|
1093
|
-
function
|
|
1093
|
+
function decodePath(value) {
|
|
1094
1094
|
try {
|
|
1095
|
-
return
|
|
1095
|
+
return value.split("/").map(v => decodeURIComponent(v).replace(/\//g, "%2F")).join("/");
|
|
1096
1096
|
} catch (error) {
|
|
1097
1097
|
warning(false, "The URL path \"" + value + "\" could not be decoded because it is is a " + "malformed URL segment. This is probably due to a bad percent " + ("encoding (" + error + ")."));
|
|
1098
1098
|
return value;
|
|
1099
1099
|
}
|
|
1100
1100
|
}
|
|
1101
|
-
function safelyDecodeURIComponent(value, paramName) {
|
|
1102
|
-
try {
|
|
1103
|
-
return decodeURIComponent(value);
|
|
1104
|
-
} catch (error) {
|
|
1105
|
-
warning(false, "The value for the URL param \"" + paramName + "\" will not be decoded because" + (" the string \"" + value + "\" is a malformed URL segment. This is probably") + (" due to a bad percent encoding (" + error + ")."));
|
|
1106
|
-
return value;
|
|
1107
|
-
}
|
|
1108
|
-
}
|
|
1109
1101
|
|
|
1110
1102
|
/**
|
|
1111
1103
|
* @private
|
|
@@ -1715,18 +1707,28 @@ function createRouter(init) {
|
|
|
1715
1707
|
[route.id]: error
|
|
1716
1708
|
};
|
|
1717
1709
|
}
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1710
|
+
let initialized;
|
|
1711
|
+
let hasLazyRoutes = initialMatches.some(m => m.route.lazy);
|
|
1712
|
+
let hasLoaders = initialMatches.some(m => m.route.loader);
|
|
1713
|
+
if (hasLazyRoutes) {
|
|
1714
|
+
// All initialMatches need to be loaded before we're ready. If we have lazy
|
|
1715
|
+
// functions around still then we'll need to run them in initialize()
|
|
1716
|
+
initialized = false;
|
|
1717
|
+
} else if (!hasLoaders) {
|
|
1718
|
+
// If we've got no loaders to run, then we're good to go
|
|
1719
|
+
initialized = true;
|
|
1720
|
+
} else if (future.v7_partialHydration) {
|
|
1721
|
+
// If partial hydration is enabled, we're initialized so long as we were
|
|
1722
|
+
// provided with hydrationData for every route with a loader, and no loaders
|
|
1723
|
+
// were marked for explicit hydration
|
|
1724
|
+
let loaderData = init.hydrationData ? init.hydrationData.loaderData : null;
|
|
1725
|
+
let errors = init.hydrationData ? init.hydrationData.errors : null;
|
|
1726
|
+
initialized = initialMatches.every(m => m.route.loader && m.route.loader.hydrate !== true && (loaderData && loaderData[m.route.id] !== undefined || errors && errors[m.route.id] !== undefined));
|
|
1727
|
+
} else {
|
|
1728
|
+
// Without partial hydration - we're initialized if we were provided any
|
|
1729
|
+
// hydrationData - which is expected to be complete
|
|
1730
|
+
initialized = init.hydrationData != null;
|
|
1731
|
+
}
|
|
1730
1732
|
let router;
|
|
1731
1733
|
let state = {
|
|
1732
1734
|
historyAction: init.history.action,
|
|
@@ -1893,7 +1895,7 @@ function createRouter(init) {
|
|
|
1893
1895
|
// in the normal navigation flow. For SSR it's expected that lazy modules are
|
|
1894
1896
|
// resolved prior to router creation since we can't go into a fallbackElement
|
|
1895
1897
|
// UI for SSR'd apps
|
|
1896
|
-
if (!state.initialized
|
|
1898
|
+
if (!state.initialized) {
|
|
1897
1899
|
startNavigation(Action.Pop, state.location, {
|
|
1898
1900
|
initialHydration: true
|
|
1899
1901
|
});
|
|
@@ -3265,8 +3267,7 @@ function createStaticHandler(routes, opts) {
|
|
|
3265
3267
|
}
|
|
3266
3268
|
// Config driven behavior flags
|
|
3267
3269
|
let future = _extends({
|
|
3268
|
-
v7_relativeSplatPath: false
|
|
3269
|
-
v7_throwAbortReason: false
|
|
3270
|
+
v7_relativeSplatPath: false
|
|
3270
3271
|
}, opts ? opts.future : null);
|
|
3271
3272
|
let dataRoutes = convertRoutesToDataRoutes(routes, mapRouteProperties, undefined, manifest);
|
|
3272
3273
|
|
|
@@ -3489,7 +3490,8 @@ function createStaticHandler(routes, opts) {
|
|
|
3489
3490
|
requestContext
|
|
3490
3491
|
});
|
|
3491
3492
|
if (request.signal.aborted) {
|
|
3492
|
-
|
|
3493
|
+
let method = isRouteRequest ? "queryRoute" : "query";
|
|
3494
|
+
throw new Error(method + "() call aborted: " + request.method + " " + request.url);
|
|
3493
3495
|
}
|
|
3494
3496
|
}
|
|
3495
3497
|
if (isRedirectResult(result)) {
|
|
@@ -3607,7 +3609,8 @@ function createStaticHandler(routes, opts) {
|
|
|
3607
3609
|
requestContext
|
|
3608
3610
|
}))]);
|
|
3609
3611
|
if (request.signal.aborted) {
|
|
3610
|
-
|
|
3612
|
+
let method = isRouteRequest ? "queryRoute" : "query";
|
|
3613
|
+
throw new Error(method + "() call aborted: " + request.method + " " + request.url);
|
|
3611
3614
|
}
|
|
3612
3615
|
|
|
3613
3616
|
// Process and commit output from loaders
|
|
@@ -3652,14 +3655,6 @@ function getStaticContextFromError(routes, context, error) {
|
|
|
3652
3655
|
});
|
|
3653
3656
|
return newContext;
|
|
3654
3657
|
}
|
|
3655
|
-
function throwStaticHandlerAbortedError(request, isRouteRequest, future) {
|
|
3656
|
-
let method = isRouteRequest ? "queryRoute" : "query";
|
|
3657
|
-
if (future.v7_throwAbortReason && request.signal.reason !== undefined) {
|
|
3658
|
-
throw request.signal.reason;
|
|
3659
|
-
} else {
|
|
3660
|
-
throw new Error(method + "() call aborted: " + request.method + " " + request.url);
|
|
3661
|
-
}
|
|
3662
|
-
}
|
|
3663
3658
|
function isSubmissionNavigation(opts) {
|
|
3664
3659
|
return opts != null && ("formData" in opts && opts.formData != null || "body" in opts && opts.body !== undefined);
|
|
3665
3660
|
}
|
|
@@ -3856,18 +3851,24 @@ function getMatchesToLoad(history, state, matches, submission, location, isIniti
|
|
|
3856
3851
|
let boundaryId = pendingError ? Object.keys(pendingError)[0] : undefined;
|
|
3857
3852
|
let boundaryMatches = getLoaderMatchesUntilBoundary(matches, boundaryId);
|
|
3858
3853
|
let navigationMatches = boundaryMatches.filter((match, index) => {
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
}
|
|
3864
|
-
if (match.route.lazy) {
|
|
3854
|
+
let {
|
|
3855
|
+
route
|
|
3856
|
+
} = match;
|
|
3857
|
+
if (route.lazy) {
|
|
3865
3858
|
// We haven't loaded this route yet so we don't know if it's got a loader!
|
|
3866
3859
|
return true;
|
|
3867
3860
|
}
|
|
3868
|
-
if (
|
|
3861
|
+
if (route.loader == null) {
|
|
3869
3862
|
return false;
|
|
3870
3863
|
}
|
|
3864
|
+
if (isInitialLoad) {
|
|
3865
|
+
if (route.loader.hydrate) {
|
|
3866
|
+
return true;
|
|
3867
|
+
}
|
|
3868
|
+
return state.loaderData[route.id] === undefined && (
|
|
3869
|
+
// Don't re-run if the loader ran and threw an error
|
|
3870
|
+
!state.errors || state.errors[route.id] === undefined);
|
|
3871
|
+
}
|
|
3871
3872
|
|
|
3872
3873
|
// Always call the loader on new route instances and pending defer cancellations
|
|
3873
3874
|
if (isNewLoader(state.loaderData, state.matches[index], match) || cancelledDeferredRoutes.some(id => id === match.route.id)) {
|
|
@@ -3969,20 +3970,6 @@ function getMatchesToLoad(history, state, matches, submission, location, isIniti
|
|
|
3969
3970
|
});
|
|
3970
3971
|
return [navigationMatches, revalidatingFetchers];
|
|
3971
3972
|
}
|
|
3972
|
-
|
|
3973
|
-
// Is this route unhydrated (when v7_partialHydration=true) such that we need
|
|
3974
|
-
// to call it's loader on the initial router creation
|
|
3975
|
-
function isUnhydratedRoute(state, route) {
|
|
3976
|
-
if (!route.loader) {
|
|
3977
|
-
return false;
|
|
3978
|
-
}
|
|
3979
|
-
if (route.loader.hydrate) {
|
|
3980
|
-
return true;
|
|
3981
|
-
}
|
|
3982
|
-
return state.loaderData[route.id] === undefined && (!state.errors ||
|
|
3983
|
-
// Loader ran but errored - don't re-run
|
|
3984
|
-
state.errors[route.id] === undefined);
|
|
3985
|
-
}
|
|
3986
3973
|
function isNewLoader(currentLoaderData, currentMatch, match) {
|
|
3987
3974
|
let isNew =
|
|
3988
3975
|
// [a] -> [a, b]
|
|
@@ -4205,7 +4192,11 @@ async function callLoaderOrAction(type, request, match, matches, manifest, mapRo
|
|
|
4205
4192
|
// Check between word boundaries instead of startsWith() due to the last
|
|
4206
4193
|
// paragraph of https://httpwg.org/specs/rfc9110.html#field.content-type
|
|
4207
4194
|
if (contentType && /\bapplication\/json\b/.test(contentType)) {
|
|
4208
|
-
|
|
4195
|
+
if (result.body == null) {
|
|
4196
|
+
data = null;
|
|
4197
|
+
} else {
|
|
4198
|
+
data = await result.json();
|
|
4199
|
+
}
|
|
4209
4200
|
} else {
|
|
4210
4201
|
data = await result.text();
|
|
4211
4202
|
}
|