diff --git a/lib/hooks/useResettingState.js b/lib/hooks/useResettingState.js new file mode 100644 --- /dev/null +++ b/lib/hooks/useResettingState.js @@ -0,0 +1,26 @@ +// @flow + +import _debounce from 'lodash/debounce.js'; +import * as React from 'react'; + +import type { SetState } from '../types/hook-types.js'; + +function useResettingState( + initialState: (() => T) | T, + duration: number, +): [T, SetState] { + const [value, setValue] = React.useState(initialState); + const resetStatusAfterTimeout = React.useRef( + _debounce(() => setValue(initialState), duration), + ); + React.useEffect(() => resetStatusAfterTimeout.current.cancel, []); + + const setNewValue = React.useCallback((newValue: (T => T) | T) => { + setValue(newValue); + resetStatusAfterTimeout.current(); + }, []); + + return React.useMemo(() => [value, setNewValue], [setNewValue, value]); +} + +export { useResettingState }; diff --git a/web/invite-links/view-invite-link-modal.react.js b/web/invite-links/view-invite-link-modal.react.js --- a/web/invite-links/view-invite-link-modal.react.js +++ b/web/invite-links/view-invite-link-modal.react.js @@ -5,6 +5,7 @@ import { useModalContext } from 'lib/components/modal-provider.react.js'; import SWMansionIcon from 'lib/components/SWMansionIcon.react.js'; import { inviteLinkUrl } from 'lib/facts/links.js'; +import { useResettingState } from 'lib/hooks/useResettingState.js'; import { threadInfoSelector } from 'lib/selectors/thread-selectors.js'; import type { InviteLink } from 'lib/types/link-types.js'; import { useResolvedThreadInfo } from 'lib/utils/entity-helpers.js'; @@ -18,6 +19,7 @@ +inviteLink: InviteLink, }; +const copiedMessageDurationMs = 2000; function ViewInviteLinkModal(props: Props): React.Node { const { inviteLink } = props; const threadInfo = useSelector( @@ -27,9 +29,16 @@ const { popModal } = useModalContext(); const url = inviteLinkUrl(inviteLink.name); - const copyLink = React.useCallback(() => { - navigator.clipboard.writeText(url); - }, [url]); + const [copied, setCopied] = useResettingState(false, copiedMessageDurationMs); + const copyLink = React.useCallback(async () => { + try { + await navigator.clipboard.writeText(url); + setCopied(true); + } catch (e) { + setCopied(false); + } + }, [setCopied, url]); + const buttonText = copied ? 'Copied!' : 'Copy'; return ( {url} diff --git a/web/utils/tooltip-action-utils.js b/web/utils/tooltip-action-utils.js --- a/web/utils/tooltip-action-utils.js +++ b/web/utils/tooltip-action-utils.js @@ -1,10 +1,10 @@ // @flow import invariant from 'invariant'; -import _debounce from 'lodash/debounce.js'; import * as React from 'react'; import { useModalContext } from 'lib/components/modal-provider.react.js'; +import { useResettingState } from 'lib/hooks/useResettingState.js'; import type { ChatMessageInfoItem } from 'lib/selectors/chat-selectors.js'; import { useCanEditMessage } from 'lib/shared/edit-messages-utils.js'; import { createMessageReply } from 'lib/shared/message-utils.js'; @@ -120,18 +120,11 @@ ): ?MessageTooltipAction { const { messageInfo } = item; - const [successful, setSuccessful] = React.useState(false); - const resetStatusAfterTimeout = React.useRef( - _debounce(() => setSuccessful(false), copiedMessageDurationMs), + const [successful, setSuccessful] = useResettingState( + false, + copiedMessageDurationMs, ); - const onSuccess = React.useCallback(() => { - setSuccessful(true); - resetStatusAfterTimeout.current(); - }, []); - - React.useEffect(() => resetStatusAfterTimeout.current.cancel, []); - return React.useMemo(() => { if (messageInfo.type !== messageTypes.TEXT) { return null; @@ -140,7 +133,7 @@ const onClick = async () => { try { await navigator.clipboard.writeText(messageInfo.text); - onSuccess(); + setSuccessful(true); } catch (e) { setSuccessful(false); } @@ -150,7 +143,7 @@ onClick, label: successful ? 'Copied!' : 'Copy', }; - }, [messageInfo.text, messageInfo.type, onSuccess, successful]); + }, [messageInfo.text, messageInfo.type, setSuccessful, successful]); } function useMessageReactAction(