react-router-dom 6.13.0 → 6.14.0-pre.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +70 -0
- package/dist/dom.d.ts +16 -8
- package/dist/index.d.ts +15 -7
- package/dist/index.js +170 -90
- package/dist/index.js.map +1 -1
- package/dist/main.js +1 -1
- package/dist/react-router-dom.development.js +177 -92
- package/dist/react-router-dom.development.js.map +1 -1
- package/dist/react-router-dom.production.min.js +2 -2
- package/dist/react-router-dom.production.min.js.map +1 -1
- package/dist/server.d.ts +1 -1
- package/dist/umd/react-router-dom.development.js +178 -91
- package/dist/umd/react-router-dom.development.js.map +1 -1
- package/dist/umd/react-router-dom.production.min.js +2 -2
- package/dist/umd/react-router-dom.production.min.js.map +1 -1
- package/package.json +3 -3
- package/server.d.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,75 @@
|
|
|
1
1
|
# `react-router-dom`
|
|
2
2
|
|
|
3
|
+
## 6.14.0-pre.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- (Remove) Fix FormData submitter feature detection check ([#10627](https://github.com/remix-run/react-router/pull/10627))
|
|
8
|
+
- Updated dependencies:
|
|
9
|
+
- `react-router@6.14.0-pre.1`
|
|
10
|
+
|
|
11
|
+
## 6.14.0-pre.0
|
|
12
|
+
|
|
13
|
+
### Minor Changes
|
|
14
|
+
|
|
15
|
+
- Add support for `application/json` and `text/plain` encodings for `useSubmit`/`fetcher.submit`. To reflect these additional types, `useNavigation`/`useFetcher` now also contain `navigation.json`/`navigation.text` and `fetcher.json`/`fetcher.text` which include the json/text submission if applicable. ([#10413](https://github.com/remix-run/react-router/pull/10413))
|
|
16
|
+
|
|
17
|
+
```jsx
|
|
18
|
+
// The default behavior will still serialize as FormData
|
|
19
|
+
function Component() {
|
|
20
|
+
let navigation = useNavigation();
|
|
21
|
+
let submit = useSubmit();
|
|
22
|
+
submit({ key: "value" });
|
|
23
|
+
// navigation.formEncType => "application/x-www-form-urlencoded"
|
|
24
|
+
// navigation.formData => FormData instance
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function action({ request }) {
|
|
28
|
+
// request.headers.get("Content-Type") => "application/x-www-form-urlencoded"
|
|
29
|
+
// await request.formData() => FormData instance
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
```js
|
|
34
|
+
// Opt-into JSON encoding with `encType: "application/json"`
|
|
35
|
+
function Component() {
|
|
36
|
+
let submit = useSubmit();
|
|
37
|
+
submit({ key: "value" }, { encType: "application/json" });
|
|
38
|
+
// navigation.formEncType => "application/json"
|
|
39
|
+
// navigation.json => { key: "value" }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function action({ request }) {
|
|
43
|
+
// request.headers.get("Content-Type") => "application/json"
|
|
44
|
+
// await request.json => { key: "value" }
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
```js
|
|
49
|
+
// Opt-into JSON encoding with `encType: "application/json"`
|
|
50
|
+
function Component() {
|
|
51
|
+
let submit = useSubmit();
|
|
52
|
+
submit("Text submission", { encType: "text/plain" });
|
|
53
|
+
// navigation.formEncType => "text/plain"
|
|
54
|
+
// navigation.text => "Text submission"
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function action({ request }) {
|
|
58
|
+
// request.headers.get("Content-Type") => "text/plain"
|
|
59
|
+
// await request.text() => "Text submission"
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Patch Changes
|
|
64
|
+
|
|
65
|
+
- When submitting a form from a `submitter` element, prefer the built-in `new FormData(form, submitter)` instead of the previous manual approach in modern browsers (those that support the new `submitter` parameter). For browsers that don't support it, we continue to just append the submit button's entry to the end, and we also add rudimentary support for `type="image"` buttons. If developers want full spec-compliant support for legacy browsers, they can use the `formdata-submitter-polyfill`. ([#9865](https://github.com/remix-run/react-router/pull/9865))
|
|
66
|
+
- upgrade `typescript` to 5.1 ([#10581](https://github.com/remix-run/react-router/pull/10581))
|
|
67
|
+
- Call `window.history.pushState/replaceState` before updating React Router state (instead of after) so that `window.location` matches `useLocation` during synchronous React 17 rendering. However, generally apps should not be relying on `window.location` and should always reference `useLocation` when possible, as `window.location` will not be in sync 100% of the time (due to `popstate` events, concurrent mode, etc.) ([#10211](https://github.com/remix-run/react-router/pull/10211))
|
|
68
|
+
- Fix `tsc --skipLibCheck:false` issues on React 17 ([#10622](https://github.com/remix-run/react-router/pull/10622))
|
|
69
|
+
- Updated dependencies:
|
|
70
|
+
- `react-router@6.14.0-pre.0`
|
|
71
|
+
- `@remix-run/router@1.7.0-pre.0`
|
|
72
|
+
|
|
3
73
|
## 6.13.0
|
|
4
74
|
|
|
5
75
|
### Minor Changes
|
package/dist/dom.d.ts
CHANGED
|
@@ -4,10 +4,10 @@ export declare function isHtmlElement(object: any): object is HTMLElement;
|
|
|
4
4
|
export declare function isButtonElement(object: any): object is HTMLButtonElement;
|
|
5
5
|
export declare function isFormElement(object: any): object is HTMLFormElement;
|
|
6
6
|
export declare function isInputElement(object: any): object is HTMLInputElement;
|
|
7
|
-
|
|
7
|
+
type LimitedMouseEvent = Pick<MouseEvent, "button" | "metaKey" | "altKey" | "ctrlKey" | "shiftKey">;
|
|
8
8
|
export declare function shouldProcessLinkClick(event: LimitedMouseEvent, target?: string): boolean;
|
|
9
|
-
export
|
|
10
|
-
export
|
|
9
|
+
export type ParamKeyValuePair = [string, string];
|
|
10
|
+
export type URLSearchParamsInit = string | ParamKeyValuePair[] | Record<string, string | string[]> | URLSearchParams;
|
|
11
11
|
/**
|
|
12
12
|
* Creates a URLSearchParams object using the given initializer.
|
|
13
13
|
*
|
|
@@ -31,6 +31,15 @@ export declare type URLSearchParamsInit = string | ParamKeyValuePair[] | Record<
|
|
|
31
31
|
*/
|
|
32
32
|
export declare function createSearchParams(init?: URLSearchParamsInit): URLSearchParams;
|
|
33
33
|
export declare function getSearchParamsForLocation(locationSearch: string, defaultSearchParams: URLSearchParams | null): URLSearchParams;
|
|
34
|
+
type JsonObject = {
|
|
35
|
+
[Key in string]: JsonValue;
|
|
36
|
+
} & {
|
|
37
|
+
[Key in string]?: JsonValue | undefined;
|
|
38
|
+
};
|
|
39
|
+
type JsonArray = JsonValue[] | readonly JsonValue[];
|
|
40
|
+
type JsonPrimitive = string | number | boolean | null;
|
|
41
|
+
type JsonValue = JsonPrimitive | JsonObject | JsonArray;
|
|
42
|
+
export type SubmitTarget = HTMLFormElement | HTMLButtonElement | HTMLInputElement | FormData | URLSearchParams | JsonValue | null;
|
|
34
43
|
export interface SubmitOptions {
|
|
35
44
|
/**
|
|
36
45
|
* The HTTP method used to submit the form. Overrides `<form method>`.
|
|
@@ -43,7 +52,7 @@ export interface SubmitOptions {
|
|
|
43
52
|
*/
|
|
44
53
|
action?: string;
|
|
45
54
|
/**
|
|
46
|
-
* The
|
|
55
|
+
* The encoding used to submit the form. Overrides `<form encType>`.
|
|
47
56
|
* Defaults to "application/x-www-form-urlencoded".
|
|
48
57
|
*/
|
|
49
58
|
encType?: FormEncType;
|
|
@@ -65,12 +74,11 @@ export interface SubmitOptions {
|
|
|
65
74
|
*/
|
|
66
75
|
preventScrollReset?: boolean;
|
|
67
76
|
}
|
|
68
|
-
export declare function getFormSubmissionInfo(target:
|
|
69
|
-
[name: string]: string;
|
|
70
|
-
} | null, options: SubmitOptions, basename: string): {
|
|
77
|
+
export declare function getFormSubmissionInfo(target: SubmitTarget, basename: string): {
|
|
71
78
|
action: string | null;
|
|
72
79
|
method: string;
|
|
73
80
|
encType: string;
|
|
74
|
-
formData: FormData;
|
|
81
|
+
formData: FormData | undefined;
|
|
82
|
+
body: any;
|
|
75
83
|
};
|
|
76
84
|
export {};
|
package/dist/index.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import * as React from "react";
|
|
6
6
|
import type { FutureConfig, NavigateOptions, RelativeRoutingType, RouteObject, To } from "react-router";
|
|
7
7
|
import type { Fetcher, FormEncType, FormMethod, FutureConfig as RouterFutureConfig, GetScrollRestorationKeyFunction, History, HTMLFormMethod, HydrationState, Router as RemixRouter, V7_FormMethod } from "@remix-run/router";
|
|
8
|
-
import type { SubmitOptions, ParamKeyValuePair, URLSearchParamsInit } from "./dom";
|
|
8
|
+
import type { SubmitOptions, ParamKeyValuePair, URLSearchParamsInit, SubmitTarget } from "./dom";
|
|
9
9
|
import { createSearchParams } from "./dom";
|
|
10
10
|
export type { FormEncType, FormMethod, GetScrollRestorationKeyFunction, ParamKeyValuePair, SubmitOptions, URLSearchParamsInit, V7_FormMethod, };
|
|
11
11
|
export { createSearchParams };
|
|
@@ -100,6 +100,11 @@ export interface FormProps extends React.FormHTMLAttributes<HTMLFormElement> {
|
|
|
100
100
|
* "put", "delete", "patch".
|
|
101
101
|
*/
|
|
102
102
|
method?: HTMLFormMethod;
|
|
103
|
+
/**
|
|
104
|
+
* `<form encType>` - enhancing beyond the normal string type and limiting
|
|
105
|
+
* to the built-in browser supported values
|
|
106
|
+
*/
|
|
107
|
+
encType?: "application/x-www-form-urlencoded" | "multipart/form-data" | "text/plain";
|
|
103
108
|
/**
|
|
104
109
|
* Normal `<form action>` but supports React Router's relative paths.
|
|
105
110
|
*/
|
|
@@ -167,10 +172,7 @@ export declare function useLinkClickHandler<E extends Element = HTMLAnchorElemen
|
|
|
167
172
|
* URLSearchParams interface.
|
|
168
173
|
*/
|
|
169
174
|
export declare function useSearchParams(defaultInit?: URLSearchParamsInit): [URLSearchParams, SetURLSearchParams];
|
|
170
|
-
export
|
|
171
|
-
declare type SubmitTarget = HTMLFormElement | HTMLButtonElement | HTMLInputElement | FormData | URLSearchParams | {
|
|
172
|
-
[name: string]: string;
|
|
173
|
-
} | null;
|
|
175
|
+
export type SetURLSearchParams = (nextInit?: URLSearchParamsInit | ((prev: URLSearchParams) => URLSearchParamsInit), navigateOpts?: NavigateOptions) => void;
|
|
174
176
|
/**
|
|
175
177
|
* Submits a HTML `<form>` to the server without reloading the page.
|
|
176
178
|
*/
|
|
@@ -191,6 +193,12 @@ export interface SubmitFunction {
|
|
|
191
193
|
*/
|
|
192
194
|
options?: SubmitOptions): void;
|
|
193
195
|
}
|
|
196
|
+
/**
|
|
197
|
+
* Submits a fetcher `<form>` to the server without reloading the page.
|
|
198
|
+
*/
|
|
199
|
+
export interface FetcherSubmitFunction {
|
|
200
|
+
(target: SubmitTarget, options?: Omit<SubmitOptions, "replace">): void;
|
|
201
|
+
}
|
|
194
202
|
/**
|
|
195
203
|
* Returns a function that may be used to programmatically submit a form (or
|
|
196
204
|
* some arbitrary data) to the server.
|
|
@@ -200,9 +208,9 @@ export declare function useFormAction(action?: string, { relative }?: {
|
|
|
200
208
|
relative?: RelativeRoutingType;
|
|
201
209
|
}): string;
|
|
202
210
|
declare function createFetcherForm(fetcherKey: string, routeId: string): React.ForwardRefExoticComponent<FormProps & React.RefAttributes<HTMLFormElement>>;
|
|
203
|
-
export
|
|
211
|
+
export type FetcherWithComponents<TData> = Fetcher<TData> & {
|
|
204
212
|
Form: ReturnType<typeof createFetcherForm>;
|
|
205
|
-
submit:
|
|
213
|
+
submit: FetcherSubmitFunction;
|
|
206
214
|
load: (href: string) => void;
|
|
207
215
|
};
|
|
208
216
|
/**
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* React Router DOM v6.
|
|
2
|
+
* React Router DOM v6.14.0-pre.1
|
|
3
3
|
*
|
|
4
4
|
* Copyright (c) Remix Software Inc.
|
|
5
5
|
*
|
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
* @license MIT
|
|
10
10
|
*/
|
|
11
11
|
import * as React from 'react';
|
|
12
|
-
import { UNSAFE_mapRouteProperties,
|
|
12
|
+
import { UNSAFE_mapRouteProperties, Router, UNSAFE_NavigationContext, useHref, useResolvedPath, useLocation, UNSAFE_DataRouterStateContext, useNavigate, createPath, UNSAFE_useRouteId, UNSAFE_RouteContext, useMatches, useNavigation, unstable_useBlocker, UNSAFE_DataRouterContext } from 'react-router';
|
|
13
13
|
export { AbortedDeferredError, Await, MemoryRouter, Navigate, NavigationType, Outlet, Route, Router, RouterProvider, Routes, UNSAFE_DataRouterContext, UNSAFE_DataRouterStateContext, UNSAFE_LocationContext, UNSAFE_NavigationContext, UNSAFE_RouteContext, UNSAFE_useRouteId, createMemoryRouter, createPath, createRoutesFromChildren, createRoutesFromElements, defer, generatePath, isRouteErrorResponse, json, matchPath, matchRoutes, parsePath, redirect, renderMatches, resolvePath, unstable_useBlocker, useActionData, useAsyncError, useAsyncValue, useHref, useInRouterContext, useLoaderData, useLocation, useMatch, useMatches, useNavigate, useNavigation, useNavigationType, useOutlet, useOutletContext, useParams, useResolvedPath, useRevalidator, useRouteError, useRouteLoaderData, useRoutes } from 'react-router';
|
|
14
|
-
import { stripBasename, createRouter, createBrowserHistory, createHashHistory, ErrorResponse,
|
|
14
|
+
import { stripBasename, UNSAFE_warning, createRouter, createBrowserHistory, createHashHistory, ErrorResponse, UNSAFE_invariant, joinPaths } from '@remix-run/router';
|
|
15
15
|
|
|
16
16
|
function _extends() {
|
|
17
17
|
_extends = Object.assign ? Object.assign.bind() : function (target) {
|
|
@@ -108,83 +108,102 @@ function getSearchParamsForLocation(locationSearch, defaultSearchParams) {
|
|
|
108
108
|
}
|
|
109
109
|
return searchParams;
|
|
110
110
|
}
|
|
111
|
-
|
|
111
|
+
// One-time check for submitter support
|
|
112
|
+
let _formDataSupportsSubmitter = null;
|
|
113
|
+
function isFormDataSubmitterSupported() {
|
|
114
|
+
if (_formDataSupportsSubmitter === null) {
|
|
115
|
+
try {
|
|
116
|
+
new FormData(document.createElement("form"),
|
|
117
|
+
// @ts-expect-error if FormData supports the submitter parameter, this will throw
|
|
118
|
+
0);
|
|
119
|
+
_formDataSupportsSubmitter = false;
|
|
120
|
+
} catch (e) {
|
|
121
|
+
_formDataSupportsSubmitter = true;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return _formDataSupportsSubmitter;
|
|
125
|
+
}
|
|
126
|
+
const supportedFormEncTypes = new Set(["application/x-www-form-urlencoded", "multipart/form-data", "text/plain"]);
|
|
127
|
+
function getFormEncType(encType) {
|
|
128
|
+
if (encType != null && !supportedFormEncTypes.has(encType)) {
|
|
129
|
+
process.env.NODE_ENV !== "production" ? UNSAFE_warning(false, "\"" + encType + "\" is not a valid `encType` for `<Form>`/`<fetcher.Form>` " + ("and will default to \"" + defaultEncType + "\"")) : void 0;
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
return encType;
|
|
133
|
+
}
|
|
134
|
+
function getFormSubmissionInfo(target, basename) {
|
|
112
135
|
let method;
|
|
113
|
-
let action
|
|
136
|
+
let action;
|
|
114
137
|
let encType;
|
|
115
138
|
let formData;
|
|
139
|
+
let body;
|
|
116
140
|
if (isFormElement(target)) {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
let attr = target.getAttribute("action");
|
|
125
|
-
action = attr ? stripBasename(attr, basename) : null;
|
|
126
|
-
}
|
|
127
|
-
method = options.method || target.getAttribute("method") || defaultMethod;
|
|
128
|
-
encType = options.encType || target.getAttribute("enctype") || defaultEncType;
|
|
141
|
+
// When grabbing the action from the element, it will have had the basename
|
|
142
|
+
// prefixed to ensure non-JS scenarios work, so strip it since we'll
|
|
143
|
+
// re-prefix in the router
|
|
144
|
+
let attr = target.getAttribute("action");
|
|
145
|
+
action = attr ? stripBasename(attr, basename) : null;
|
|
146
|
+
method = target.getAttribute("method") || defaultMethod;
|
|
147
|
+
encType = getFormEncType(target.getAttribute("enctype")) || defaultEncType;
|
|
129
148
|
formData = new FormData(target);
|
|
130
|
-
if (submissionTrigger && submissionTrigger.name) {
|
|
131
|
-
formData.append(submissionTrigger.name, submissionTrigger.value);
|
|
132
|
-
}
|
|
133
149
|
} else if (isButtonElement(target) || isInputElement(target) && (target.type === "submit" || target.type === "image")) {
|
|
134
150
|
let form = target.form;
|
|
135
151
|
if (form == null) {
|
|
136
152
|
throw new Error("Cannot submit a <button> or <input type=\"submit\"> without a <form>");
|
|
137
153
|
}
|
|
138
154
|
// <button>/<input type="submit"> may override attributes of <form>
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
//
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
+
// When grabbing the action from the element, it will have had the basename
|
|
156
|
+
// prefixed to ensure non-JS scenarios work, so strip it since we'll
|
|
157
|
+
// re-prefix in the router
|
|
158
|
+
let attr = target.getAttribute("formaction") || form.getAttribute("action");
|
|
159
|
+
action = attr ? stripBasename(attr, basename) : null;
|
|
160
|
+
method = target.getAttribute("formmethod") || form.getAttribute("method") || defaultMethod;
|
|
161
|
+
encType = getFormEncType(target.getAttribute("formenctype")) || getFormEncType(form.getAttribute("enctype")) || defaultEncType;
|
|
162
|
+
// Build a FormData object populated from a form and submitter
|
|
163
|
+
formData = new FormData(form, target);
|
|
164
|
+
// If this browser doesn't support the `FormData(el, submitter)` format,
|
|
165
|
+
// then tack on the submitter value at the end. This is a lightweight
|
|
166
|
+
// solution that is not 100% spec compliant. For complete support in older
|
|
167
|
+
// browsers, consider using the `formdata-submitter-polyfill` package
|
|
168
|
+
if (!isFormDataSubmitterSupported()) {
|
|
169
|
+
let {
|
|
170
|
+
name,
|
|
171
|
+
type,
|
|
172
|
+
value
|
|
173
|
+
} = target;
|
|
174
|
+
if (type === "image") {
|
|
175
|
+
let prefix = name ? name + "." : "";
|
|
176
|
+
formData.append(prefix + "x", "0");
|
|
177
|
+
formData.append(prefix + "y", "0");
|
|
178
|
+
} else if (name) {
|
|
179
|
+
formData.append(name, value);
|
|
180
|
+
}
|
|
155
181
|
}
|
|
156
182
|
} else if (isHtmlElement(target)) {
|
|
157
183
|
throw new Error("Cannot submit element that is not <form>, <button>, or " + "<input type=\"submit|image\">");
|
|
158
184
|
} else {
|
|
159
|
-
method =
|
|
160
|
-
action =
|
|
161
|
-
encType =
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
formData.append(name, value);
|
|
169
|
-
}
|
|
170
|
-
} else if (target != null) {
|
|
171
|
-
for (let name of Object.keys(target)) {
|
|
172
|
-
formData.append(name, target[name]);
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
}
|
|
185
|
+
method = defaultMethod;
|
|
186
|
+
action = null;
|
|
187
|
+
encType = defaultEncType;
|
|
188
|
+
body = target;
|
|
189
|
+
}
|
|
190
|
+
// Send body for <Form encType="text/plain" so we encode it into text
|
|
191
|
+
if (formData && encType === "text/plain") {
|
|
192
|
+
body = formData;
|
|
193
|
+
formData = undefined;
|
|
176
194
|
}
|
|
177
195
|
return {
|
|
178
196
|
action,
|
|
179
197
|
method: method.toLowerCase(),
|
|
180
198
|
encType,
|
|
181
|
-
formData
|
|
199
|
+
formData,
|
|
200
|
+
body
|
|
182
201
|
};
|
|
183
202
|
}
|
|
184
203
|
|
|
185
204
|
const _excluded = ["onClick", "relative", "reloadDocument", "replace", "state", "target", "to", "preventScrollReset"],
|
|
186
205
|
_excluded2 = ["aria-current", "caseSensitive", "className", "end", "style", "to", "children"],
|
|
187
|
-
_excluded3 = ["reloadDocument", "replace", "method", "action", "onSubmit", "
|
|
206
|
+
_excluded3 = ["reloadDocument", "replace", "method", "action", "onSubmit", "submit", "relative", "preventScrollReset"];
|
|
188
207
|
function createBrowserRouter(routes, opts) {
|
|
189
208
|
return createRouter({
|
|
190
209
|
basename: opts == null ? void 0 : opts.basename,
|
|
@@ -244,6 +263,33 @@ function deserializeErrors(errors) {
|
|
|
244
263
|
}
|
|
245
264
|
return serialized;
|
|
246
265
|
}
|
|
266
|
+
//#endregion
|
|
267
|
+
////////////////////////////////////////////////////////////////////////////////
|
|
268
|
+
//#region Components
|
|
269
|
+
////////////////////////////////////////////////////////////////////////////////
|
|
270
|
+
/**
|
|
271
|
+
Webpack + React 17 fails to compile on any of the following because webpack
|
|
272
|
+
complains that `startTransition` doesn't exist in `React`:
|
|
273
|
+
* import { startTransition } from "react"
|
|
274
|
+
* import * as React from from "react";
|
|
275
|
+
"startTransition" in React ? React.startTransition(() => setState()) : setState()
|
|
276
|
+
* import * as React from from "react";
|
|
277
|
+
"startTransition" in React ? React["startTransition"](() => setState()) : setState()
|
|
278
|
+
|
|
279
|
+
Moving it to a constant such as the following solves the Webpack/React 17 issue:
|
|
280
|
+
* import * as React from from "react";
|
|
281
|
+
const START_TRANSITION = "startTransition";
|
|
282
|
+
START_TRANSITION in React ? React[START_TRANSITION](() => setState()) : setState()
|
|
283
|
+
|
|
284
|
+
However, that introduces webpack/terser minification issues in production builds
|
|
285
|
+
in React 18 where minification/obfuscation ends up removing the call of
|
|
286
|
+
React.startTransition entirely from the first half of the ternary. Grabbing
|
|
287
|
+
this exported reference once up front resolves that issue.
|
|
288
|
+
|
|
289
|
+
See https://github.com/remix-run/react-router/issues/10579
|
|
290
|
+
*/
|
|
291
|
+
const START_TRANSITION = "startTransition";
|
|
292
|
+
const startTransitionImpl = React[START_TRANSITION];
|
|
247
293
|
/**
|
|
248
294
|
* A `<Router>` for use in web browsers. Provides the cleanest URLs.
|
|
249
295
|
*/
|
|
@@ -270,7 +316,7 @@ function BrowserRouter(_ref) {
|
|
|
270
316
|
v7_startTransition
|
|
271
317
|
} = future || {};
|
|
272
318
|
let setState = React.useCallback(newState => {
|
|
273
|
-
v7_startTransition &&
|
|
319
|
+
v7_startTransition && startTransitionImpl ? startTransitionImpl(() => setStateImpl(newState)) : setStateImpl(newState);
|
|
274
320
|
}, [setStateImpl, v7_startTransition]);
|
|
275
321
|
React.useLayoutEffect(() => history.listen(setState), [history, setState]);
|
|
276
322
|
return /*#__PURE__*/React.createElement(Router, {
|
|
@@ -308,7 +354,7 @@ function HashRouter(_ref2) {
|
|
|
308
354
|
v7_startTransition
|
|
309
355
|
} = future || {};
|
|
310
356
|
let setState = React.useCallback(newState => {
|
|
311
|
-
v7_startTransition &&
|
|
357
|
+
v7_startTransition && startTransitionImpl ? startTransitionImpl(() => setStateImpl(newState)) : setStateImpl(newState);
|
|
312
358
|
}, [setStateImpl, v7_startTransition]);
|
|
313
359
|
React.useLayoutEffect(() => history.listen(setState), [history, setState]);
|
|
314
360
|
return /*#__PURE__*/React.createElement(Router, {
|
|
@@ -340,7 +386,7 @@ function HistoryRouter(_ref3) {
|
|
|
340
386
|
v7_startTransition
|
|
341
387
|
} = future || {};
|
|
342
388
|
let setState = React.useCallback(newState => {
|
|
343
|
-
v7_startTransition &&
|
|
389
|
+
v7_startTransition && startTransitionImpl ? startTransitionImpl(() => setStateImpl(newState)) : setStateImpl(newState);
|
|
344
390
|
}, [setStateImpl, v7_startTransition]);
|
|
345
391
|
React.useLayoutEffect(() => history.listen(setState), [history, setState]);
|
|
346
392
|
return /*#__PURE__*/React.createElement(Router, {
|
|
@@ -501,7 +547,9 @@ if (process.env.NODE_ENV !== "production") {
|
|
|
501
547
|
* submitted and returns with data.
|
|
502
548
|
*/
|
|
503
549
|
const Form = /*#__PURE__*/React.forwardRef((props, ref) => {
|
|
550
|
+
let submit = useSubmit();
|
|
504
551
|
return /*#__PURE__*/React.createElement(FormImpl, _extends({}, props, {
|
|
552
|
+
submit: submit,
|
|
505
553
|
ref: ref
|
|
506
554
|
}));
|
|
507
555
|
});
|
|
@@ -515,13 +563,11 @@ const FormImpl = /*#__PURE__*/React.forwardRef((_ref6, forwardedRef) => {
|
|
|
515
563
|
method = defaultMethod,
|
|
516
564
|
action,
|
|
517
565
|
onSubmit,
|
|
518
|
-
|
|
519
|
-
routeId,
|
|
566
|
+
submit,
|
|
520
567
|
relative,
|
|
521
568
|
preventScrollReset
|
|
522
569
|
} = _ref6,
|
|
523
570
|
props = _objectWithoutPropertiesLoose(_ref6, _excluded3);
|
|
524
|
-
let submit = useSubmitImpl(fetcherKey, routeId);
|
|
525
571
|
let formMethod = method.toLowerCase() === "get" ? "get" : "post";
|
|
526
572
|
let formAction = useFormAction(action, {
|
|
527
573
|
relative
|
|
@@ -574,7 +620,8 @@ if (process.env.NODE_ENV !== "production") {
|
|
|
574
620
|
var DataRouterHook;
|
|
575
621
|
(function (DataRouterHook) {
|
|
576
622
|
DataRouterHook["UseScrollRestoration"] = "useScrollRestoration";
|
|
577
|
-
DataRouterHook["
|
|
623
|
+
DataRouterHook["UseSubmit"] = "useSubmit";
|
|
624
|
+
DataRouterHook["UseSubmitFetcher"] = "useSubmitFetcher";
|
|
578
625
|
DataRouterHook["UseFetcher"] = "useFetcher";
|
|
579
626
|
})(DataRouterHook || (DataRouterHook = {}));
|
|
580
627
|
var DataRouterStateHook;
|
|
@@ -650,17 +697,19 @@ function useSearchParams(defaultInit) {
|
|
|
650
697
|
}, [navigate, searchParams]);
|
|
651
698
|
return [searchParams, setSearchParams];
|
|
652
699
|
}
|
|
700
|
+
function validateClientSideSubmission() {
|
|
701
|
+
if (typeof document === "undefined") {
|
|
702
|
+
throw new Error("You are calling submit during the server render. " + "Try calling submit within a `useEffect` or callback instead.");
|
|
703
|
+
}
|
|
704
|
+
}
|
|
653
705
|
/**
|
|
654
706
|
* Returns a function that may be used to programmatically submit a form (or
|
|
655
707
|
* some arbitrary data) to the server.
|
|
656
708
|
*/
|
|
657
709
|
function useSubmit() {
|
|
658
|
-
return useSubmitImpl();
|
|
659
|
-
}
|
|
660
|
-
function useSubmitImpl(fetcherKey, fetcherRouteId) {
|
|
661
710
|
let {
|
|
662
711
|
router
|
|
663
|
-
} = useDataRouterContext(DataRouterHook.
|
|
712
|
+
} = useDataRouterContext(DataRouterHook.UseSubmit);
|
|
664
713
|
let {
|
|
665
714
|
basename
|
|
666
715
|
} = React.useContext(UNSAFE_NavigationContext);
|
|
@@ -669,32 +718,56 @@ function useSubmitImpl(fetcherKey, fetcherRouteId) {
|
|
|
669
718
|
if (options === void 0) {
|
|
670
719
|
options = {};
|
|
671
720
|
}
|
|
672
|
-
|
|
673
|
-
throw new Error("You are calling submit during the server render. " + "Try calling submit within a `useEffect` or callback instead.");
|
|
674
|
-
}
|
|
721
|
+
validateClientSideSubmission();
|
|
675
722
|
let {
|
|
676
723
|
action,
|
|
677
724
|
method,
|
|
678
725
|
encType,
|
|
679
|
-
formData
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
726
|
+
formData,
|
|
727
|
+
body
|
|
728
|
+
} = getFormSubmissionInfo(target, basename);
|
|
729
|
+
router.navigate(options.action || action, {
|
|
683
730
|
preventScrollReset: options.preventScrollReset,
|
|
684
731
|
formData,
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
732
|
+
body,
|
|
733
|
+
formMethod: options.method || method,
|
|
734
|
+
formEncType: options.encType || encType,
|
|
735
|
+
replace: options.replace,
|
|
736
|
+
fromRouteId: currentRouteId
|
|
737
|
+
});
|
|
738
|
+
}, [router, basename, currentRouteId]);
|
|
739
|
+
}
|
|
740
|
+
/**
|
|
741
|
+
* Returns the implementation for fetcher.submit
|
|
742
|
+
*/
|
|
743
|
+
function useSubmitFetcher(fetcherKey, fetcherRouteId) {
|
|
744
|
+
let {
|
|
745
|
+
router
|
|
746
|
+
} = useDataRouterContext(DataRouterHook.UseSubmitFetcher);
|
|
747
|
+
let {
|
|
748
|
+
basename
|
|
749
|
+
} = React.useContext(UNSAFE_NavigationContext);
|
|
750
|
+
return React.useCallback(function (target, options) {
|
|
751
|
+
if (options === void 0) {
|
|
752
|
+
options = {};
|
|
696
753
|
}
|
|
697
|
-
|
|
754
|
+
validateClientSideSubmission();
|
|
755
|
+
let {
|
|
756
|
+
action,
|
|
757
|
+
method,
|
|
758
|
+
encType,
|
|
759
|
+
formData,
|
|
760
|
+
body
|
|
761
|
+
} = getFormSubmissionInfo(target, basename);
|
|
762
|
+
!(fetcherRouteId != null) ? process.env.NODE_ENV !== "production" ? UNSAFE_invariant(false, "No routeId available for useFetcher()") : UNSAFE_invariant(false) : void 0;
|
|
763
|
+
router.fetch(fetcherKey, fetcherRouteId, options.action || action, {
|
|
764
|
+
preventScrollReset: options.preventScrollReset,
|
|
765
|
+
formData,
|
|
766
|
+
body,
|
|
767
|
+
formMethod: options.method || method,
|
|
768
|
+
formEncType: options.encType || encType
|
|
769
|
+
});
|
|
770
|
+
}, [router, basename, fetcherKey, fetcherRouteId]);
|
|
698
771
|
}
|
|
699
772
|
// v7: Eventually we should deprecate this entirely in favor of using the
|
|
700
773
|
// router method directly?
|
|
@@ -748,10 +821,10 @@ function useFormAction(action, _temp2) {
|
|
|
748
821
|
}
|
|
749
822
|
function createFetcherForm(fetcherKey, routeId) {
|
|
750
823
|
let FetcherForm = /*#__PURE__*/React.forwardRef((props, ref) => {
|
|
824
|
+
let submit = useSubmitFetcher(fetcherKey, routeId);
|
|
751
825
|
return /*#__PURE__*/React.createElement(FormImpl, _extends({}, props, {
|
|
752
826
|
ref: ref,
|
|
753
|
-
|
|
754
|
-
routeId: routeId
|
|
827
|
+
submit: submit
|
|
755
828
|
}));
|
|
756
829
|
});
|
|
757
830
|
if (process.env.NODE_ENV !== "production") {
|
|
@@ -783,7 +856,7 @@ function useFetcher() {
|
|
|
783
856
|
!routeId ? process.env.NODE_ENV !== "production" ? UNSAFE_invariant(false, "No routeId available for fetcher.load()") : UNSAFE_invariant(false) : void 0;
|
|
784
857
|
router.fetch(fetcherKey, routeId, href);
|
|
785
858
|
});
|
|
786
|
-
let submit =
|
|
859
|
+
let submit = useSubmitFetcher(fetcherKey, routeId);
|
|
787
860
|
let fetcher = router.getFetcher(fetcherKey);
|
|
788
861
|
let fetcherWithComponents = React.useMemo(() => _extends({
|
|
789
862
|
Form,
|
|
@@ -829,6 +902,9 @@ function useScrollRestoration(_temp3) {
|
|
|
829
902
|
restoreScrollPosition,
|
|
830
903
|
preventScrollReset
|
|
831
904
|
} = useDataRouterState(DataRouterStateHook.UseScrollRestoration);
|
|
905
|
+
let {
|
|
906
|
+
basename
|
|
907
|
+
} = React.useContext(UNSAFE_NavigationContext);
|
|
832
908
|
let location = useLocation();
|
|
833
909
|
let matches = useMatches();
|
|
834
910
|
let navigation = useNavigation();
|
|
@@ -864,9 +940,13 @@ function useScrollRestoration(_temp3) {
|
|
|
864
940
|
// Enable scroll restoration in the router
|
|
865
941
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
866
942
|
React.useLayoutEffect(() => {
|
|
867
|
-
let
|
|
943
|
+
let getKeyWithoutBasename = getKey && basename !== "/" ? (location, matches) => getKey( // Strip the basename to match useLocation()
|
|
944
|
+
_extends({}, location, {
|
|
945
|
+
pathname: stripBasename(location.pathname, basename) || location.pathname
|
|
946
|
+
}), matches) : getKey;
|
|
947
|
+
let disableScrollRestoration = router == null ? void 0 : router.enableScrollRestoration(savedScrollPositions, () => window.scrollY, getKeyWithoutBasename);
|
|
868
948
|
return () => disableScrollRestoration && disableScrollRestoration();
|
|
869
|
-
}, [router, getKey]);
|
|
949
|
+
}, [router, basename, getKey]);
|
|
870
950
|
// Restore scrolling when state.restoreScrollPosition changes
|
|
871
951
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
872
952
|
React.useLayoutEffect(() => {
|