-
Notifications
You must be signed in to change notification settings - Fork 2.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #53227 from margelo/fix/do-not-call-submit-twice
fix: do not call submit function twice if user clicks fast (cherry picked from commit 51a2879) (CP triggered by mountiny)
- Loading branch information
Showing
2 changed files
with
81 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
// eslint-disable-next-line lodash/import-scope | ||
import type {DebouncedFunc, DebounceSettings} from 'lodash'; | ||
import lodashDebounce from 'lodash/debounce'; | ||
import {useCallback, useEffect, useRef} from 'react'; | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
type GenericFunction = (...args: any[]) => void; | ||
|
||
/** | ||
* Create and return a debounced function. | ||
* | ||
* Every time the identity of any of the arguments changes, the debounce operation will restart (canceling any ongoing debounce). | ||
* This hook doesn't react on function identity changes and will not cancel the debounce in case of function identity change. | ||
* This is important because we want to debounce the function call and not the function reference. | ||
* | ||
* @param func The function to debounce. | ||
* @param wait The number of milliseconds to delay. | ||
* @param options The options object. | ||
* @param options.leading Specify invoking on the leading edge of the timeout. | ||
* @param options.maxWait The maximum time func is allowed to be delayed before it’s invoked. | ||
* @param options.trailing Specify invoking on the trailing edge of the timeout. | ||
* @returns Returns a function to call the debounced function. | ||
*/ | ||
export default function useDebounceNonReactive<T extends GenericFunction>(func: T, wait: number, options?: DebounceSettings): T { | ||
const funcRef = useRef<T>(func); // Store the latest func reference | ||
const debouncedFnRef = useRef<DebouncedFunc<T>>(); | ||
const {leading, maxWait, trailing = true} = options ?? {}; | ||
|
||
useEffect(() => { | ||
// Update the funcRef dynamically to avoid recreating debounce | ||
funcRef.current = func; | ||
}, [func]); | ||
|
||
// Recreate the debounce instance only if debounce settings change | ||
useEffect(() => { | ||
const debouncedFn = lodashDebounce( | ||
(...args: Parameters<T>) => { | ||
funcRef.current(...args); // Use the latest func reference | ||
}, | ||
wait, | ||
{leading, maxWait, trailing}, | ||
); | ||
|
||
debouncedFnRef.current = debouncedFn; | ||
|
||
return () => { | ||
debouncedFn.cancel(); | ||
}; | ||
}, [wait, leading, maxWait, trailing]); | ||
|
||
const debounceCallback = useCallback((...args: Parameters<T>) => { | ||
debouncedFnRef.current?.(...args); | ||
}, []); | ||
|
||
// eslint-disable-next-line react-compiler/react-compiler | ||
return debounceCallback as T; | ||
} |