diff --git a/docs/useCopyToClipboard.md b/docs/useCopyToClipboard.md index 79b83f7b4a..cc56dae1eb 100644 --- a/docs/useCopyToClipboard.md +++ b/docs/useCopyToClipboard.md @@ -26,10 +26,10 @@ const Demo = () => { ```js const [copied, copyToClipboard] = useCopyToClipboard(text); -const [copied, copyToClipboard] = useCopyToClipboard(text, copyFunction); +const [copied, copyToClipboard] = useCopyToClipboard(text, writeText); ``` , where -- `copyFunction` — function that receives a single string argument, which +- `writeText` — function that receives a single string argument, which it copies to user's clipboard. diff --git a/src/useCopyToClipboard.ts b/src/useCopyToClipboard.ts index 0515f9270f..62024190c6 100644 --- a/src/useCopyToClipboard.ts +++ b/src/useCopyToClipboard.ts @@ -1,24 +1,54 @@ import useUpdateEffect from './useUpdateEffect'; -import {useState, useCallback} from 'react'; +import useRefMounted from './useRefMounted'; +import {useState, useCallback, useRef} from 'react'; -const copyDefault = (text) => { +export type WriteText = (text: string) => Promise; // https://developer.mozilla.org/en-US/docs/Web/API/Clipboard/writeText +export type UseCopyToClipboard = (text?: string, writeText?: WriteText) => [boolean, () => void]; + +const writeTextDefault = async (text) => { const element = document.createElement('textarea'); element.value = text; document.body.appendChild(element); - element.select(); - document.execCommand('copy'); - document.body.removeChild(element); + try { + element.select(); + document.execCommand('copy'); + } finally { + document.body.removeChild(element); + } }; -const useCopyToClipboard = (text: string = '', copy = copyDefault): [boolean, () => void] => { +const useCopyToClipboard: UseCopyToClipboard = (text = '', writeText = writeTextDefault) => { + if (process.env.NODE_ENV !== 'production') { + if (typeof text !== 'string') { + console.warn('useCopyToClipboard hook expects first argument to be string.'); + } + } + + const mounted = useRefMounted(); + const latestText = useRef(text); const [copied, setCopied] = useState(false); - const copyToClipboard = useCallback(() => { - copy(text); - setCopied(true); + const copyToClipboard = useCallback(async () => { + if (latestText.current !== text) { + if (process.env.NODE_ENV !== 'production') { + console.warn('Trying to copy stale text.'); + } + return; + } + + try { + await writeText(text); + if (!mounted.current) return; + setCopied(true); + } catch (error) { + if (!mounted.current) return; + console.error(error); + setCopied(false); + } }, [text]); useUpdateEffect(() => { setCopied(false); + latestText.current = text; }, [text]); return [copied, copyToClipboard];