Skip to content

Commit

Permalink
feat: add useCopyToClipboard() hook
Browse files Browse the repository at this point in the history
  • Loading branch information
streamich authored Apr 7, 2019
2 parents 4998c54 + 2553225 commit 4d8e276
Show file tree
Hide file tree
Showing 7 changed files with 129 additions and 1 deletion.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down
35 changes: 35 additions & 0 deletions docs/useCopyToClipboard.md
Original file line number Diff line number Diff line change
@@ -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.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
27 changes: 27 additions & 0 deletions src/__stories__/useCopyToClipboard.story.tsx
Original file line number Diff line number Diff line change
@@ -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/>)
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -73,6 +74,7 @@ export {
useAudio,
useBattery,
useBoolean,
useCopyToClipboard,
useDrop,
useDropArea,
useClickAway,
Expand Down
55 changes: 55 additions & 0 deletions src/useCopyToClipboard.ts
Original file line number Diff line number Diff line change
@@ -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;
9 changes: 8 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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=
Expand Down

0 comments on commit 4d8e276

Please sign in to comment.