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