|
1 | 1 | import useUpdateEffect from './useUpdateEffect';
|
2 |
| -import {useState, useCallback} from 'react'; |
| 2 | +import useRefMounted from './useRefMounted'; |
| 3 | +import {useState, useCallback, useRef} from 'react'; |
3 | 4 |
|
4 |
| -const copyDefault = (text) => { |
| 5 | +export type WriteText = (text: string) => Promise<void>; // https://developer.mozilla.org/en-US/docs/Web/API/Clipboard/writeText |
| 6 | +export type UseCopyToClipboard = (text?: string, writeText?: WriteText) => [boolean, () => void]; |
| 7 | + |
| 8 | +const writeTextDefault = async (text) => { |
5 | 9 | const element = document.createElement('textarea');
|
6 | 10 | element.value = text;
|
7 | 11 | document.body.appendChild(element);
|
8 |
| - element.select(); |
9 |
| - document.execCommand('copy'); |
10 |
| - document.body.removeChild(element); |
| 12 | + try { |
| 13 | + element.select(); |
| 14 | + document.execCommand('copy'); |
| 15 | + } finally { |
| 16 | + document.body.removeChild(element); |
| 17 | + } |
11 | 18 | };
|
12 | 19 |
|
13 |
| -const useCopyToClipboard = (text: string = '', copy = copyDefault): [boolean, () => void] => { |
| 20 | +const useCopyToClipboard: UseCopyToClipboard = (text = '', writeText = writeTextDefault) => { |
| 21 | + if (process.env.NODE_ENV !== 'production') { |
| 22 | + if (typeof text !== 'string') { |
| 23 | + console.warn('useCopyToClipboard hook expects first argument to be string.'); |
| 24 | + } |
| 25 | + } |
| 26 | + |
| 27 | + const mounted = useRefMounted(); |
| 28 | + const latestText = useRef(text); |
14 | 29 | const [copied, setCopied] = useState(false);
|
15 |
| - const copyToClipboard = useCallback(() => { |
16 |
| - copy(text); |
17 |
| - setCopied(true); |
| 30 | + const copyToClipboard = useCallback(async () => { |
| 31 | + if (latestText.current !== text) { |
| 32 | + if (process.env.NODE_ENV !== 'production') { |
| 33 | + console.warn('Trying to copy stale text.'); |
| 34 | + } |
| 35 | + return; |
| 36 | + } |
| 37 | + |
| 38 | + try { |
| 39 | + await writeText(text); |
| 40 | + if (!mounted.current) return; |
| 41 | + setCopied(true); |
| 42 | + } catch (error) { |
| 43 | + if (!mounted.current) return; |
| 44 | + console.error(error); |
| 45 | + setCopied(false); |
| 46 | + } |
18 | 47 | }, [text]);
|
19 | 48 |
|
20 | 49 | useUpdateEffect(() => {
|
21 | 50 | setCopied(false);
|
| 51 | + latestText.current = text; |
22 | 52 | }, [text]);
|
23 | 53 |
|
24 | 54 | return [copied, copyToClipboard];
|
|
0 commit comments