- Created useDebouncedCallback hook for debouncing callbacks - Created useThrottledCallback hook for throttling callbacks - Created usePreventDoubleClick hook for preventing double-clicks - All hooks are properly typed and documented with examples - Components can use these hooks to prevent rapid interactions - Existing ButtonLoading component already disables buttons during loading
67 lines
1.7 KiB
TypeScript
67 lines
1.7 KiB
TypeScript
import { useCallback, useRef, useState } from 'react';
|
|
|
|
/**
|
|
* Edge 3.2: Hook to prevent double-clicking on buttons
|
|
* Disables the callback for a short period after execution to prevent rapid clicks
|
|
*
|
|
* @param callback - The function to protect from double-clicks
|
|
* @param delay - Minimum time between executions in milliseconds (default: 500ms)
|
|
* @returns Object with protected callback and isProcessing state
|
|
*
|
|
* @example
|
|
* ```typescript
|
|
* const { handleClick, isProcessing } = usePreventDoubleClick(
|
|
* async () => {
|
|
* await submitForm();
|
|
* },
|
|
* 1000
|
|
* );
|
|
*
|
|
* <Button onClick={handleClick} disabled={isProcessing}>
|
|
* Submit
|
|
* </Button>
|
|
* ```
|
|
*/
|
|
export function usePreventDoubleClick<T extends (...args: any[]) => any>(
|
|
callback: T,
|
|
delay: number = 500,
|
|
): {
|
|
handleClick: T;
|
|
isProcessing: boolean;
|
|
} {
|
|
const [isProcessing, setIsProcessing] = useState(false);
|
|
const lastExecTimeRef = useRef<number>(0);
|
|
|
|
const handleClick = useCallback(
|
|
(async (...args: Parameters<T>) => {
|
|
const currentTime = Date.now();
|
|
const timeSinceLastExec = currentTime - lastExecTimeRef.current;
|
|
|
|
// Prevent execution if called too soon after last execution
|
|
if (timeSinceLastExec < delay) {
|
|
return;
|
|
}
|
|
|
|
// Prevent execution if already processing
|
|
if (isProcessing) {
|
|
return;
|
|
}
|
|
|
|
setIsProcessing(true);
|
|
lastExecTimeRef.current = currentTime;
|
|
|
|
try {
|
|
const result = await callback(...args);
|
|
return result;
|
|
} finally {
|
|
// Ensure minimum delay before allowing next execution
|
|
setTimeout(() => {
|
|
setIsProcessing(false);
|
|
}, delay);
|
|
}
|
|
}) as T,
|
|
[callback, delay, isProcessing],
|
|
);
|
|
|
|
return { handleClick, isProcessing };
|
|
}
|