Skip to content

Commit

Permalink
feat: change useCopyToClipboard implementation
Browse files Browse the repository at this point in the history
BREAKING CHANGE: useCopyToClipboard interface changed
  • Loading branch information
streamich committed Apr 8, 2019
2 parents a48d57c + 8b04ef0 commit c391038
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 59 deletions.
24 changes: 14 additions & 10 deletions docs/useCopyToClipboard.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,23 @@

Copy text to a user's clipboard.


## Usage

Basic usage

```jsx
const Demo = () => {
const [text, setText] = React.useState('');
const [state, copyToClipboard] = useCopyToClipboard();

return (
<div>
<input value={text} onChange={e => setText(e.target.value)} />
<button type="button" onClick={() => copyToClipboard(text)}>copy text</button>
{state.error
? <p>Unable to copy value: {state.error.message}</p>
: state.value && <p>Copied {state.value}</p>}
</div>
)

const [text, setText] = React.useState('');
const [copied, copyToClipboard] = useCopyToClipboard(text);

Expand All @@ -25,11 +35,5 @@ const Demo = () => {
## Reference

```js
const [copied, copyToClipboard] = useCopyToClipboard(text);
const [copied, copyToClipboard] = useCopyToClipboard(text, writeText);
const [state, copyToClipboard] = useCopyToClipboard();
```

, where

- `writeText` &mdash; function that receives a single string argument, which
it copies to user's clipboard.
20 changes: 11 additions & 9 deletions src/__stories__/useCopyToClipboard.story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,21 @@ import {useCopyToClipboard} from '..';

const Demo = () => {
const [text, setText] = React.useState('');
const [copied, copyToClipboard] = useCopyToClipboard(text, {
onCopy: txt => alert('success: ' + txt),
onError: err => alert(err),
});
const [state, copyToClipboard] = useCopyToClipboard();

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>
<button type="button" onClick={() => copyToClipboard(text)}>copy text</button>
{state.error
? <p>Unable to copy value: {state.error.message}</p>
: state.value && (
<>
<p>Copied {state.value} {state.noUserInteraction ? 'without' : 'with'} user interaction</p>
<input type="text" placeholder="Paste it in here to check"/>
</>
)}

</div>
)
}
Expand Down
74 changes: 34 additions & 40 deletions src/useCopyToClipboard.ts
Original file line number Diff line number Diff line change
@@ -1,55 +1,49 @@
import {useState, useCallback, useRef} from 'react';
import useUpdateEffect from './useUpdateEffect';
import {useCallback} from 'react';
import useSetState from './useSetState'
import useRefMounted from './useRefMounted';
const writeTextDefault = require('copy-to-clipboard');
import * as writeText from '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 interface CopyToClipboardState {
value?: string,
noUserInteraction: boolean,
error?: Error,
}
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 useCopyToClipboard = (): [CopyToClipboardState, (value: string) => void] => {
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;
}
const [state, setState] = useSetState<CopyToClipboardState>({
value: undefined,
error: undefined,
noUserInteraction: true
});

const copyToClipboard = useCallback((value) => {
try {
await writeText(text);
if (process.env.NODE_ENV === 'development') {
if (typeof value !== "string") {
console.error(`Cannot copy typeof ${typeof value} to clipboard, must be a string`);
}
}

const noUserInteraction = writeText(value);

if (!mounted.current) return;
setCopied(true);
onCopy && onCopy(text);
setState({
value,
error: undefined,
noUserInteraction
});
} catch (error) {
if (!mounted.current) return;
console.error(error);
setCopied(false);
onError && onError(error, text);
setState({
value: undefined,
error,
noUserInteraction: true
});
}
}, [text]);

useUpdateEffect(() => {
setCopied(false);
latestText.current = text;
}, [text]);
}, []);

return [copied, copyToClipboard];
return [state, copyToClipboard];
}

export default useCopyToClipboard;

0 comments on commit c391038

Please sign in to comment.