diff --git a/jam-ui/src/hooks/useDebounceCallback.js b/jam-ui/src/hooks/useDebounceCallback.js new file mode 100644 index 000000000..a3b18f2d1 --- /dev/null +++ b/jam-ui/src/hooks/useDebounceCallback.js @@ -0,0 +1,42 @@ +import { useRef, useEffect, useMemo } from 'react'; +import { debounce } from 'lodash'; + +/** + * Hook that creates a stable debounced callback with fresh closure access. + * Solves the problem of debounce recreation when dependencies change. + * + * Pattern: useRef stores latest callback, useMemo creates debounce once. + * Source: https://www.developerway.com/posts/debouncing-in-react + * + * @param {Function} callback - The callback to debounce + * @param {number} delay - Debounce delay in ms (default: 500) + * @returns {Function} Debounced function that always uses latest callback + */ +export const useDebounceCallback = (callback, delay = 500) => { + const callbackRef = useRef(callback); + + // Always keep ref current with latest callback + useEffect(() => { + callbackRef.current = callback; + }, [callback]); + + // Create debounced function once - never recreated + const debouncedFn = useMemo( + () => + debounce((...args) => { + if (callbackRef.current) { + callbackRef.current(...args); + } + }, delay), + [delay] // Only recreate if delay changes + ); + + // Cleanup on unmount + useEffect(() => { + return () => debouncedFn.cancel(); + }, [debouncedFn]); + + return debouncedFn; +}; + +export default useDebounceCallback;