useCopyToClipboard
React hook for copying text to clipboard with feedback, error handling, and modern Clipboard API support with legacy fallbacks.
The useCopyToClipboard hook provides a simple interface for copying text to the clipboard using the modern Clipboard API with fallback to legacy methods. It includes loading states, success feedback, and comprehensive error handling for a seamless user experience.
Basic Usage
import { useCopyToClipboard } from 'light-hooks';
function ShareComponent() {
  const { copy, copied, error, loading } = useCopyToClipboard();
  const shareUrl = 'https://example.com/article/123';
  const handleCopy = () => {
    copy(shareUrl);
  };
  return (
    <div>
      <input value={shareUrl} readOnly />
      <button onClick={handleCopy} disabled={loading}>
        {loading ? 'Copying...' : copied ? 'Copied!' : 'Copy'}
      </button>
      {error && <p style={{ color: 'red' }}>Failed to copy: {error}</p>}
    </div>
  );
}
API Reference
Parameters
| Parameter | Type | Description | 
|---|---|---|
options | UseCopyToClipboardOptions | Configuration options for copy behavior (optional) | 
Options
interface UseCopyToClipboardOptions {
  /**
   * Duration in milliseconds to show success state
   * @default 2000
   */
  successDuration?: number;
  /**
   * Whether to reset the state after the success duration
   * @default true
   */
  resetAfterDelay?: boolean;
  /**
   * Custom success message
   * @default 'Copied to clipboard!'
   */
  successMessage?: string;
}
Return Value
interface UseCopyToClipboardReturn {
  copiedValue: string | null;        // The last copied value
  copied: boolean;                   // Whether the copy operation was successful
  error: string | null;              // Error message if copy failed
  loading: boolean;                  // Whether a copy operation is in progress
  copy: (text: string) => Promise<boolean>;  // Function to copy text to clipboard
  reset: () => void;                 // Function to reset the state
}
Examples
Code Block with Copy Functionality
function CodeBlock({ code, language }: { code: string; language: string }) {
  const { copy, copied, error } = useCopyToClipboard({
    successDuration: 1000,
    successMessage: 'Code copied!'
  });
  return (
    <div className="code-block">
      <div className="code-header">
        <span className="language">{language}</span>
        <button 
          className="copy-button"
          onClick={() => copy(code)}
        >
          {copied ? 'β Copied' : 'π Copy'}
        </button>
      </div>
      <pre>
        <code>{code}</code>
      </pre>
      {error && (
        <div className="error">
          Failed to copy code: {error}
        </div>
      )}
    </div>
  );
}
Share Button with Multiple Options
function ShareButtons({ title, url }: { title: string; url: string }) {
  const { copy, copied, copiedValue, loading } = useCopyToClipboard({
    successDuration: 3000
  });
  const shareTexts = {
    url: url,
    title: title,
    full: `${title} - ${url}`,
    markdown: `[${title}](${url})`,
    html: `<a href="${url}">${title}</a>`
  };
  return (
    <div className="share-buttons">
      <h3>Share this article</h3>
      
      {Object.entries(shareTexts).map(([type, text]) => (
        <button
          key={type}
          onClick={() => copy(text)}
          disabled={loading}
          className={copiedValue === text ? 'copied' : ''}
        >
          {copiedValue === text ? 'Copied!' : `Copy ${type.toUpperCase()}`}
        </button>
      ))}
      
      {copied && (
        <div className="success-message">
          Successfully copied to clipboard!
        </div>
      )}
    </div>
  );
}
Contact Information Cards
function ContactCard({ contact }: { contact: Contact }) {
  const { copy, copied, copiedValue } = useCopyToClipboard();
  const copyableFields = [
    { label: 'Email', value: contact.email, icon: 'π§' },
    { label: 'Phone', value: contact.phone, icon: 'π±' },
    { label: 'Address', value: contact.address, icon: 'π ' },
    { label: 'Website', value: contact.website, icon: 'π' }
  ];
  return (
    <div className="contact-card">
      <h3>{contact.name}</h3>
      
      {copyableFields.map(({ label, value, icon }) => (
        <div key={label} className="contact-field">
          <span className="icon">{icon}</span>
          <span className="label">{label}:</span>
          <span className="value">{value}</span>
          <button
            onClick={() => copy(value)}
            className={`copy-btn ${copiedValue === value ? 'copied' : ''}`}
          >
            {copiedValue === value ? 'β' : 'π'}
          </button>
        </div>
      ))}
    </div>
  );
}
Form Data Copy Functionality
function FormDataExporter() {
  const [formData, setFormData] = useState({
    name: 'John Doe',
    email: 'john@example.com',
    preferences: { theme: 'dark', notifications: true }
  });
  const { copy, copied, error, reset } = useCopyToClipboard({
    successDuration: 2000,
    resetAfterDelay: true
  });
  const exportFormats = {
    json: () => JSON.stringify(formData, null, 2),
    csv: () => Object.entries(formData).map(([key, value]) => 
      `${key},${typeof value === 'object' ? JSON.stringify(value) : value}`
    ).join('\n'),
    query: () => new URLSearchParams(
      Object.fromEntries(
        Object.entries(formData).map(([key, value]) => 
          [key, typeof value === 'object' ? JSON.stringify(value) : String(value)]
        )
      )
    ).toString()
  };
  return (
    <div className="form-exporter">
      <h3>Export Form Data</h3>
      
      <div className="form-data">
        <pre>{JSON.stringify(formData, null, 2)}</pre>
      </div>
      
      <div className="export-buttons">
        {Object.entries(exportFormats).map(([format, generator]) => (
          <button
            key={format}
            onClick={() => copy(generator())}
            className="export-btn"
          >
            Copy as {format.toUpperCase()}
          </button>
        ))}
      </div>
      
      {copied && (
        <div className="success">
          β
 Form data copied to clipboard!
          <button onClick={reset}>Clear</button>
        </div>
      )}
      
      {error && (
        <div className="error">
          β Failed to copy: {error}
          <button onClick={reset}>Dismiss</button>
        </div>
      )}
    </div>
  );
}
Advanced Examples
Bulk Copy Operations
function BulkCopyManager({ items }: { items: string[] }) {
  const { copy, copied, copiedValue, loading } = useCopyToClipboard();
  const [selectedItems, setSelectedItems] = useState<string[]>([]);
  const copySelected = () => {
    const selectedText = selectedItems.join('\n');
    copy(selectedText);
  };
  const copyAll = () => {
    copy(items.join('\n'));
  };
  const toggleSelection = (item: string) => {
    setSelectedItems(prev => 
      prev.includes(item) 
        ? prev.filter(i => i !== item)
        : [...prev, item]
    );
  };
  return (
    <div className="bulk-copy-manager">
      <div className="controls">
        <button onClick={copyAll} disabled={loading}>
          Copy All ({items.length} items)
        </button>
        <button 
          onClick={copySelected} 
          disabled={loading || selectedItems.length === 0}
        >
          Copy Selected ({selectedItems.length} items)
        </button>
      </div>
      <div className="items-list">
        {items.map((item, index) => (
          <div key={index} className="item">
            <input
              type="checkbox"
              checked={selectedItems.includes(item)}
              onChange={() => toggleSelection(item)}
            />
            <span className="item-text">{item}</span>
            <button
              onClick={() => copy(item)}
              className={copiedValue === item ? 'copied' : ''}
            >
              {copiedValue === item ? 'β' : 'π'}
            </button>
          </div>
        ))}
      </div>
      {copied && (
        <div className="status">
          Successfully copied to clipboard!
        </div>
      )}
    </div>
  );
}
QR Code Generator with Copy
function QRCodeGenerator() {
  const [text, setText] = useState('');
  const [qrCode, setQrCode] = useState('');
  const { copy, copied, error } = useCopyToClipboard();
  const generateQR = async () => {
    // Simulate QR code generation
    const qrUrl = `https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${encodeURIComponent(text)}`;
    setQrCode(qrUrl);
  };
  const copyQRLink = () => {
    copy(qrCode);
  };
  const copyOriginalText = () => {
    copy(text);
  };
  return (
    <div className="qr-generator">
      <h3>QR Code Generator</h3>
      
      <div className="input-section">
        <textarea
          value={text}
          onChange={(e) => setText(e.target.value)}
          placeholder="Enter text to generate QR code..."
        />
        <button onClick={generateQR} disabled={!text}>
          Generate QR Code
        </button>
      </div>
      {qrCode && (
        <div className="qr-result">
          <img src={qrCode} alt="QR Code" />
          
          <div className="copy-options">
            <button onClick={copyOriginalText}>
              {copied ? 'β Text Copied' : 'Copy Original Text'}
            </button>
            <button onClick={copyQRLink}>
              {copied ? 'β QR Link Copied' : 'Copy QR Code URL'}
            </button>
          </div>
        </div>
      )}
      {error && (
        <div className="error">
          Failed to copy: {error}
        </div>
      )}
    </div>
  );
}
API Response Formatter
function APIResponseFormatter({ apiResponse }: { apiResponse: any }) {
  const { copy, copied, copiedValue, loading } = useCopyToClipboard({
    successDuration: 1500
  });
  const formatters = {
    pretty: () => JSON.stringify(apiResponse, null, 2),
    minified: () => JSON.stringify(apiResponse),
    curl: () => `curl -X GET "${apiResponse.url}" -H "Accept: application/json"`,
    javascript: () => `fetch('${apiResponse.url}').then(res => res.json()).then(data => console.log(data));`,
    python: () => `import requests\nresponse = requests.get('${apiResponse.url}')\ndata = response.json()`,
    typescript: () => `interface ApiResponse {\n${Object.keys(apiResponse.data || {}).map(key => `  ${key}: any;`).join('\n')}\n}`
  };
  return (
    <div className="api-formatter">
      <h3>API Response Formatter</h3>
      
      <div className="format-buttons">
        {Object.entries(formatters).map(([format, formatter]) => (
          <button
            key={format}
            onClick={() => copy(formatter())}
            disabled={loading}
            className={copiedValue === formatter() ? 'active' : ''}
          >
            {format.charAt(0).toUpperCase() + format.slice(1)}
            {copiedValue === formatter() && ' β'}
          </button>
        ))}
      </div>
      <div className="preview">
        <pre>{JSON.stringify(apiResponse, null, 2)}</pre>
      </div>
      {copied && (
        <div className="success-toast">
          π Copied to clipboard!
        </div>
      )}
    </div>
  );
}
Social Media Content Generator
function SocialMediaGenerator({ article }: { article: Article }) {
  const { copy, copied, copiedValue } = useCopyToClipboard({
    successDuration: 2500
  });
  const socialTemplates = {
    twitter: `π Just read: "${article.title}"\n\n${article.summary}\n\nπ ${article.url}\n\n#webdev #technology`,
    
    linkedin: `I recently came across this insightful article: "${article.title}"\n\nKey takeaways:\n${article.keyPoints?.map(point => `β’ ${point}`).join('\n')}\n\nWhat are your thoughts on this topic?\n\n${article.url}`,
    
    facebook: `π Sharing an interesting read:\n\n"${article.title}"\n\n${article.summary}\n\nCheck it out: ${article.url}`,
    
    reddit: `**${article.title}**\n\n${article.summary}\n\nThought this community might find this interesting!\n\n[Read more](${article.url})`,
    
    discord: `π **${article.title}**\n${article.summary}\n\n${article.url}`
  };
  return (
    <div className="social-generator">
      <h3>Social Media Content Generator</h3>
      
      <div className="article-preview">
        <h4>{article.title}</h4>
        <p>{article.summary}</p>
      </div>
      <div className="social-platforms">
        {Object.entries(socialTemplates).map(([platform, template]) => (
          <div key={platform} className="platform-card">
            <div className="platform-header">
              <h4>{platform.charAt(0).toUpperCase() + platform.slice(1)}</h4>
              <button
                onClick={() => copy(template)}
                className={copiedValue === template ? 'copied' : ''}
              >
                {copiedValue === template ? 'β Copied' : 'Copy'}
              </button>
            </div>
            <div className="template-preview">
              <pre>{template}</pre>
            </div>
          </div>
        ))}
      </div>
      {copied && (
        <div className="global-success">
          β
 Content copied! Ready to paste on social media.
        </div>
      )}
    </div>
  );
}
Debugging Information Collector
function DebugInfoCollector() {
  const { copy, copied, loading, error } = useCopyToClipboard();
  const collectDebugInfo = () => {
    const debugInfo = {
      timestamp: new Date().toISOString(),
      userAgent: navigator.userAgent,
      url: window.location.href,
      viewport: {
        width: window.innerWidth,
        height: window.innerHeight
      },
      screen: {
        width: screen.width,
        height: screen.height,
        colorDepth: screen.colorDepth
      },
      connection: (navigator as any).connection ? {
        effectiveType: (navigator as any).connection.effectiveType,
        downlink: (navigator as any).connection.downlink
      } : 'Unknown',
      localStorage: Object.keys(localStorage).length,
      sessionStorage: Object.keys(sessionStorage).length,
      cookies: document.cookie ? 'Enabled' : 'Disabled'
    };
    const formatted = `=== DEBUG INFORMATION ===
Timestamp: ${debugInfo.timestamp}
URL: ${debugInfo.url}
User Agent: ${debugInfo.userAgent}
Viewport: ${debugInfo.viewport.width}x${debugInfo.viewport.height}
Screen: ${debugInfo.screen.width}x${debugInfo.screen.height} (${debugInfo.screen.colorDepth}-bit)
Connection: ${typeof debugInfo.connection === 'object' ? 
  `${debugInfo.connection.effectiveType} (${debugInfo.connection.downlink} Mbps)` : 
  debugInfo.connection}
Storage:
- localStorage items: ${debugInfo.localStorage}
- sessionStorage items: ${debugInfo.sessionStorage}
- Cookies: ${debugInfo.cookies}
=== END DEBUG INFO ===`;
    copy(formatted);
  };
  return (
    <div className="debug-collector">
      <h3>Debug Information Collector</h3>
      <p>Collect system information for debugging purposes</p>
      
      <button
        onClick={collectDebugInfo}
        disabled={loading}
        className="collect-btn"
      >
        {loading ? 'Collecting...' : copied ? 'β Debug Info Copied' : 'π Collect Debug Info'}
      </button>
      {copied && (
        <div className="success-instructions">
          <p>β
 Debug information copied to clipboard!</p>
          <p>You can now paste this information when reporting issues.</p>
        </div>
      )}
      {error && (
        <div className="error-message">
          β Failed to collect debug info: {error}
        </div>
      )}
    </div>
  );
}
Browser Compatibility
The hook automatically handles browser compatibility:
- Modern browsers: Uses the Clipboard API for secure, modern copying
 - Legacy browsers: Falls back to 
document.execCommand('copy') - HTTPS requirement: Clipboard API requires HTTPS in production
 - Permission handling: Automatically requests clipboard permissions when needed
 
Error Handling
Common error scenarios and how the hook handles them:
function ErrorHandlingExample() {
  const { copy, error, reset } = useCopyToClipboard();
  const handleCopy = async (text: string) => {
    const success = await copy(text);
    
    if (!success && error) {
      // Handle specific error types
      if (error.includes('permission')) {
        alert('Please grant clipboard permission');
      } else if (error.includes('https')) {
        alert('Clipboard access requires HTTPS');
      } else {
        alert('Copy failed: ' + error);
      }
    }
  };
  return (
    <div>
      <button onClick={() => handleCopy('Hello World')}>
        Copy Text
      </button>
      {error && (
        <div className="error">
          {error}
          <button onClick={reset}>Dismiss</button>
        </div>
      )}
    </div>
  );
}
TypeScript Support
The hook provides comprehensive TypeScript support:
const {
  copiedValue,  // string | null
  copied,       // boolean
  error,        // string | null
  loading,      // boolean
  copy,         // (text: string) => Promise<boolean>
  reset         // () => void
}: UseCopyToClipboardReturn = useCopyToClipboard({
  successDuration: 2000,
  resetAfterDelay: true,
  successMessage: 'Copied!'
});
Performance Tips
- Debounce rapid clicks: Prevent multiple copy operations
 - Use loading state: Disable buttons during copy operations
 - Reset state: Clear success/error states when appropriate
 - Handle permissions: Check clipboard permissions beforehand
 - Provide feedback: Always show copy status to users
 
Common Use Cases
- π Code Snippets: Copy code blocks in documentation
 - π URL Sharing: Share links and references
 - π± Contact Info: Copy phone numbers, emails, addresses
 - π³ Credentials: Copy API keys, tokens (with security considerations)
 - π Data Export: Copy formatted data (JSON, CSV, etc.)
 - π― Social Sharing: Generate and copy social media content
 - π Debug Info: Collect system information for support
 - π Form Data: Export form contents in various formats
 
Security Considerations
- Sensitive Data: Be cautious when copying passwords or API keys
 - HTTPS Only: Modern clipboard API requires secure contexts
 - User Consent: Always provide clear feedback about what's being copied
 - Sanitize Input: Clean user input before copying
 - Audit Logs: Consider logging copy operations for sensitive data
 
Best Practices
- Provide Clear Feedback: Always show copy status and success messages
 - Handle Errors Gracefully: Provide fallback options when copy fails
 - Use Appropriate Timeouts: Balance user experience with state management
 - Implement Keyboard Shortcuts: Support Ctrl+C for accessibility
 - Test Across Browsers: Ensure compatibility with target browsers
 - Consider Mobile: Test touch interactions and mobile browsers
 
Perfect for any application that needs reliable clipboard functionality!