diff --git a/.changeset/normalize-form-method-deprecation.md b/.changeset/normalize-form-method-deprecation.md
new file mode 100644
index 00000000000..9e91779965b
--- /dev/null
+++ b/.changeset/normalize-form-method-deprecation.md
@@ -0,0 +1,6 @@
+---
+"@remix-run/dev": minor
+"@remix-run/react": minor
+---
+
+Add deprecation warnings for `v2_normalizeFormMethod`
diff --git a/docs/hooks/use-navigation.md b/docs/hooks/use-navigation.md
index 620620d5f09..22f915491b0 100644
--- a/docs/hooks/use-navigation.md
+++ b/docs/hooks/use-navigation.md
@@ -12,14 +12,17 @@ import { useNavigation } from "@remix-run/react";
function SomeComponent() {
const navigation = useNavigation();
- navigation.state;
- navigation.location;
- navigation.formData;
- navigation.formAction;
- navigation.formMethod;
+ navigation.state; // "idle" | "submitting" | "loading"
+ navigation.location; // Location being navigated to
+ navigation.formData; // formData being submitted
+ navigation.formAction; // url being submitted to
+ navigation.formMethod; // "GET" | "POST" | "PATCH" | "PUT" | "DELETE"
}
```
+The `useNavigation().formMethod` field is lowercase without the `future.v2_normalizeFormMethod` [Future Flag][api-development-strategy]. This is being normalized to uppercase to align with the `fetch()` behavior in v2, so please upgrade your Remix v1 applications to adopt the uppercase HTTP methods.
+
For more information and usage, please refer to the [React Router `useNavigation` docs][rr-usenavigation].
[rr-usenavigation]: https://reactrouter.com/hooks/use-navigation
+[api-development-strategy]: ../pages/api-development-strategy
diff --git a/integration/action-test.ts b/integration/action-test.ts
index dee315b2a55..02e3a900933 100644
--- a/integration/action-test.ts
+++ b/integration/action-test.ts
@@ -20,6 +20,7 @@ test.describe("actions", () => {
future: {
v2_routeConvention: true,
v2_errorBoundary: true,
+ v2_normalizeFormMethod: true,
},
files: {
"app/routes/urlencoded.jsx": js`
diff --git a/integration/defer-test.ts b/integration/defer-test.ts
index edac58789e1..f72e0cbebe6 100644
--- a/integration/defer-test.ts
+++ b/integration/defer-test.ts
@@ -36,6 +36,7 @@ test.describe("non-aborted", () => {
future: {
v2_routeConvention: true,
v2_errorBoundary: true,
+ v2_normalizeFormMethod: true,
},
files: {
"app/components/counter.tsx": js`
diff --git a/integration/hmr-test.ts b/integration/hmr-test.ts
index bd08171da42..33b6894e22f 100644
--- a/integration/hmr-test.ts
+++ b/integration/hmr-test.ts
@@ -17,6 +17,7 @@ let fixture = (options: { port: number; appServerPort: number }) => ({
unstable_tailwind: true,
v2_routeConvention: true,
v2_errorBoundary: true,
+ v2_normalizeFormMethod: true,
},
files: {
"package.json": json({
diff --git a/packages/remix-dev/__tests__/create-test.ts b/packages/remix-dev/__tests__/create-test.ts
index 2f9eb196b83..c905ad83d09 100644
--- a/packages/remix-dev/__tests__/create-test.ts
+++ b/packages/remix-dev/__tests__/create-test.ts
@@ -8,7 +8,11 @@ import stripAnsi from "strip-ansi";
import { run } from "../cli/run";
import { server } from "./msw";
-import { errorBoundaryWarning, flatRoutesWarning } from "../config";
+import {
+ errorBoundaryWarning,
+ flatRoutesWarning,
+ formMethodWarning,
+} from "../config";
beforeAll(() => server.listen({ onUnhandledRequest: "error" }));
afterAll(() => server.close());
@@ -349,6 +353,8 @@ describe("the create command", () => {
]);
expect(output.trim()).toBe(
errorBoundaryWarning +
+ "\n" +
+ formMethodWarning +
"\n" +
flatRoutesWarning +
"\n\n" +
diff --git a/packages/remix-dev/config.ts b/packages/remix-dev/config.ts
index 1bd1f30220e..3749e44f582 100644
--- a/packages/remix-dev/config.ts
+++ b/packages/remix-dev/config.ts
@@ -410,6 +410,10 @@ export async function readConfig(
warnOnce(errorBoundaryWarning, "v2_errorBoundary");
}
+ if (!appConfig.future?.v2_normalizeFormMethod) {
+ warnOnce(formMethodWarning, "v2_normalizeFormMethod");
+ }
+
let isCloudflareRuntime = ["cloudflare-pages", "cloudflare-workers"].includes(
appConfig.serverBuildTarget ?? ""
);
@@ -775,3 +779,9 @@ export const errorBoundaryWarning =
"behavior in Remix v1 via the `future.v2_errorBoundary` flag in your " +
"`remix.config.js` file. For more information, see " +
"https://remix.run/docs/route/error-boundary-v2";
+
+export const formMethodWarning =
+ "⚠️ DEPRECATED: Please enable the `future.v2_normalizeFormMethod` flag to " +
+ "prepare for the Remix v2 release. Lowercase `useNavigation().formMethod`" +
+ "values are being normalized to uppercase in v2 to align with the `fetch()` " +
+ "behavior. For more information, see https://remix.run/docs/hooks/use-navigation";
diff --git a/packages/remix-react/browser.tsx b/packages/remix-react/browser.tsx
index 6286c367398..35993a5d4d2 100644
--- a/packages/remix-react/browser.tsx
+++ b/packages/remix-react/browser.tsx
@@ -151,6 +151,16 @@ export function RemixBrowser(_props: RemixBrowserProps): ReactElement {
);
}
+ if (!window.__remixContext.future.v2_normalizeFormMethod) {
+ warnOnce(
+ false,
+ "⚠️ DEPRECATED: Please enable the `future.v2_normalizeFormMethod` flag to " +
+ "prepare for the Remix v2 release. Lowercase `useNavigation().formMethod`" +
+ "values are being normalized to uppercase in v2 to align with the `fetch()` " +
+ "behavior. For more information, see https://remix.run/docs/hooks/use-navigation"
+ );
+ }
+
let routes = createClientRoutes(
window.__remixManifest.routes,
window.__remixRouteModules,