Skip to main content

Implementing Clipboard Functionality

In this tutorial, you will learn how to implement a "Copy to Clipboard" feature with automatic state management and visual feedback. You will build a reusable component that allows users to copy unique identifiers (IDs) from a data table, similar to the implementation found in the Items dashboard.

Prerequisites

To follow this tutorial, you need the following components and hooks already available in your project:

  • useCopyToClipboard: The custom hook located in frontend/src/hooks/useCopyToClipboard.ts.
  • Lucide Icons: Specifically Check and Copy for visual feedback.
  • Shadcn UI Button: The base button component used for the interaction.

Step 1: Initialize the Clipboard Hook

First, you need to import and initialize the useCopyToClipboard hook within your functional component. This hook returns a tuple containing the current state and a function to perform the copy operation.

import { useCopyToClipboard } from "@/hooks/useCopyToClipboard"

function CopyId({ id }: { id: string }) {
const [copiedText, copy] = useCopyToClipboard()

// ... component logic
}

The useCopyToClipboard hook manages two internal types:

  • CopiedValue: A string | null representing the text currently in the clipboard state.
  • CopyFn: An asynchronous function (text: string) => Promise<boolean> that handles the browser's Clipboard API.

Step 2: Determine the Copied State

To provide visual feedback, you need to know if the specific ID associated with this component instance is the one currently stored in the hook's state.

const isCopied = copiedText === id

Because the hook automatically resets its state to null after 2 seconds (as defined in frontend/src/hooks/useCopyToClipboard.ts), isCopied will automatically revert to false, allowing the UI to switch back from a "Success" state to the "Default" state.

Step 3: Implement the Interactive Button

Now, create the UI using a button that triggers the copy function. You will use the isCopied boolean to conditionally render different icons.

import { Check, Copy } from "lucide-react"
import { Button } from "@/components/ui/button"

// Inside your CopyId component return statement:
<Button
variant="ghost"
size="icon"
className="size-6"
onClick={() => copy(id)}
>
{isCopied ? (
<Check className="size-3 text-green-500" />
) : (
<Copy className="size-3" />
)}
<span className="sr-only">Copy ID</span>
</Button>

When the user clicks the button, the copy(id) function is called. This function performs the following actions internally:

  1. Checks if navigator.clipboard is supported by the browser.
  2. Attempts to write the text to the clipboard using navigator.clipboard.writeText(text).
  3. Updates the copiedText state.
  4. Sets a timeout to clear the state after 2000ms.

Step 4: Complete Implementation

The following example shows the complete CopyId component as implemented in frontend/src/components/Items/columns.tsx. It includes styling to show the copy button only when the user hovers over the ID container.

import { Check, Copy } from "lucide-react"
import { Button } from "@/components/ui/button"
import { useCopyToClipboard } from "@/hooks/useCopyToClipboard"

function CopyId({ id }: { id: string }) {
const [copiedText, copy] = useCopyToClipboard()
const isCopied = copiedText === id

return (
<div className="flex items-center gap-1.5 group">
<span className="font-mono text-xs text-muted-foreground">{id}</span>
<Button
variant="ghost"
size="icon"
className="size-6 opacity-0 group-hover:opacity-100 transition-opacity"
onClick={() => copy(id)}
>
{isCopied ? (
<Check className="size-3 text-green-500" />
) : (
<Copy className="size-3" />
)}
<span className="sr-only">Copy ID</span>
</Button>
</div>
)
}

How it Works

The underlying hook useCopyToClipboard ensures that the operation is safe and provides consistent behavior:

  • Safety Checks: It verifies navigator?.clipboard exists to prevent crashes in older browsers or non-secure contexts.
  • Error Handling: If the browser blocks the clipboard action, it catches the error, logs a warning, and resets the state to null.
  • Auto-Reset: The setTimeout(() => setCopiedText(null), 2000) call inside the hook is what allows your UI to "flicker" the checkmark icon briefly before returning to the copy icon, providing a polished user experience without requiring manual state management in your components.