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 `
` to be submitted to the server, a specific + * `