feat: add useCopyToClipboard() hook

This commit is contained in:
Va Da 2019-04-08 00:44:07 +02:00 committed by GitHub
commit 4d8e276607
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 129 additions and 1 deletions

View File

@ -73,6 +73,7 @@
- [**Side-effects**](./docs/Side-effects.md)
- [`useAsync`](./docs/useAsync.md) — resolves an `async` function.
- [`useAsyncRetry`](./docs/useAsyncRetry.md) — `useAsync` with `retry()` method.
- [`useCopyToClipboard`](./docs/useCopyToClipboard.md) — copies text to clipboard.
- [`useDebounce`](./docs/useDebounce.md) — debounces a function. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/side-effects-usedebounce--demo)
- [`useFavicon`](./docs/useFavicon.md) — sets favicon of the page.
- [`useLocalStorage`](./docs/useLocalStorage.md) — manages a value in `localStorage`.

View File

@ -0,0 +1,35 @@
# `useCopyToClipboard`
Copy text to a user's clipboard.
## Usage
Basic usage
```jsx
const Demo = () => {
const [text, setText] = React.useState('');
const [copied, copyToClipboard] = useCopyToClipboard(text);
return (
<div>
<input value={text} onChange={e => setText(e.target.value)} />
<button type="button" onClick={copyToClipboard}>copy text</button>
<div>Copied: {copied ? 'Yes' : 'No'}</div>
</div>
)
}
```
## Reference
```js
const [copied, copyToClipboard] = useCopyToClipboard(text);
const [copied, copyToClipboard] = useCopyToClipboard(text, writeText);
```
, where
- `writeText` &mdash; function that receives a single string argument, which
it copies to user's clipboard.

View File

@ -34,6 +34,7 @@
},
"homepage": "https://github.com/streamich/react-use#readme",
"dependencies": {
"copy-to-clipboard": "^3.1.0",
"nano-css": "^5.1.0",
"react-fast-compare": "^2.0.4",
"react-wait": "^0.3.0",

View File

@ -0,0 +1,27 @@
import * as React from 'react';
import {storiesOf} from '@storybook/react';
import ShowDocs from './util/ShowDocs';
import {useCopyToClipboard} from '..';
const Demo = () => {
const [text, setText] = React.useState('');
const [copied, copyToClipboard] = useCopyToClipboard(text, {
onCopy: txt => alert('success: ' + txt),
onError: err => alert(err),
});
return (
<div>
<input value={text} onChange={e => setText(e.target.value)} />
<button type="button" onClick={copyToClipboard}>copy text</button>
<div>Copied: {copied ? 'Yes' : 'No'}</div>
<div style={{margin: 10}}>
<input type="text" placeholder="now paste it in here"/>
</div>
</div>
)
}
storiesOf('Side-effects|useCopyToClipboard', module)
.add('Docs', () => <ShowDocs md={require('../../docs/useCopyToClipboard.md')} />)
.add('Demo', () => <Demo/>)

View File

@ -4,6 +4,7 @@ import useAsyncRetry from './useAsyncRetry';
import useAudio from './useAudio';
import useBattery from './useBattery';
import useBoolean from './useBoolean';
import useCopyToClipboard from './useCopyToClipboard';
import useDrop from './useDrop';
import useDropArea from './useDropArea';
import useCounter from './useCounter';
@ -73,6 +74,7 @@ export {
useAudio,
useBattery,
useBoolean,
useCopyToClipboard,
useDrop,
useDropArea,
useClickAway,

55
src/useCopyToClipboard.ts Normal file
View File

@ -0,0 +1,55 @@
import {useState, useCallback, useRef} from 'react';
import useUpdateEffect from './useUpdateEffect';
import useRefMounted from './useRefMounted';
const writeTextDefault = require('copy-to-clipboard');
export type WriteText = (text: string) => Promise<void>; // https://developer.mozilla.org/en-US/docs/Web/API/Clipboard/writeText
export interface UseCopyToClipboardOptions {
writeText?: WriteText;
onCopy?: (text: string) => void;
onError?: (error: any, text: string) => void;
}
export type UseCopyToClipboard = (text?: string, options?: UseCopyToClipboardOptions) => [boolean, () => void];
const useCopyToClipboard: UseCopyToClipboard = (text = '', options) => {
const {writeText = writeTextDefault, onCopy, onError} = (options || {}) as UseCopyToClipboardOptions;
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(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);
onCopy && onCopy(text);
} catch (error) {
if (!mounted.current) return;
console.error(error);
setCopied(false);
onError && onError(error, text);
}
}, [text]);
useUpdateEffect(() => {
setCopied(false);
latestText.current = text;
}, [text]);
return [copied, copyToClipboard];
}
export default useCopyToClipboard;

View File

@ -3932,6 +3932,13 @@ copy-to-clipboard@^3.0.8:
dependencies:
toggle-selection "^1.0.3"
copy-to-clipboard@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.1.0.tgz#0a28141899e6bd217b9dc13fd1689b3b38820b44"
integrity sha512-+RNyDq266tv5aGhfRsL6lxgj8Y6sCvTrVJnFUVvuxuqkcSMaLISt1wd4JkdQSphbcLTIQ9kEpTULNnoCXAFdng==
dependencies:
toggle-selection "^1.0.6"
core-js-compat@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.0.0.tgz#cd9810b8000742535a4a43773866185e310bd4f7"
@ -11010,7 +11017,7 @@ to-regex@^3.0.1, to-regex@^3.0.2:
regex-not "^1.0.2"
safe-regex "^1.1.0"
toggle-selection@^1.0.3:
toggle-selection@^1.0.3, toggle-selection@^1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32"
integrity sha1-bkWxJj8gF/oKzH2J14sVuL932jI=