react-router-dom 6.13.0 → 6.14.0-pre.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,67 @@
1
1
  # `react-router-dom`
2
2
 
3
+ ## 6.14.0-pre.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 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))
8
+
9
+ ```jsx
10
+ // The default behavior will still serialize as FormData
11
+ function Component() {
12
+ let navigation = useNavigation();
13
+ let submit = useSubmit();
14
+ submit({ key: "value" });
15
+ // navigation.formEncType => "application/x-www-form-urlencoded"
16
+ // navigation.formData => FormData instance
17
+ }
18
+
19
+ async function action({ request }) {
20
+ // request.headers.get("Content-Type") => "application/x-www-form-urlencoded"
21
+ // await request.formData() => FormData instance
22
+ }
23
+ ```
24
+
25
+ ```js
26
+ // Opt-into JSON encoding with `encType: "application/json"`
27
+ function Component() {
28
+ let submit = useSubmit();
29
+ submit({ key: "value" }, { encType: "application/json" });
30
+ // navigation.formEncType => "application/json"
31
+ // navigation.json => { key: "value" }
32
+ }
33
+
34
+ async function action({ request }) {
35
+ // request.headers.get("Content-Type") => "application/json"
36
+ // await request.json => { key: "value" }
37
+ }
38
+ ```
39
+
40
+ ```js
41
+ // Opt-into JSON encoding with `encType: "application/json"`
42
+ function Component() {
43
+ let submit = useSubmit();
44
+ submit("Text submission", { encType: "text/plain" });
45
+ // navigation.formEncType => "text/plain"
46
+ // navigation.text => "Text submission"
47
+ }
48
+
49
+ async function action({ request }) {
50
+ // request.headers.get("Content-Type") => "text/plain"
51
+ // await request.text() => "Text submission"
52
+ }
53
+ ```
54
+
55
+ ### Patch Changes
56
+
57
+ - 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))
58
+ - upgrade `typescript` to 5.1 ([#10581](https://github.com/remix-run/react-router/pull/10581))
59
+ - 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))
60
+ - Fix `tsc --skipLibCheck:false` issues on React 17 ([#10622](https://github.com/remix-run/react-router/pull/10622))
61
+ - Updated dependencies:
62
+ - `react-router@6.14.0-pre.0`
63
+ - `@remix-run/router@1.7.0-pre.0`
64
+
3
65
  ## 6.13.0
4
66
 
5
67
  ### 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.0
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,95 @@ 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 = false;
113
+ try {
114
+ // @ts-expect-error if FormData supports the submitter parameter, this will throw
115
+ new FormData(undefined, 0);
116
+ } catch (e) {
117
+ formDataSupportsSubmitter = true;
118
+ }
119
+ const supportedFormEncTypes = new Set(["application/x-www-form-urlencoded", "multipart/form-data", "text/plain"]);
120
+ function getFormEncType(encType) {
121
+ if (encType != null && !supportedFormEncTypes.has(encType)) {
122
+ 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;
123
+ return null;
124
+ }
125
+ return encType;
126
+ }
127
+ function getFormSubmissionInfo(target, basename) {
112
128
  let method;
113
- let action = null;
129
+ let action;
114
130
  let encType;
115
131
  let formData;
132
+ let body;
116
133
  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;
134
+ // When grabbing the action from the element, it will have had the basename
135
+ // prefixed to ensure non-JS scenarios work, so strip it since we'll
136
+ // re-prefix in the router
137
+ let attr = target.getAttribute("action");
138
+ action = attr ? stripBasename(attr, basename) : null;
139
+ method = target.getAttribute("method") || defaultMethod;
140
+ encType = getFormEncType(target.getAttribute("enctype")) || defaultEncType;
129
141
  formData = new FormData(target);
130
- if (submissionTrigger && submissionTrigger.name) {
131
- formData.append(submissionTrigger.name, submissionTrigger.value);
132
- }
133
142
  } else if (isButtonElement(target) || isInputElement(target) && (target.type === "submit" || target.type === "image")) {
134
143
  let form = target.form;
135
144
  if (form == null) {
136
145
  throw new Error("Cannot submit a <button> or <input type=\"submit\"> without a <form>");
137
146
  }
138
147
  // <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);
148
+ // When grabbing the action from the element, it will have had the basename
149
+ // prefixed to ensure non-JS scenarios work, so strip it since we'll
150
+ // re-prefix in the router
151
+ let attr = target.getAttribute("formaction") || form.getAttribute("action");
152
+ action = attr ? stripBasename(attr, basename) : null;
153
+ method = target.getAttribute("formmethod") || form.getAttribute("method") || defaultMethod;
154
+ encType = getFormEncType(target.getAttribute("formenctype")) || getFormEncType(form.getAttribute("enctype")) || defaultEncType;
155
+ // Build a FormData object populated from a form and submitter
156
+ formData = new FormData(form, target);
157
+ // If this browser doesn't support the `FormData(el, submitter)` format,
158
+ // then tack on the submitter value at the end. This is a lightweight
159
+ // solution that is not 100% spec compliant. For complete support in older
160
+ // browsers, consider using the `formdata-submitter-polyfill` package
161
+ if (!formDataSupportsSubmitter) {
162
+ let {
163
+ name,
164
+ type,
165
+ value
166
+ } = target;
167
+ if (type === "image") {
168
+ let prefix = name ? name + "." : "";
169
+ formData.append(prefix + "x", "0");
170
+ formData.append(prefix + "y", "0");
171
+ } else if (name) {
172
+ formData.append(name, value);
173
+ }
155
174
  }
156
175
  } else if (isHtmlElement(target)) {
157
176
  throw new Error("Cannot submit element that is not <form>, <button>, or " + "<input type=\"submit|image\">");
158
177
  } 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
- }
178
+ method = defaultMethod;
179
+ action = null;
180
+ encType = defaultEncType;
181
+ body = target;
182
+ }
183
+ // Send body for <Form encType="text/plain" so we encode it into text
184
+ if (formData && encType === "text/plain") {
185
+ body = formData;
186
+ formData = undefined;
176
187
  }
177
188
  return {
178
189
  action,
179
190
  method: method.toLowerCase(),
180
191
  encType,
181
- formData
192
+ formData,
193
+ body
182
194
  };
183
195
  }
184
196
 
185
197
  const _excluded = ["onClick", "relative", "reloadDocument", "replace", "state", "target", "to", "preventScrollReset"],
186
198
  _excluded2 = ["aria-current", "caseSensitive", "className", "end", "style", "to", "children"],
187
- _excluded3 = ["reloadDocument", "replace", "method", "action", "onSubmit", "fetcherKey", "routeId", "relative", "preventScrollReset"];
199
+ _excluded3 = ["reloadDocument", "replace", "method", "action", "onSubmit", "submit", "relative", "preventScrollReset"];
188
200
  function createBrowserRouter(routes, opts) {
189
201
  return createRouter({
190
202
  basename: opts == null ? void 0 : opts.basename,
@@ -244,6 +256,33 @@ function deserializeErrors(errors) {
244
256
  }
245
257
  return serialized;
246
258
  }
259
+ //#endregion
260
+ ////////////////////////////////////////////////////////////////////////////////
261
+ //#region Components
262
+ ////////////////////////////////////////////////////////////////////////////////
263
+ /**
264
+ Webpack + React 17 fails to compile on any of the following because webpack
265
+ complains that `startTransition` doesn't exist in `React`:
266
+ * import { startTransition } from "react"
267
+ * import * as React from from "react";
268
+ "startTransition" in React ? React.startTransition(() => setState()) : setState()
269
+ * import * as React from from "react";
270
+ "startTransition" in React ? React["startTransition"](() => setState()) : setState()
271
+
272
+ Moving it to a constant such as the following solves the Webpack/React 17 issue:
273
+ * import * as React from from "react";
274
+ const START_TRANSITION = "startTransition";
275
+ START_TRANSITION in React ? React[START_TRANSITION](() => setState()) : setState()
276
+
277
+ However, that introduces webpack/terser minification issues in production builds
278
+ in React 18 where minification/obfuscation ends up removing the call of
279
+ React.startTransition entirely from the first half of the ternary. Grabbing
280
+ this exported reference once up front resolves that issue.
281
+
282
+ See https://github.com/remix-run/react-router/issues/10579
283
+ */
284
+ const START_TRANSITION = "startTransition";
285
+ const startTransitionImpl = React[START_TRANSITION];
247
286
  /**
248
287
  * A `<Router>` for use in web browsers. Provides the cleanest URLs.
249
288
  */
@@ -270,7 +309,7 @@ function BrowserRouter(_ref) {
270
309
  v7_startTransition
271
310
  } = future || {};
272
311
  let setState = React.useCallback(newState => {
273
- v7_startTransition && UNSAFE_startTransitionImpl ? UNSAFE_startTransitionImpl(() => setStateImpl(newState)) : setStateImpl(newState);
312
+ v7_startTransition && startTransitionImpl ? startTransitionImpl(() => setStateImpl(newState)) : setStateImpl(newState);
274
313
  }, [setStateImpl, v7_startTransition]);
275
314
  React.useLayoutEffect(() => history.listen(setState), [history, setState]);
276
315
  return /*#__PURE__*/React.createElement(Router, {
@@ -308,7 +347,7 @@ function HashRouter(_ref2) {
308
347
  v7_startTransition
309
348
  } = future || {};
310
349
  let setState = React.useCallback(newState => {
311
- v7_startTransition && UNSAFE_startTransitionImpl ? UNSAFE_startTransitionImpl(() => setStateImpl(newState)) : setStateImpl(newState);
350
+ v7_startTransition && startTransitionImpl ? startTransitionImpl(() => setStateImpl(newState)) : setStateImpl(newState);
312
351
  }, [setStateImpl, v7_startTransition]);
313
352
  React.useLayoutEffect(() => history.listen(setState), [history, setState]);
314
353
  return /*#__PURE__*/React.createElement(Router, {
@@ -340,7 +379,7 @@ function HistoryRouter(_ref3) {
340
379
  v7_startTransition
341
380
  } = future || {};
342
381
  let setState = React.useCallback(newState => {
343
- v7_startTransition && UNSAFE_startTransitionImpl ? UNSAFE_startTransitionImpl(() => setStateImpl(newState)) : setStateImpl(newState);
382
+ v7_startTransition && startTransitionImpl ? startTransitionImpl(() => setStateImpl(newState)) : setStateImpl(newState);
344
383
  }, [setStateImpl, v7_startTransition]);
345
384
  React.useLayoutEffect(() => history.listen(setState), [history, setState]);
346
385
  return /*#__PURE__*/React.createElement(Router, {
@@ -501,7 +540,9 @@ if (process.env.NODE_ENV !== "production") {
501
540
  * submitted and returns with data.
502
541
  */
503
542
  const Form = /*#__PURE__*/React.forwardRef((props, ref) => {
543
+ let submit = useSubmit();
504
544
  return /*#__PURE__*/React.createElement(FormImpl, _extends({}, props, {
545
+ submit: submit,
505
546
  ref: ref
506
547
  }));
507
548
  });
@@ -515,13 +556,11 @@ const FormImpl = /*#__PURE__*/React.forwardRef((_ref6, forwardedRef) => {
515
556
  method = defaultMethod,
516
557
  action,
517
558
  onSubmit,
518
- fetcherKey,
519
- routeId,
559
+ submit,
520
560
  relative,
521
561
  preventScrollReset
522
562
  } = _ref6,
523
563
  props = _objectWithoutPropertiesLoose(_ref6, _excluded3);
524
- let submit = useSubmitImpl(fetcherKey, routeId);
525
564
  let formMethod = method.toLowerCase() === "get" ? "get" : "post";
526
565
  let formAction = useFormAction(action, {
527
566
  relative
@@ -574,7 +613,8 @@ if (process.env.NODE_ENV !== "production") {
574
613
  var DataRouterHook;
575
614
  (function (DataRouterHook) {
576
615
  DataRouterHook["UseScrollRestoration"] = "useScrollRestoration";
577
- DataRouterHook["UseSubmitImpl"] = "useSubmitImpl";
616
+ DataRouterHook["UseSubmit"] = "useSubmit";
617
+ DataRouterHook["UseSubmitFetcher"] = "useSubmitFetcher";
578
618
  DataRouterHook["UseFetcher"] = "useFetcher";
579
619
  })(DataRouterHook || (DataRouterHook = {}));
580
620
  var DataRouterStateHook;
@@ -650,17 +690,19 @@ function useSearchParams(defaultInit) {
650
690
  }, [navigate, searchParams]);
651
691
  return [searchParams, setSearchParams];
652
692
  }
693
+ function validateClientSideSubmission() {
694
+ if (typeof document === "undefined") {
695
+ throw new Error("You are calling submit during the server render. " + "Try calling submit within a `useEffect` or callback instead.");
696
+ }
697
+ }
653
698
  /**
654
699
  * Returns a function that may be used to programmatically submit a form (or
655
700
  * some arbitrary data) to the server.
656
701
  */
657
702
  function useSubmit() {
658
- return useSubmitImpl();
659
- }
660
- function useSubmitImpl(fetcherKey, fetcherRouteId) {
661
703
  let {
662
704
  router
663
- } = useDataRouterContext(DataRouterHook.UseSubmitImpl);
705
+ } = useDataRouterContext(DataRouterHook.UseSubmit);
664
706
  let {
665
707
  basename
666
708
  } = React.useContext(UNSAFE_NavigationContext);
@@ -669,32 +711,56 @@ function useSubmitImpl(fetcherKey, fetcherRouteId) {
669
711
  if (options === void 0) {
670
712
  options = {};
671
713
  }
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
- }
714
+ validateClientSideSubmission();
675
715
  let {
676
716
  action,
677
717
  method,
678
718
  encType,
679
- formData
680
- } = getFormSubmissionInfo(target, options, basename);
681
- // Base options shared between fetch() and navigate()
682
- let opts = {
719
+ formData,
720
+ body
721
+ } = getFormSubmissionInfo(target, basename);
722
+ router.navigate(options.action || action, {
683
723
  preventScrollReset: options.preventScrollReset,
684
724
  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
- }));
725
+ body,
726
+ formMethod: options.method || method,
727
+ formEncType: options.encType || encType,
728
+ replace: options.replace,
729
+ fromRouteId: currentRouteId
730
+ });
731
+ }, [router, basename, currentRouteId]);
732
+ }
733
+ /**
734
+ * Returns the implementation for fetcher.submit
735
+ */
736
+ function useSubmitFetcher(fetcherKey, fetcherRouteId) {
737
+ let {
738
+ router
739
+ } = useDataRouterContext(DataRouterHook.UseSubmitFetcher);
740
+ let {
741
+ basename
742
+ } = React.useContext(UNSAFE_NavigationContext);
743
+ return React.useCallback(function (target, options) {
744
+ if (options === void 0) {
745
+ options = {};
696
746
  }
697
- }, [router, basename, fetcherKey, fetcherRouteId, currentRouteId]);
747
+ validateClientSideSubmission();
748
+ let {
749
+ action,
750
+ method,
751
+ encType,
752
+ formData,
753
+ body
754
+ } = getFormSubmissionInfo(target, basename);
755
+ !(fetcherRouteId != null) ? process.env.NODE_ENV !== "production" ? UNSAFE_invariant(false, "No routeId available for useFetcher()") : UNSAFE_invariant(false) : void 0;
756
+ router.fetch(fetcherKey, fetcherRouteId, options.action || action, {
757
+ preventScrollReset: options.preventScrollReset,
758
+ formData,
759
+ body,
760
+ formMethod: options.method || method,
761
+ formEncType: options.encType || encType
762
+ });
763
+ }, [router, basename, fetcherKey, fetcherRouteId]);
698
764
  }
699
765
  // v7: Eventually we should deprecate this entirely in favor of using the
700
766
  // router method directly?
@@ -748,10 +814,10 @@ function useFormAction(action, _temp2) {
748
814
  }
749
815
  function createFetcherForm(fetcherKey, routeId) {
750
816
  let FetcherForm = /*#__PURE__*/React.forwardRef((props, ref) => {
817
+ let submit = useSubmitFetcher(fetcherKey, routeId);
751
818
  return /*#__PURE__*/React.createElement(FormImpl, _extends({}, props, {
752
819
  ref: ref,
753
- fetcherKey: fetcherKey,
754
- routeId: routeId
820
+ submit: submit
755
821
  }));
756
822
  });
757
823
  if (process.env.NODE_ENV !== "production") {
@@ -783,7 +849,7 @@ function useFetcher() {
783
849
  !routeId ? process.env.NODE_ENV !== "production" ? UNSAFE_invariant(false, "No routeId available for fetcher.load()") : UNSAFE_invariant(false) : void 0;
784
850
  router.fetch(fetcherKey, routeId, href);
785
851
  });
786
- let submit = useSubmitImpl(fetcherKey, routeId);
852
+ let submit = useSubmitFetcher(fetcherKey, routeId);
787
853
  let fetcher = router.getFetcher(fetcherKey);
788
854
  let fetcherWithComponents = React.useMemo(() => _extends({
789
855
  Form,
@@ -829,6 +895,9 @@ function useScrollRestoration(_temp3) {
829
895
  restoreScrollPosition,
830
896
  preventScrollReset
831
897
  } = useDataRouterState(DataRouterStateHook.UseScrollRestoration);
898
+ let {
899
+ basename
900
+ } = React.useContext(UNSAFE_NavigationContext);
832
901
  let location = useLocation();
833
902
  let matches = useMatches();
834
903
  let navigation = useNavigation();
@@ -864,9 +933,13 @@ function useScrollRestoration(_temp3) {
864
933
  // Enable scroll restoration in the router
865
934
  // eslint-disable-next-line react-hooks/rules-of-hooks
866
935
  React.useLayoutEffect(() => {
867
- let disableScrollRestoration = router == null ? void 0 : router.enableScrollRestoration(savedScrollPositions, () => window.scrollY, getKey);
936
+ let getKeyWithoutBasename = getKey && basename !== "/" ? (location, matches) => getKey( // Strip the basename to match useLocation()
937
+ _extends({}, location, {
938
+ pathname: stripBasename(location.pathname, basename) || location.pathname
939
+ }), matches) : getKey;
940
+ let disableScrollRestoration = router == null ? void 0 : router.enableScrollRestoration(savedScrollPositions, () => window.scrollY, getKeyWithoutBasename);
868
941
  return () => disableScrollRestoration && disableScrollRestoration();
869
- }, [router, getKey]);
942
+ }, [router, basename, getKey]);
870
943
  // Restore scrolling when state.restoreScrollPosition changes
871
944
  // eslint-disable-next-line react-hooks/rules-of-hooks
872
945
  React.useLayoutEffect(() => {