かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

TypeScript React クリップボードにコピーする hooks とコンポーネント作ってみた

input タグに入力された値をクリップボードにコピーする hooks と コンポーネントを作ってみたのでログとしてメモ

📝 Clipboard.writeText() を使ってクリップボードにテキストをコピーすることができる

clipboardjs のようなライブラリもあったのですが、ブラウザで動作するフロントエンドなら Clipboard API を使えばクリップボードに書き込みが可能そうだったのでライブラリなしに実装しました

Clipboard: writeText() method
The writeText() method of the Clipboard interface writes the specified text to the system clipboard, returning a Promise that is resolved once the system clipboard has been updated.
cf. Clipboard: writeText() method - Web APIs | MDN

const onCopy = async (text: string) => {
  try {
    await navigator.clipboard.writeText(text);
  } catch (error) {
    console.error(error.message);
  }
}

Clipboard にテキストをコピーする hooks

// /hooks/useCopyClipboard.ts
import { useCallback, useEffect, useState } from "react";

export const useCopyClipboard = () => {
  const [hasCopied, setHasCpied] = useState(false);
  const [error, setError] = useState<Error | null>(null);

  const onCopy = useCallback(async (text: string) => {
    try {
      await navigator.clipboard.writeText(text);
      setHasCpied(true);
    } catch (err) {
      if (err instanceof Error) {
        console.error(err.message);
        setError(err);
        return;
      }

      console.error(err);
      setError(new Error("Failed to copy"));
    }
  }, []);

  useEffect(() => {
    let timeoutId: number | null = null;
    if (hasCopied) {
      timeoutId = window.setTimeout(() => {
        setHasCpied(false);
      }, 1500);
    }

    return () => {
      if (timeoutId) {
        window.clearTimeout(timeoutId);
      }
    };
  }, [hasCopied]);

  return {
    onCopy,
    hasCopied,
    isError: !!error,
    error,
  };
};

useCopyClipboard hooks から返される onCopy に渡した文字列をクリップボードにコピーするシンプルな hooks です (※ Chakra UI の useClipboard を参考にしました)

input に入力された文字列をクリップボードにコピーする

import { useRef } from "react";
import { useCopyClipboard } from "./hooks/useCopyClipboard";

export default function App() {
  const inputRef = useRef<HTMLInputElement>(null);
  const { onCopy, hasCopied, isError } = useCopyClipboard();

  const handleCopyClipboard = () => {
    if (!inputRef.current) {
      return;
    }
    onCopy(inputRef.current.value);
  };

  return (
    <div className="App">
      <input ref={inputRef} type="text" defaultValue=""  />
      <button type="button" onClick={handleCopyClipboard}>
        {hasCopied ? "Copied!" : "Copy!"}
      </button>
      {isError && <small className="text-error">Failed to copy<small>}
    </div>
  );
}

Sample

iframe だとクリップボードにコピーできないので codesandbox を直接開いてください

ライブラリを使うこと無くクリップボードに任意のテキストをコピーさせることができてびっくりしました!
と、同時に Clipboard API にはブラウザのサポートが完全なわけではないですがクリップボードから読み込みもあるので、evil なサイトではクリップボードの入力値を不正に API にも送るとか出来てしまうのかな〜なども気になるところでした。

Clipboard API - Browser compatibility | MDN Clipboard API - Web APIs | MDN

evil な使い方が出来てしまうんので navigator.clipboard.readText はサポートがマチマチなのかも?

おわり ₍ᐢ. ̫.ᐢ₎


[参考]