diff --git a/.changeset/fair-cheetahs-hope.md b/.changeset/fair-cheetahs-hope.md new file mode 100644 index 0000000000..e0503d468d --- /dev/null +++ b/.changeset/fair-cheetahs-hope.md @@ -0,0 +1,6 @@ +--- +"react-router-dom": major +"react-router": major +--- + +Remove `future.v7_normalizeFormMethod` future flag diff --git a/packages/react-router-dom/index.ts b/packages/react-router-dom/index.ts index d142f68d0b..7383fec854 100644 --- a/packages/react-router-dom/index.ts +++ b/packages/react-router-dom/index.ts @@ -77,7 +77,6 @@ export type { FormMethod, GetScrollRestorationKeyFunction, StaticHandlerContext, - V7_FormMethod, BrowserRouterProps, HashRouterProps, HistoryRouterProps, diff --git a/packages/react-router/__tests__/router/fetchers-test.ts b/packages/react-router/__tests__/router/fetchers-test.ts index 504e68d750..2fa7b1c0b5 100644 --- a/packages/react-router/__tests__/router/fetchers-test.ts +++ b/packages/react-router/__tests__/router/fetchers-test.ts @@ -169,7 +169,7 @@ describe("fetchers", () => { formData: createFormData({ key: "value" }), }); expect(A.fetcher.state).toBe("loading"); - expect(A.fetcher.formMethod).toBe("get"); + expect(A.fetcher.formMethod).toBe("GET"); expect(A.fetcher.formAction).toBe("/foo"); expect(A.fetcher.formData).toEqual(createFormData({ key: "value" })); expect(A.fetcher.formEncType).toBe("application/x-www-form-urlencoded"); @@ -2111,7 +2111,7 @@ describe("fetchers", () => { "formAction": "/two/three", "formData": FormData {}, "formEncType": "application/x-www-form-urlencoded", - "formMethod": "post", + "formMethod": "POST", "json": undefined, "nextParams": { "a": "two", diff --git a/packages/react-router/__tests__/router/navigation-test.ts b/packages/react-router/__tests__/router/navigation-test.ts index a21cc3dcfa..909cff68f8 100644 --- a/packages/react-router/__tests__/router/navigation-test.ts +++ b/packages/react-router/__tests__/router/navigation-test.ts @@ -1154,7 +1154,7 @@ describe("navigations", () => { // @ts-expect-error new URLSearchParams(navigation.formData).toString() ).toBe("gosh=dang"); - expect(navigation.formMethod).toBe("post"); + expect(navigation.formMethod).toBe("POST"); expect(navigation.formEncType).toBe("application/x-www-form-urlencoded"); expect(navigation.location).toMatchObject({ pathname: "/foo", @@ -1169,7 +1169,7 @@ describe("navigations", () => { // @ts-expect-error new URLSearchParams(navigation.formData).toString() ).toBe("gosh=dang"); - expect(navigation.formMethod).toBe("post"); + expect(navigation.formMethod).toBe("POST"); expect(navigation.formEncType).toBe("application/x-www-form-urlencoded"); expect(navigation.location).toMatchObject({ pathname: "/foo", @@ -1203,7 +1203,7 @@ describe("navigations", () => { // @ts-expect-error new URLSearchParams(navigation.formData).toString() ).toBe("gosh=dang"); - expect(navigation.formMethod).toBe("post"); + expect(navigation.formMethod).toBe("POST"); expect(navigation.location).toMatchObject({ pathname: "/bar", search: "", @@ -1229,7 +1229,7 @@ describe("navigations", () => { let navigation = t.router.state.navigation; expect(navigation.state).toBe("loading"); expect(navigation.formData).toEqual(createFormData({ gosh: "dang" })); - expect(navigation.formMethod).toBe("get"); + expect(navigation.formMethod).toBe("GET"); expect(navigation.formEncType).toBe("application/x-www-form-urlencoded"); expect(navigation.location).toMatchObject({ pathname: "/foo", @@ -1257,7 +1257,7 @@ describe("navigations", () => { let navigation = t.router.state.navigation; expect(navigation.state).toBe("loading"); expect(navigation.formData).toEqual(createFormData({ gosh: "dang" })); - expect(navigation.formMethod).toBe("get"); + expect(navigation.formMethod).toBe("GET"); expect(navigation.formEncType).toBe("application/x-www-form-urlencoded"); expect(navigation.location?.pathname).toBe("/bar"); diff --git a/packages/react-router/__tests__/router/revalidate-test.ts b/packages/react-router/__tests__/router/revalidate-test.ts index 8f15661e36..3255fd4397 100644 --- a/packages/react-router/__tests__/router/revalidate-test.ts +++ b/packages/react-router/__tests__/router/revalidate-test.ts @@ -271,7 +271,7 @@ describe("router.revalidate", () => { pathname: "/tasks", search: "?key=value", }, - formMethod: "get", + formMethod: "GET", formData: createFormData({ key: "value" }), }, revalidation: "loading", diff --git a/packages/react-router/__tests__/router/router-test.ts b/packages/react-router/__tests__/router/router-test.ts index 929c075460..49ba868309 100644 --- a/packages/react-router/__tests__/router/router-test.ts +++ b/packages/react-router/__tests__/router/router-test.ts @@ -690,7 +690,7 @@ describe("a router", () => { pathname: "/tasks", search: "?key=value", }); - expect(t.router.state.navigation.formMethod).toBe("get"); + expect(t.router.state.navigation.formMethod).toBe("GET"); expect(t.router.state.navigation.formData).toEqual( createFormData({ key: "value" }) ); @@ -710,7 +710,7 @@ describe("a router", () => { pathname: "/tasks", search: "?key=value", }); - expect(t.router.state.navigation.formMethod).toBe("get"); + expect(t.router.state.navigation.formMethod).toBe("GET"); expect(t.router.state.navigation.formData).toEqual( createFormData({ key: "value" }) ); @@ -730,7 +730,7 @@ describe("a router", () => { pathname: "/tasks", search: "?key=2", }); - expect(t.router.state.navigation.formMethod).toBe("get"); + expect(t.router.state.navigation.formMethod).toBe("GET"); expect(t.router.state.navigation.formData).toEqual( createFormData({ key: "2" }) ); @@ -750,7 +750,7 @@ describe("a router", () => { pathname: "/tasks", search: "?key=1", }); - expect(t.router.state.navigation.formMethod).toBe("post"); + expect(t.router.state.navigation.formMethod).toBe("POST"); expect(t.router.state.navigation.formData).toEqual( createFormData({ key: "2" }) ); diff --git a/packages/react-router/__tests__/router/should-revalidate-test.ts b/packages/react-router/__tests__/router/should-revalidate-test.ts index f1aebc5f38..69037af7ed 100644 --- a/packages/react-router/__tests__/router/should-revalidate-test.ts +++ b/packages/react-router/__tests__/router/should-revalidate-test.ts @@ -256,7 +256,7 @@ describe("shouldRevalidate", () => { nextParams: {}, nextUrl: expect.urlMatch("http://localhost/child"), defaultShouldRevalidate: true, - formMethod: "post", + formMethod: "POST", formAction: "/child", formEncType: "application/x-www-form-urlencoded", actionResult: "ACTION", @@ -310,7 +310,7 @@ describe("shouldRevalidate", () => { nextParams: {}, nextUrl: expect.urlMatch("http://localhost/"), defaultShouldRevalidate: true, - formMethod: "post", + formMethod: "POST", formAction: "/child", formEncType: "application/x-www-form-urlencoded", actionResult: undefined, @@ -364,7 +364,7 @@ describe("shouldRevalidate", () => { nextParams: {}, nextUrl: expect.urlMatch("http://localhost/child"), defaultShouldRevalidate: true, - formMethod: "post", + formMethod: "POST", formAction: "/child", formEncType: "application/x-www-form-urlencoded", actionResult: "ACTION", @@ -406,7 +406,7 @@ describe("shouldRevalidate", () => { // @ts-expect-error let arg = shouldRevalidate.mock.calls[0][0]; let expectedArg: Partial = { - formMethod: "post", + formMethod: "POST", formAction: "/", formEncType: "application/json", text: undefined, @@ -448,7 +448,7 @@ describe("shouldRevalidate", () => { // @ts-expect-error let arg = shouldRevalidate.mock.calls[0][0]; let expectedArg: Partial = { - formMethod: "post", + formMethod: "POST", formAction: "/", formEncType: "text/plain", text: "hello world", @@ -657,7 +657,7 @@ describe("shouldRevalidate", () => { formAction: "/child", formData: createFormData({}), formEncType: "application/x-www-form-urlencoded", - formMethod: "post", + formMethod: "POST", defaultShouldRevalidate: true, }); @@ -715,7 +715,7 @@ describe("shouldRevalidate", () => { "formAction": "/fetch", "formData": FormData {}, "formEncType": "application/x-www-form-urlencoded", - "formMethod": "post", + "formMethod": "POST", "json": undefined, "nextParams": {}, "nextUrl": "http://localhost/", @@ -779,7 +779,7 @@ describe("shouldRevalidate", () => { "formAction": "/fetch", "formData": FormData {}, "formEncType": "application/x-www-form-urlencoded", - "formMethod": "post", + "formMethod": "POST", "json": undefined, "nextParams": {}, "nextUrl": "http://localhost/", diff --git a/packages/react-router/__tests__/router/submission-test.ts b/packages/react-router/__tests__/router/submission-test.ts index 2f0744b9ac..8c49a593b4 100644 --- a/packages/react-router/__tests__/router/submission-test.ts +++ b/packages/react-router/__tests__/router/submission-test.ts @@ -504,16 +504,16 @@ describe("submissions", () => { formMethod: "get", formData: createFormData({}), }); - expect(t.router.state.navigation.formMethod).toBe("get"); + expect(t.router.state.navigation.formMethod).toBe("GET"); await A.loaders.child.resolve("LOADER"); expect(t.router.state.navigation.formMethod).toBeUndefined(); await t.router.navigate("/"); let B = await t.navigate("/child", { - formMethod: "POST", + formMethod: "post", formData: createFormData({}), }); - expect(t.router.state.navigation.formMethod).toBe("post"); + expect(t.router.state.navigation.formMethod).toBe("POST"); await B.actions.child.resolve("ACTION"); await B.loaders.child.resolve("LOADER"); expect(t.router.state.navigation.formMethod).toBeUndefined(); @@ -523,20 +523,20 @@ describe("submissions", () => { formMethod: "GET", formData: createFormData({}), }); - expect(t.router.state.fetchers.get("key")?.formMethod).toBe("get"); + expect(t.router.state.fetchers.get("key")?.formMethod).toBe("GET"); await C.loaders.child.resolve("LOADER FETCH"); expect(t.router.state.fetchers.get("key")?.formMethod).toBeUndefined(); let D = await t.fetch("/child", "key", { - formMethod: "post", + formMethod: "POST", formData: createFormData({}), }); - expect(t.router.state.fetchers.get("key")?.formMethod).toBe("post"); + expect(t.router.state.fetchers.get("key")?.formMethod).toBe("POST"); await D.actions.child.resolve("ACTION FETCH"); expect(t.router.state.fetchers.get("key")?.formMethod).toBeUndefined(); }); - it("normalizes to uppercase in v7 via v7_normalizeFormMethod", async () => { + it("normalizes to uppercase", async () => { let t = setup({ routes: [ { @@ -553,7 +553,6 @@ describe("submissions", () => { }, ], future: { - v7_normalizeFormMethod: true, v7_prependBasename: false, }, }); diff --git a/packages/react-router/__tests__/router/utils/data-router-setup.ts b/packages/react-router/__tests__/router/utils/data-router-setup.ts index 7dd6288328..b472e23d37 100644 --- a/packages/react-router/__tests__/router/utils/data-router-setup.ts +++ b/packages/react-router/__tests__/router/utils/data-router-setup.ts @@ -507,7 +507,7 @@ export function setup({ // Otherwise we should only need a loader for the leaf match let activeLoaderMatches = [match]; // @ts-expect-error - if (opts?.formMethod != null && opts.formMethod.toLowerCase() !== "get") { + if (opts?.formMethod != null && opts.formMethod.toUpperCase() !== "GET") { if (currentRouter.state.navigation?.location) { let matches = matchRoutes( inFlightRoutes || currentRouter.routes, @@ -572,7 +572,7 @@ export function setup({ invariant(currentRouter, "No currentRouter available"); // @ts-expect-error - if (opts?.formMethod != null && opts.formMethod.toLowerCase() !== "get") { + if (opts?.formMethod != null && opts.formMethod.toUpperCase() !== "GET") { activeActionType = "navigation"; activeActionNavigationId = navigationId; // Assume happy path and mark this navigations loaders as active. Even if @@ -662,7 +662,7 @@ export function setup({ invariant(currentRouter, "No currentRouter available"); // @ts-expect-error - if (opts?.formMethod != null && opts.formMethod.toLowerCase() !== "get") { + if (opts?.formMethod != null && opts.formMethod.toUpperCase() !== "GET") { activeActionType = "fetch"; activeActionFetchId = navigationId; } else { diff --git a/packages/react-router/index.ts b/packages/react-router/index.ts index 2fbb5905fd..2b58c74452 100644 --- a/packages/react-router/index.ts +++ b/packages/react-router/index.ts @@ -258,7 +258,6 @@ export type { GetScrollRestorationKeyFunction, StaticHandlerContext, Submission, - V7_FormMethod, } from "./lib/router"; export type { diff --git a/packages/react-router/lib/dom/dom.ts b/packages/react-router/lib/dom/dom.ts index de976a0176..23001f6dc9 100644 --- a/packages/react-router/lib/dom/dom.ts +++ b/packages/react-router/lib/dom/dom.ts @@ -59,9 +59,9 @@ export type URLSearchParamsInit = supports arrays as values in the object form of the initializer instead of just strings. This is convenient when you need multiple values for a given key, but don't want to use an array initializer. - + For example, instead of: - + ```tsx let searchParams = new URLSearchParams([ ['sort', 'name'], diff --git a/packages/react-router/lib/dom/server.tsx b/packages/react-router/lib/dom/server.tsx index 3530bfbe02..186ac0d82e 100644 --- a/packages/react-router/lib/dom/server.tsx +++ b/packages/react-router/lib/dom/server.tsx @@ -304,7 +304,6 @@ export function createStaticRouter( get future() { return { v7_fetcherPersist: false, - v7_normalizeFormMethod: false, v7_partialHydration: opts.future?.v7_partialHydration === true, v7_prependBasename: false, unstable_skipActionErrorRevalidation: false, diff --git a/packages/react-router/lib/dom/ssr/browser.tsx b/packages/react-router/lib/dom/ssr/browser.tsx index 14d31e7a25..15f35e67bc 100644 --- a/packages/react-router/lib/dom/ssr/browser.tsx +++ b/packages/react-router/lib/dom/ssr/browser.tsx @@ -162,7 +162,6 @@ function createHydratedRouter(): RemixRouter { history: createBrowserHistory(), basename: ssrInfo.context.basename, future: { - v7_normalizeFormMethod: true, v7_fetcherPersist: ssrInfo.context.future.v3_fetcherPersist, v7_partialHydration: true, v7_prependBasename: true, diff --git a/packages/react-router/lib/router/index.ts b/packages/react-router/lib/router/index.ts index 417f59ee37..650125c08a 100644 --- a/packages/react-router/lib/router/index.ts +++ b/packages/react-router/lib/router/index.ts @@ -35,7 +35,6 @@ export type { TrackedPromise, UIMatch, UpperCaseFormMethod, - V7_FormMethod, } from "./utils"; export { diff --git a/packages/react-router/lib/router/router.ts b/packages/react-router/lib/router/router.ts index aafa19810f..660f97bef7 100644 --- a/packages/react-router/lib/router/router.ts +++ b/packages/react-router/lib/router/router.ts @@ -33,8 +33,6 @@ import type { Submission, SuccessResult, UIMatch, - V7_FormMethod, - V7_MutationFormMethod, AgnosticPatchRoutesOnMissFunction, } from "./utils"; import { @@ -367,7 +365,6 @@ export type HydrationState = Partial< */ export interface FutureConfig { v7_fetcherPersist: boolean; - v7_normalizeFormMethod: boolean; v7_partialHydration: boolean; v7_prependBasename: boolean; unstable_skipActionErrorRevalidation: boolean; @@ -735,17 +732,17 @@ interface RevalidatingFetcher extends FetchLoadMatch { } const validMutationMethodsArr: MutationFormMethod[] = [ - "post", - "put", - "patch", - "delete", + "POST", + "PUT", + "PATCH", + "DELETE", ]; const validMutationMethods = new Set( validMutationMethodsArr ); const validRequestMethodsArr: FormMethod[] = [ - "get", + "GET", ...validMutationMethodsArr, ]; const validRequestMethods = new Set(validRequestMethodsArr); @@ -845,7 +842,6 @@ export function createRouter(init: RouterInit): Router { // Config driven behavior flags let future: FutureConfig = { v7_fetcherPersist: false, - v7_normalizeFormMethod: false, v7_partialHydration: false, v7_prependBasename: false, unstable_skipActionErrorRevalidation: false, @@ -1373,7 +1369,6 @@ export function createRouter(init: RouterInit): Router { opts?.relative ); let { path, submission, error } = normalizeNavigateOptions( - future.v7_normalizeFormMethod, false, normalizedPath, opts @@ -2180,7 +2175,6 @@ export function createRouter(init: RouterInit): Router { } let { path, submission, error } = normalizeNavigateOptions( - future.v7_normalizeFormMethod, true, normalizedPath, opts @@ -3668,7 +3662,7 @@ export function createStaticHandler( ); try { - if (isMutationMethod(request.method.toLowerCase())) { + if (isMutationMethod(request.method)) { let result = await submit( request, matches, @@ -4140,7 +4134,6 @@ function normalizeTo( // Normalize navigation options by converting formMethod=GET formData objects to // URLSearchParams so they behave identically to links with query params function normalizeNavigateOptions( - normalizeFormMethod: boolean, isFetcher: boolean, path: string, opts?: BaseNavigateOrFetchOptions @@ -4168,9 +4161,7 @@ function normalizeNavigateOptions( // Create a Submission on non-GET navigations let rawFormMethod = opts.formMethod || "get"; - let formMethod = normalizeFormMethod - ? (rawFormMethod.toUpperCase() as V7_FormMethod) - : (rawFormMethod.toLowerCase() as FormMethod); + let formMethod = rawFormMethod.toUpperCase() as FormMethod; let formAction = stripHashFromPath(path); if (opts.body !== undefined) { @@ -5501,14 +5492,12 @@ function isRedirectResponse(result: any): result is Response { return status >= 300 && status <= 399 && location != null; } -function isValidMethod(method: string): method is FormMethod | V7_FormMethod { - return validRequestMethods.has(method.toLowerCase() as FormMethod); +function isValidMethod(method: string): method is FormMethod { + return validRequestMethods.has(method.toUpperCase() as FormMethod); } -function isMutationMethod( - method: string -): method is MutationFormMethod | V7_MutationFormMethod { - return validMutationMethods.has(method.toLowerCase() as MutationFormMethod); +function isMutationMethod(method: string): method is MutationFormMethod { + return validMutationMethods.has(method.toUpperCase() as MutationFormMethod); } async function resolveDeferredResults( diff --git a/packages/react-router/lib/router/utils.ts b/packages/react-router/lib/router/utils.ts index 19f24b8be1..8547af9d81 100644 --- a/packages/react-router/lib/router/utils.ts +++ b/packages/react-router/lib/router/utils.ts @@ -82,18 +82,11 @@ export type UpperCaseFormMethod = Uppercase; export type HTMLFormMethod = LowerCaseFormMethod | UpperCaseFormMethod; /** - * Active navigation/fetcher form methods are exposed in lowercase on the - * RouterState + * Active navigation/fetcher form methods are exposed in uppercase on the + * RouterState. This is to align with the normalization done via fetch(). */ -export type FormMethod = LowerCaseFormMethod; -export type MutationFormMethod = Exclude; - -/** - * In v7, active navigation/fetcher form methods are exposed in uppercase on the - * RouterState. This is to align with the normalization done via fetch(). - */ -export type V7_FormMethod = UpperCaseFormMethod; -export type V7_MutationFormMethod = Exclude; +export type FormMethod = UpperCaseFormMethod; +export type MutationFormMethod = Exclude; export type FormEncType = | "application/x-www-form-urlencoded" @@ -116,7 +109,7 @@ type JsonValue = JsonPrimitive | JsonObject | JsonArray; */ export type Submission = | { - formMethod: FormMethod | V7_FormMethod; + formMethod: FormMethod; formAction: string; formEncType: FormEncType; formData: FormData; @@ -124,7 +117,7 @@ export type Submission = text: undefined; } | { - formMethod: FormMethod | V7_FormMethod; + formMethod: FormMethod; formAction: string; formEncType: FormEncType; formData: undefined; @@ -132,7 +125,7 @@ export type Submission = text: undefined; } | { - formMethod: FormMethod | V7_FormMethod; + formMethod: FormMethod; formAction: string; formEncType: FormEncType; formData: undefined;