diff --git a/README.md b/README.md
index a28e8a7..76a9d2b 100644
--- a/README.md
+++ b/README.md
@@ -1759,16 +1759,14 @@ export default function Component() {
Now you can see in your DevTools that when the user hovers an anchor it will prefetch it, and when the user clicks it will do a client-side navigation.
-### Debounced Fetcher
+### Debounced Fetcher and Submit
> **Note**
> This depends on `react`, and `@remix-run/react`.
-The `useDebounceFetcher` is a wrapper of `useFetcher` that adds debounce support to `fetcher.submit`.
+`useDebounceFetcher` and `useDebounceSubmit` are wrappers of `useFetcher` and `useSubmit` that add debounce support.
-The hook is based on [@JacobParis](https://github.com/JacobParis)' [article](https://www.jacobparis.com/content/use-debounce-fetcher).
-
-The main difference with Jacob's version is that Remix Utils' version overwrites `fetcher.submit` instead of appending a `fetcher.debounceSubmit` method.
+These hooks are based on [@JacobParis](https://github.com/JacobParis)' [article](https://www.jacobparis.com/content/use-debounce-fetcher).
```tsx
import { useDebounceFetcher } from "remix-utils/use-debounce-fetcher";
@@ -1788,6 +1786,37 @@ export function Component({ data }) {
}
```
+Usage with `useDebounceSubmit` is similar.
+
+```tsx
+import { useDebounceSubmit } from "remix-utils/use-debounce-submit";
+
+export function Component({ name }) {
+ let submit = useDebounceSubmit();
+
+ return (
+ {
+ submit(event.target.form, {
+ navigate: false, // use a fetcher instead of a page navigation
+ fetcherKey: name, // cancel any previous fetcher with the same key
+ debounceTimeout: 1000,
+ });
+ }}
+ onBlur={() => {
+ submit(event.target.form, {
+ navigate: false,
+ fetcherKey: name,
+ debounceTimeout: 0, // submit immediately, canceling any pending fetcher
+ });
+ }}
+ />
+ );
+}
+```
+
### Derive Fetcher Type
> **Note**
diff --git a/package.json b/package.json
index 23c80c3..87fbd17 100644
--- a/package.json
+++ b/package.json
@@ -30,6 +30,7 @@
"./fetcher-type": "./build/react/fetcher-type.js",
"./server-only": "./build/react/server-only.js",
"./use-debounce-fetcher": "./build/react/use-debounce-fetcher.js",
+ "./use-debounce-submit": "./build/react/use-debounce-submit.js",
"./use-delegated-anchors": "./build/react/use-delegated-anchors.js",
"./use-global-navigation-state": "./build/react/use-global-navigation-state.js",
"./use-hydrated": "./build/react/use-hydrated.js",
diff --git a/src/react/use-debounce-submit.ts b/src/react/use-debounce-submit.ts
new file mode 100644
index 0000000..6d4e020
--- /dev/null
+++ b/src/react/use-debounce-submit.ts
@@ -0,0 +1,55 @@
+import type { SubmitOptions, SubmitFunction } from "@remix-run/react";
+import { useSubmit } from "@remix-run/react";
+import { useCallback, useEffect, useRef } from "react";
+
+type SubmitTarget = Parameters["0"];
+
+export function useDebounceSubmit() {
+ let timeoutRef = useRef();
+
+ useEffect(() => {
+ // no initialize step required since timeoutRef defaults undefined
+ let timeout = timeoutRef.current;
+ return () => {
+ if (timeout) clearTimeout(timeout);
+ };
+ }, [timeoutRef]);
+
+ // Clone the original submit to avoid a recursive loop
+ const originalSubmit = useSubmit();
+
+ const submit = useCallback(
+ (
+ /**
+ * Specifies the `