From 61d14fa19dee27d29f2c70656d5ae26cdd218821 Mon Sep 17 00:00:00 2001 From: Aleksey Troynikov Date: Tue, 23 Jan 2024 18:16:32 +0300 Subject: [PATCH] refactor(CopyToClipboard): replaced class component by functional component (#1216) --- .../ClipboardButton/ClipboardButton.tsx | 9 +- .../CopyToClipboard/CopyToClipboard.tsx | 87 +++++-------------- .../__tests__/CopyToClipboard.test.tsx | 18 ++++ src/components/CopyToClipboard/types.ts | 11 ++- 4 files changed, 50 insertions(+), 75 deletions(-) create mode 100644 src/components/CopyToClipboard/__tests__/CopyToClipboard.test.tsx diff --git a/src/components/ClipboardButton/ClipboardButton.tsx b/src/components/ClipboardButton/ClipboardButton.tsx index e6eff87a1e..1b526365ac 100644 --- a/src/components/ClipboardButton/ClipboardButton.tsx +++ b/src/components/ClipboardButton/ClipboardButton.tsx @@ -4,17 +4,14 @@ import {Button} from '../Button'; import type {ButtonProps, ButtonSize} from '../Button'; import {ClipboardIcon} from '../ClipboardIcon'; import {CopyToClipboard} from '../CopyToClipboard'; -import type {CopyToClipboardBaseProps, CopyToClipboardStatus} from '../CopyToClipboard/types'; +import type {CopyToClipboardProps, CopyToClipboardStatus} from '../CopyToClipboard/types'; import {Tooltip} from '../Tooltip'; import i18n from './i18n'; export interface ClipboardButtonProps - extends CopyToClipboardBaseProps, - Omit { - /** Time to restore initial state, ms */ - timeout?: number; -} + extends Omit, + Omit {} interface ClipboardButtonComponentProps extends Omit { diff --git a/src/components/CopyToClipboard/CopyToClipboard.tsx b/src/components/CopyToClipboard/CopyToClipboard.tsx index 59cbf37cdc..e54c4daf95 100644 --- a/src/components/CopyToClipboard/CopyToClipboard.tsx +++ b/src/components/CopyToClipboard/CopyToClipboard.tsx @@ -2,77 +2,38 @@ import React from 'react'; import ReactCopyToClipboard from 'react-copy-to-clipboard'; -import type { - CopyToClipboardBaseProps, - CopyToClipboardContent, - CopyToClipboardStatus, -} from './types'; +import type {CopyToClipboardProps, CopyToClipboardStatus} from './types'; -interface CopyToClipboardGeneralProps extends CopyToClipboardBaseProps { - children: CopyToClipboardContent; -} - -interface CopyToClipboardDefaultProps { - timeout: number; -} +const INITIAL_STATUS: CopyToClipboardStatus = 'pending'; -interface CopyToClipboardInnerProps - extends CopyToClipboardGeneralProps, - CopyToClipboardDefaultProps {} +export function CopyToClipboard(props: CopyToClipboardProps) { + const {children, text, options, timeout, onCopy} = props; -export interface CopyToClipboardProps - extends CopyToClipboardGeneralProps, - Partial {} + const [status, setStatus] = React.useState(INITIAL_STATUS); -interface CopyToClipboardState { - status: CopyToClipboardStatus; -} + const timerIdRef = React.useRef(); -export class CopyToClipboard extends React.Component< - CopyToClipboardInnerProps, - CopyToClipboardState -> { - static INITIAL_STATUS: CopyToClipboardStatus = 'pending'; + const content = React.useMemo(() => children(status), [children, status]); - state: CopyToClipboardState = { - status: CopyToClipboard.INITIAL_STATUS, - }; + const handleCopy = React.useCallback['onCopy']>( + (copyText, result) => { + setStatus(result ? 'success' : 'error'); + window.clearTimeout(timerIdRef.current); + timerIdRef.current = window.setTimeout(() => setStatus(INITIAL_STATUS), timeout); + onCopy?.(copyText, result); + }, + [onCopy, timeout], + ); - private timerId?: number; + React.useEffect(() => () => window.clearTimeout(timerIdRef.current), []); - componentWillUnmount() { - clearTimeout(this.timerId); + if (!React.isValidElement(content)) { + throw new Error('Content must be a valid react element'); } - render() { - const {children, text, options} = this.props; - const {status} = this.state; - const content = children(status); - - if (!React.isValidElement(content)) { - throw new Error('Content must be a valid react element'); - } - - return ( - - {content} - - ); - } - - private handleCopy = (text: string, result: boolean) => { - const {timeout, onCopy} = this.props; - - this.setState({ - status: result ? 'success' : 'error', - }); - - clearTimeout(this.timerId); - this.timerId = window.setTimeout(() => { - this.setState({status: CopyToClipboard.INITIAL_STATUS}); - this.timerId = undefined; - }, timeout); - - onCopy?.(text, result); - }; + return ( + + {content} + + ); } diff --git a/src/components/CopyToClipboard/__tests__/CopyToClipboard.test.tsx b/src/components/CopyToClipboard/__tests__/CopyToClipboard.test.tsx new file mode 100644 index 0000000000..74d0ab2d95 --- /dev/null +++ b/src/components/CopyToClipboard/__tests__/CopyToClipboard.test.tsx @@ -0,0 +1,18 @@ +import React from 'react'; + +import {render} from '../../../../test-utils/utils'; +import {CopyToClipboard} from '../CopyToClipboard'; +import type {CopyToClipboardContent} from '../types'; + +describe('CopyToClipboard', () => { + test('content must be a valid react element', () => { + const onCopy = jest.fn(); + expect(() => + render( + + {(() => 123) as any as CopyToClipboardContent} + , + ), + ).toThrow('Content must be a valid react element'); + }); +}); diff --git a/src/components/CopyToClipboard/types.ts b/src/components/CopyToClipboard/types.ts index 4f998f7633..b70a733b7f 100644 --- a/src/components/CopyToClipboard/types.ts +++ b/src/components/CopyToClipboard/types.ts @@ -1,3 +1,5 @@ +import type React from 'react'; + import type ReactCopyToClipboard from 'react-copy-to-clipboard'; export type CopyToClipboardStatus = 'pending' | 'success' | 'error'; @@ -6,13 +8,10 @@ export type OnCopyHandler = (text: string, result: boolean) => void; export type CopyToClipboardContent = (status: CopyToClipboardStatus) => React.ReactElement; -export interface CopyToClipboardBaseProps { - /** Text to copy */ +export interface CopyToClipboardProps { text: string; - - /** Handler that would be invoked after copy to clipboard */ + timeout?: number; + children: CopyToClipboardContent; onCopy?: OnCopyHandler; - - /** copy-to-clipboard options */ options?: ReactCopyToClipboard.Options; }