ReactAsyncClipboard APIFeedback State

Build a Copy to Clipboard Button

Learn how to use the async Clipboard API, manage feedback states, and handle permission errors gracefully.

10 min read5 sections
01

Problem Statement

Build a copy button that:

  • Copies text to the clipboard on click
  • Shows visual feedback: idle → copied → idle
  • Handles errors (permission denied, insecure context)
  • Resets back to idle after 2 seconds
  • Works independently per instance (multiple buttons on page)

Why this question?

It's a small, focused problem that tests async/await, state transitions, and cleanup (clearing timeouts). Interviewers use it to see if you handle edge cases like errors and stale timers.

02

The Clipboard API

The modern Clipboard API is promise-based and requires a secure context (HTTPS or localhost):

clipboard basicstypescript
// Write text
await navigator.clipboard.writeText("hello");

// Read text (requires permission)
const text = await navigator.clipboard.readText();

Fallback

If you need to support older browsers, fall back to document.execCommand("copy") with a hidden textarea — but in an interview, the async API is the expected answer.

03

Feedback State Machine

The button has three states: idle, copied, and error. A timeout resets it:

state flowtext
idle ──click──→ copied ──2s──→ idle
                    └──error──→ error ──2s──→ idle
implementation shapetypescript
type Status = "idle" | "copied" | "error";

const [status, setStatus] = useState<Status>("idle");

const handleCopy = async () => {
  try {
    await navigator.clipboard.writeText(text);
    setStatus("copied");
  } catch {
    setStatus("error");
  }
  setTimeout(() => setStatus("idle"), 2000);
};

Cleanup

If the component unmounts before the timeout fires, you get a "set state on unmounted component" warning. Store the timeout ID in a ref and clear it on unmount.

04

Accessibility

  • Use aria-label to describe the action (e.g. "Copy install command")
  • Announce feedback with aria-live="polite" or role="status"
  • Don't rely on color alone — change the text/icon too
  • Button should remain focusable during all states
05

Interview Follow-up Questions

Q:How do you prevent stale timeouts if the user clicks multiple times quickly?

A: Store the timeout ID in a ref. Before setting a new timeout, clear the previous one with clearTimeout(ref.current). This ensures only the latest click's timer is active.

Q:What happens if the Clipboard API isn't available?

A: navigator.clipboard is undefined in insecure contexts (plain HTTP). Check if it exists before calling writeText, and show an error state or fall back to document.execCommand('copy') with a temporary textarea.

Q:How would you make this into a reusable hook?

A: Extract into useCopyToClipboard(text) that returns { status, copy }. The hook owns the state and timeout cleanup via useEffect return. The component just renders based on status and calls copy on click.

Ready to build it yourself?

The snippets and button shell are ready. Implement the CopyButton — add state, call the Clipboard API, and show feedback.