useCopyToClipboard
Copy text to clipboard with success/error state.
Use Cases
- Copy code snippets
- Share links
- Copy referral codes
- Copy API keys or tokens
Code
import { useState, useCallback } from 'react';
interface UseCopyToClipboardReturn {
copied: boolean;
error: Error | null;
copy: (text: string) => Promise<boolean>;
reset: () => void;
}
export function useCopyToClipboard(
resetDelay: number = 2000
): UseCopyToClipboardReturn {
const [copied, setCopied] = useState(false);
const [error, setError] = useState<Error | null>(null);
const copy = useCallback(
async (text: string): Promise<boolean> => {
if (!navigator?.clipboard) {
const error = new Error('Clipboard API not supported');
setError(error);
return false;
}
try {
await navigator.clipboard.writeText(text);
setCopied(true);
setError(null);
if (resetDelay > 0) {
setTimeout(() => {
setCopied(false);
}, resetDelay);
}
return true;
} catch (err) {
const error = err instanceof Error ? err : new Error('Copy failed');
setError(error);
setCopied(false);
return false;
}
},
[resetDelay]
);
const reset = useCallback(() => {
setCopied(false);
setError(null);
}, []);
return { copied, error, copy, reset };
}Usage
Copy Button
import { Check, Copy } from 'lucide-react';
import { useCopyToClipboard } from '@/hooks/useCopyToClipboard';
import { Button } from '@/components/ui/button';
function CopyButton({ text }: { text: string }) {
const { copied, copy } = useCopyToClipboard();
return (
<Button
variant="outline"
size="sm"
onClick={() => copy(text)}
>
{copied ? (
<>
<Check className="mr-2 h-4 w-4" />
Copied!
</>
) : (
<>
<Copy className="mr-2 h-4 w-4" />
Copy
</>
)}
</Button>
);
}Code Block with Copy
function CodeBlock({ code, language }: { code: string; language: string }) {
const { copied, copy } = useCopyToClipboard();
return (
<div className="relative rounded-lg bg-gray-900 p-4">
<button
onClick={() => copy(code)}
className="absolute right-2 top-2 rounded bg-gray-700 px-2 py-1 text-xs text-white hover:bg-gray-600"
>
{copied ? 'Copied!' : 'Copy'}
</button>
<pre className="text-gray-100">
<code>{code}</code>
</pre>
</div>
);
}Share Link
import { toast } from 'sonner';
function ShareButton({ url }: { url: string }) {
const { copy } = useCopyToClipboard();
const handleShare = async () => {
const success = await copy(url);
if (success) {
toast.success('Link copied to clipboard');
} else {
toast.error('Failed to copy link');
}
};
return (
<button onClick={handleShare}>
Share
</button>
);
}Copy with Icon Toggle
function CopyIconButton({ text }: { text: string }) {
const { copied, copy } = useCopyToClipboard(1500);
return (
<button
onClick={() => copy(text)}
className="rounded p-2 hover:bg-gray-100"
title={copied ? 'Copied!' : 'Copy to clipboard'}
>
{copied ? (
<Check className="h-4 w-4 text-green-500" />
) : (
<Copy className="h-4 w-4 text-gray-500" />
)}
</button>
);
}With Toast Notification
function useCopyWithToast() {
const { copy } = useCopyToClipboard(0); // No auto-reset needed
return async (text: string, label?: string) => {
const success = await copy(text);
if (success) {
toast.success(label ? `${label} copied` : 'Copied to clipboard');
} else {
toast.error('Failed to copy');
}
return success;
};
}
// Usage
const copyWithToast = useCopyWithToast();
copyWithToast(user.email, 'Email');