diff --git a/.changeset/gorgeous-geese-sit.md b/.changeset/gorgeous-geese-sit.md new file mode 100644 index 0000000000..c38daf1ce8 --- /dev/null +++ b/.changeset/gorgeous-geese-sit.md @@ -0,0 +1,5 @@ +--- +"react-router-dom": patch +--- + +Fix `fetcher.submit` types - remove incorrect `navigate`/`fetcherKey`/`unstable_viewTransition` options because they are only relevant for `useSubmit` diff --git a/packages/react-router-dom/dom.ts b/packages/react-router-dom/dom.ts index 960e0b5036..ca2ac9a767 100644 --- a/packages/react-router-dom/dom.ts +++ b/packages/react-router-dom/dom.ts @@ -150,7 +150,10 @@ function isFormDataSubmitterSupported() { return _formDataSupportsSubmitter; } -export interface SubmitOptions { +/** + * Submit options shared by both navigations and fetchers + */ +interface SharedSubmitOptions { /** * The HTTP method used to submit the form. Overrides `
`. * Defaults to "GET". @@ -170,15 +173,33 @@ export interface SubmitOptions { encType?: FormEncType; /** - * Indicate a specific fetcherKey to use when using navigate=false + * Determines whether the form action is relative to the route hierarchy or + * the pathname. Use this if you want to opt out of navigating the route + * hierarchy and want to instead route based on /-delimited URL segments */ - fetcherKey?: string; + relative?: RelativeRoutingType; /** - * navigate=false will use a fetcher instead of a navigation + * In browser-based environments, prevent resetting scroll after this + * navigation when using the component */ - navigate?: boolean; + preventScrollReset?: boolean; + + /** + * Enable flushSync for this submission's state updates + */ + unstable_flushSync?: boolean; +} + +/** + * Submit options available to fetchers + */ +export interface FetcherSubmitOptions extends SharedSubmitOptions {} +/** + * Submit options available to navigations + */ +export interface SubmitOptions extends FetcherSubmitOptions { /** * Set `true` to replace the current entry in the browser's history stack * instead of creating a new one (i.e. stay on "the same page"). Defaults @@ -192,22 +213,14 @@ export interface SubmitOptions { state?: any; /** - * Determines whether the form action is relative to the route hierarchy or - * the pathname. Use this if you want to opt out of navigating the route - * hierarchy and want to instead route based on /-delimited URL segments - */ - relative?: RelativeRoutingType; - - /** - * In browser-based environments, prevent resetting scroll after this - * navigation when using the component + * Indicate a specific fetcherKey to use when using navigate=false */ - preventScrollReset?: boolean; + fetcherKey?: string; /** - * Enable flushSync for this navigation's state updates + * navigate=false will use a fetcher instead of a navigation */ - unstable_flushSync?: boolean; + navigate?: boolean; /** * Enable view transitions on this submission navigation diff --git a/packages/react-router-dom/index.tsx b/packages/react-router-dom/index.tsx index 1f2e515f75..1229fcb453 100644 --- a/packages/react-router-dom/index.tsx +++ b/packages/react-router-dom/index.tsx @@ -72,6 +72,7 @@ import type { ParamKeyValuePair, URLSearchParamsInit, SubmitTarget, + FetcherSubmitOptions, } from "./dom"; import { createSearchParams, @@ -1158,8 +1159,10 @@ if (__DEV__) { NavLink.displayName = "NavLink"; } -export interface FetcherFormProps - extends React.FormHTMLAttributes { +/** + * Form props shared by navigations and fetchers + */ +interface SharedFormProps extends React.FormHTMLAttributes { /** * The HTTP verb to use when the form is submit. Supports "get", "post", * "put", "delete", "patch". @@ -1200,7 +1203,15 @@ export interface FetcherFormProps onSubmit?: React.FormEventHandler; } -export interface FormProps extends FetcherFormProps { +/** + * Form props available to fetchers + */ +export interface FetcherFormProps extends SharedFormProps {} + +/** + * Form props available to navigations + */ +export interface FormProps extends SharedFormProps { /** * Indicate a specific fetcherKey to use when using navigate=false */ @@ -1523,7 +1534,7 @@ export interface FetcherSubmitFunction { ( target: SubmitTarget, // Fetchers cannot replace or set state because they are not navigation events - options?: Omit + options?: FetcherSubmitOptions ): void; } diff --git a/packages/router/__tests__/navigation-test.ts b/packages/router/__tests__/navigation-test.ts index 894da06535..e6ea50c5b1 100644 --- a/packages/router/__tests__/navigation-test.ts +++ b/packages/router/__tests__/navigation-test.ts @@ -169,11 +169,13 @@ describe("navigations", () => { }) ); expect(t.router.state.loaderData).toEqual({}); - expect(t.router.state.errors).toMatchInlineSnapshot(` - { - "foo": [SyntaxError: Unexpected token } in JSON at position 15], - } - `); + + // Node 16/18 versus 20 output different errors here :/ + let expected = + process.version.startsWith("v16") || process.version.startsWith("v18") + ? "Unexpected token } in JSON at position 15" + : "Unexpected non-whitespace character after JSON at position 15"; + expect(t.router.state.errors?.foo).toEqual(new SyntaxError(expected)); }); it("bubbles errors when unwrapping Responses", async () => { @@ -204,11 +206,13 @@ describe("navigations", () => { }) ); expect(t.router.state.loaderData).toEqual({}); - expect(t.router.state.errors).toMatchInlineSnapshot(` - { - "root": [SyntaxError: Unexpected token } in JSON at position 15], - } - `); + + // Node 16/18 versus 20 output different errors here :/ + let expected = + process.version.startsWith("v16") || process.version.startsWith("v18") + ? "Unexpected token } in JSON at position 15" + : "Unexpected non-whitespace character after JSON at position 15"; + expect(t.router.state.errors?.root).toEqual(new SyntaxError(expected)); }); it("does not fetch unchanging layout data", async () => {