useCopyToClipboard
Copy text to the clipboard with feedback state and toast notifications.
View source
Features
- Automatic clipboard copying with feedback
- Configurable timeout for reset state
- Toast notifications for success/error states
- Error handling with optional persistence
- Cleanup on unmount
use-copy-to-clipboard.ts
1'use client';2
3
4
5interface UseCopyToClipboardOptions {6 /** Timeout in milliseconds to reset the copied state. Defaults to 2000ms. */7 timeout?: number;8 /** Whether copying is enabled. Can be a boolean or a function that receives the current copied state. */9 enabled?: boolean | ((isCopied: boolean) => boolean);10 /** Message to display on successful copy. */11 successMessage?: string;12 /** Message to display on copy failure. Defaults to 'Failed to copy to clipboard'. */13 errorMessage?: string;14 /** Whether to persist the error state after timeout. Defaults to false. */15 persistError?: boolean;16 /** Whether to show toast notifications. Defaults to true. */17 showToast?: boolean;18}19
20/**21 * A hook to copy text to the clipboard with visual feedback.22 *23 * When the text is copied, `isCopied` is set to true for the timeout period.24 * If copied again before timeout expires, the timeout resets.25 *26 * @param text - The text to copy to the clipboard.27 * @param options - Configuration options for the copy behavior.28 *29 * @example30 * ```tsx31 * const { handleCopy, isCopied } = useCopyToClipboard('Hello, World!', {32 * successMessage: 'Copied!',33 * });34 *35 * return (36 * <button onClick={handleCopy}>37 * {isCopied ? 'Copied!' : 'Copy'}38 * </button>39 * );40 * ```41 */42function useCopyToClipboard(text: string, options: UseCopyToClipboardOptions = {}) {43 const {44 timeout = 2000,45 enabled = true,46 successMessage,47 errorMessage = 'Failed to copy to clipboard',48 persistError = false,49 showToast = true,50 } = options;51
52 const [isCopied, setIsCopied] = useState(false);53 const [error, setError] = useState<string | null>(null);54 const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);55
56 const handleCopy = useCallback(async () => {57 if (typeof enabled === 'function' ? !enabled(isCopied) : !enabled) return;58
59 if (timeoutRef.current) {60 clearTimeout(timeoutRef.current);61 }62
63 setError(null);64
65 try {66 if (!navigator.clipboard) {67 throw new Error('Clipboard API not supported');68 }69
70 await navigator.clipboard.writeText(text);71
72 if (showToast && successMessage) {73 toastManager.add({74 title: successMessage,75 type: 'success',76 });77 }78
79 setIsCopied(true);80
81 timeoutRef.current = setTimeout(() => {82 setIsCopied(false);83 }, timeout);84
85 return true;86 } catch (err) {87 if (showToast) {88 toastManager.add({89 title: errorMessage,90 type: 'error',91 });92 }93
94 const errorMsg = err instanceof Error ? err.message : 'Failed to copy to clipboard';95 setError(errorMsg);96 setIsCopied(false);97
98 if (!persistError) {99 timeoutRef.current = setTimeout(() => {100 setError(null);101 }, timeout);102 }103
104 return false;105 }106 }, [text, timeout, successMessage, errorMessage, persistError, showToast, enabled, isCopied]);107
108 const reset = useCallback(() => {109 setIsCopied(false);110 setError(null);111 if (timeoutRef.current) {112 clearTimeout(timeoutRef.current);113 }114 }, []);115
116 useEffect(() => {117 return () => {118 reset();119 };120 }, [reset]);121
122 return {123 isCopied,124 error,125 handleCopy,126 reset,127 };128}129
130export { useCopyToClipboard, type UseCopyToClipboardOptions };