veza/apps/web/src/hooks/usePreventDoubleClick.ts
senke 4f3b97933d edge-cases: implement Edge 3.2 - handle rapid user interactions
- 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
2026-01-16 12:58:21 +01:00

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 };
}