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.