Skip to content

Commit f185044

Browse files
committed
feat: 🎸 improve useCopyToClipboard() hook
1 parent 0a6d773 commit f185044

File tree

2 files changed

+41
-11
lines changed

2 files changed

+41
-11
lines changed

docs/useCopyToClipboard.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,10 @@ const Demo = () => {
2626

2727
```js
2828
const [copied, copyToClipboard] = useCopyToClipboard(text);
29-
const [copied, copyToClipboard] = useCopyToClipboard(text, copyFunction);
29+
const [copied, copyToClipboard] = useCopyToClipboard(text, writeText);
3030
```
3131

3232
, where
3333

34-
- `copyFunction` — function that receives a single string argument, which
34+
- `writeText` — function that receives a single string argument, which
3535
it copies to user's clipboard.

src/useCopyToClipboard.ts

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,54 @@
11
import useUpdateEffect from './useUpdateEffect';
2-
import {useState, useCallback} from 'react';
2+
import useRefMounted from './useRefMounted';
3+
import {useState, useCallback, useRef} from 'react';
34

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) => {
59
const element = document.createElement('textarea');
610
element.value = text;
711
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+
}
1118
};
1219

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);
1429
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+
}
1847
}, [text]);
1948

2049
useUpdateEffect(() => {
2150
setCopied(false);
51+
latestText.current = text;
2252
}, [text]);
2353

2454
return [copied, copyToClipboard];

0 commit comments

Comments
 (0)