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 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
- declare type LimitedMouseEvent = Pick<MouseEvent, "button" | "metaKey" | "altKey" | "ctrlKey" | "shiftKey">;
7
+ type LimitedMouseEvent = Pick<MouseEvent, "button" | "metaKey" | "altKey" | "ctrlKey" | "shiftKey">;
8
8
  export declare function shouldProcessLinkClick(event: LimitedMouseEvent, target?: string): boolean;
9
- export declare type ParamKeyValuePair = [string, string];
10
- export declare type URLSearchParamsInit = string | ParamKeyValuePair[] | Record<string, string | string[]> | URLSearchParams;
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 action URL used to submit the form. Overrides `<form encType>`.
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: HTMLFormElement | HTMLButtonElement | HTMLInputElement | FormData | URLSearchParams | {
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 declare type SetURLSearchParams = (nextInit?: URLSearchParamsInit | ((prev: URLSearchParams) => URLSearchParamsInit), navigateOpts?: NavigateOptions) => void;
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 declare type FetcherWithComponents<TData> = Fetcher<TData> & {
211
+ export type FetcherWithComponents<TData> = Fetcher<TData> & {
204
212
  Form: ReturnType<typeof createFetcherForm>;
205
- submit: (target: SubmitTarget, options?: Omit<SubmitOptions, "replace" | "preventScrollReset">) => void;
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.13.0
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, UNSAFE_startTransitionImpl, Router, UNSAFE_NavigationContext, useHref, useResolvedPath, useLocation, UNSAFE_DataRouterStateContext, useNavigate, createPath, UNSAFE_useRouteId, UNSAFE_RouteContext, useMatches, useNavigation, unstable_useBlocker, UNSAFE_DataRouterContext } from 'react-router';
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, UNSAFE_warning, UNSAFE_invariant, joinPaths } from '@remix-run/router';
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
- function getFormSubmissionInfo(target, options, basename) {
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 = null;
136
+ let action;
114
137
  let encType;
115
138
  let formData;
139
+ let body;
116
140
  if (isFormElement(target)) {
117
- let submissionTrigger = options.submissionTrigger;
118
- if (options.action) {
119
- action = options.action;
120
- } else {
121
- // When grabbing the action from the element, it will have had the basename
122
- // prefixed to ensure non-JS scenarios work, so strip it since we'll
123
- // re-prefix in the router
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
- if (options.action) {
140
- action = options.action;
141
- } else {
142
- // When grabbing the action from the element, it will have had the basename
143
- // prefixed to ensure non-JS scenarios work, so strip it since we'll
144
- // re-prefix in the router
145
- let attr = target.getAttribute("formaction") || form.getAttribute("action");
146
- action = attr ? stripBasename(attr, basename) : null;
147
- }
148
- method = options.method || target.getAttribute("formmethod") || form.getAttribute("method") || defaultMethod;
149
- encType = options.encType || target.getAttribute("formenctype") || form.getAttribute("enctype") || defaultEncType;
150
- formData = new FormData(form);
151
- // Include name + value from a <button>, appending in case the button name
152
- // matches an existing input name
153
- if (target.name) {
154
- formData.append(target.name, target.value);
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 = options.method || defaultMethod;
160
- action = options.action || null;
161
- encType = options.encType || defaultEncType;
162
- if (target instanceof FormData) {
163
- formData = target;
164
- } else {
165
- formData = new FormData();
166
- if (target instanceof URLSearchParams) {
167
- for (let [name, value] of target) {
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", "fetcherKey", "routeId", "relative", "preventScrollReset"];
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 && UNSAFE_startTransitionImpl ? UNSAFE_startTransitionImpl(() => setStateImpl(newState)) : setStateImpl(newState);
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 && UNSAFE_startTransitionImpl ? UNSAFE_startTransitionImpl(() => setStateImpl(newState)) : setStateImpl(newState);
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 && UNSAFE_startTransitionImpl ? UNSAFE_startTransitionImpl(() => setStateImpl(newState)) : setStateImpl(newState);
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
- fetcherKey,
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["UseSubmitImpl"] = "useSubmitImpl";
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.UseSubmitImpl);
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
- if (typeof document === "undefined") {
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
- } = getFormSubmissionInfo(target, options, basename);
681
- // Base options shared between fetch() and navigate()
682
- let opts = {
726
+ formData,
727
+ body
728
+ } = getFormSubmissionInfo(target, basename);
729
+ router.navigate(options.action || action, {
683
730
  preventScrollReset: options.preventScrollReset,
684
731
  formData,
685
- formMethod: method,
686
- formEncType: encType
687
- };
688
- if (fetcherKey) {
689
- !(fetcherRouteId != null) ? process.env.NODE_ENV !== "production" ? UNSAFE_invariant(false, "No routeId available for useFetcher()") : UNSAFE_invariant(false) : void 0;
690
- router.fetch(fetcherKey, fetcherRouteId, action, opts);
691
- } else {
692
- router.navigate(action, _extends({}, opts, {
693
- replace: options.replace,
694
- fromRouteId: currentRouteId
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
- }, [router, basename, fetcherKey, fetcherRouteId, currentRouteId]);
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
- fetcherKey: fetcherKey,
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 = useSubmitImpl(fetcherKey, routeId);
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 disableScrollRestoration = router == null ? void 0 : router.enableScrollRestoration(savedScrollPositions, () => window.scrollY, getKey);
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(() => {