From b03959e8477231d1c67952159d9d325945f149e5 Mon Sep 17 00:00:00 2001 From: Alexander Zinchuk Date: Mon, 12 Jun 2023 11:44:58 +0200 Subject: [PATCH] [Refactoring] Introduce `useLastCallback` --- src/components/common/AnimatedIcon.tsx | 14 +- .../common/AnimatedIconWithPreview.tsx | 8 +- src/components/common/AnimatedSticker.tsx | 24 ++- src/components/common/Audio.tsx | 40 ++-- src/components/common/Avatar.tsx | 7 +- src/components/common/ChatExtra.tsx | 8 +- src/components/common/ChatOrUserPicker.tsx | 28 +-- src/components/common/CustomEmoji.tsx | 13 +- src/components/common/CustomEmojiPicker.tsx | 11 +- src/components/common/Document.tsx | 14 +- src/components/common/GifButton.tsx | 41 ++--- src/components/common/GroupChatInfo.tsx | 27 +-- src/components/common/Media.tsx | 7 +- src/components/common/MediaSpoiler.tsx | 8 +- src/components/common/Picker.tsx | 12 +- src/components/common/PrivateChatInfo.tsx | 27 +-- src/components/common/ProfileInfo.tsx | 22 +-- src/components/common/ReactionEmoji.tsx | 7 +- src/components/common/ReportModal.tsx | 29 +-- src/components/common/SafeLink.tsx | 7 +- src/components/common/SeenByModal.tsx | 12 +- src/components/common/StickerButton.tsx | 54 +++--- src/components/common/StickerSet.tsx | 30 +-- src/components/common/WebLink.tsx | 10 +- .../common/hooks/useAnimatedEmoji.ts | 17 +- src/components/common/hooks/useCustomEmoji.ts | 7 +- .../common/hooks/useStickerPickerObservers.ts | 12 +- src/components/common/spoiler/Spoiler.tsx | 14 +- src/components/left/ArchivedChats.tsx | 8 +- src/components/left/LeftColumn.tsx | 39 ++-- src/components/left/main/Chat.tsx | 27 +-- src/components/left/main/ChatFolders.tsx | 8 +- src/components/left/main/ChatList.tsx | 15 +- src/components/left/main/ForumPanel.tsx | 11 +- src/components/left/main/LeftMain.tsx | 36 ++-- src/components/left/main/LeftMainHeader.tsx | 48 ++--- src/components/left/main/Topic.tsx | 19 +- src/components/left/search/ChatMessage.tsx | 7 +- src/components/left/settings/Settings.tsx | 11 +- src/components/main/DownloadManager.tsx | 7 +- src/components/main/Main.tsx | 19 +- src/components/mediaViewer/MediaViewer.tsx | 25 +-- .../mediaViewer/MediaViewerActions.tsx | 23 +-- .../mediaViewer/MediaViewerContent.tsx | 8 +- .../mediaViewer/MediaViewerSlides.tsx | 7 +- src/components/mediaViewer/SeekLine.tsx | 7 +- src/components/mediaViewer/SenderInfo.tsx | 8 +- src/components/mediaViewer/VideoPlayer.tsx | 56 +++--- .../mediaViewer/VideoPlayerControls.tsx | 14 +- src/components/middle/ActionMessage.tsx | 12 +- .../middle/ActionMessageSuggestedAvatar.tsx | 20 +- src/components/middle/AudioPlayer.tsx | 34 ++-- src/components/middle/ChatReportPanel.tsx | 25 ++- src/components/middle/ContactGreeting.tsx | 14 +- .../middle/DeleteSelectedMessageModal.tsx | 12 +- .../middle/EmojiInteractionAnimation.tsx | 19 +- .../middle/FloatingActionButtons.tsx | 10 +- src/components/middle/HeaderActions.tsx | 49 +++-- src/components/middle/HeaderMenuContainer.tsx | 104 +++++------ src/components/middle/HeaderPinnedMessage.tsx | 11 +- .../middle/MessageLanguageModal.tsx | 11 +- src/components/middle/MessageList.tsx | 8 +- .../middle/MessageSelectToolbar.tsx | 15 +- src/components/middle/MiddleColumn.tsx | 42 +++-- src/components/middle/MiddleHeader.tsx | 29 ++- src/components/middle/MobileSearch.tsx | 20 +- src/components/middle/ReactorListModal.tsx | 19 +- .../middle/composer/AttachBotItem.tsx | 19 +- src/components/middle/composer/AttachMenu.tsx | 19 +- .../middle/composer/AttachmentModal.tsx | 50 +++-- .../middle/composer/AttachmentModalItem.tsx | 8 +- .../middle/composer/BotCommandMenu.tsx | 8 +- .../middle/composer/BotCommandTooltip.tsx | 14 +- src/components/middle/composer/Composer.tsx | 173 ++++++++---------- .../composer/ComposerEmbeddedMessage.tsx | 28 ++- .../middle/composer/CustomEmojiButton.tsx | 8 +- .../middle/composer/CustomEmojiTooltip.tsx | 9 +- src/components/middle/composer/DropArea.tsx | 18 +- .../middle/composer/EmojiButton.tsx | 8 +- .../middle/composer/EmojiPicker.tsx | 12 +- .../middle/composer/EmojiTooltip.tsx | 25 ++- src/components/middle/composer/GifPicker.tsx | 7 +- .../middle/composer/InlineBotTooltip.tsx | 19 +- .../middle/composer/MentionTooltip.tsx | 18 +- .../middle/composer/MessageInput.tsx | 15 +- src/components/middle/composer/PollModal.tsx | 67 +++---- src/components/middle/composer/SendAsMenu.tsx | 12 +- .../middle/composer/StickerPicker.tsx | 23 +-- src/components/middle/composer/SymbolMenu.tsx | 19 +- .../middle/composer/SymbolMenuButton.tsx | 39 ++-- .../middle/composer/SymbolMenuFooter.tsx | 8 +- .../middle/composer/TextFormatter.tsx | 58 +++--- .../middle/composer/WebPagePreview.tsx | 10 +- .../composer/hooks/useAttachmentModal.ts | 23 ++- .../composer/hooks/useCustomEmojiTooltip.ts | 7 +- .../middle/composer/hooks/useDraft.ts | 8 +- .../middle/composer/hooks/useEditing.ts | 20 +- .../middle/composer/hooks/useEmojiTooltip.ts | 11 +- .../composer/hooks/useInlineBotTooltip.ts | 7 +- .../composer/hooks/useInputCustomEmojis.ts | 20 +- .../composer/hooks/useKeyboardNavigation.ts | 17 +- .../composer/hooks/useMentionTooltip.ts | 9 +- .../composer/hooks/useVoiceRecording.ts | 18 +- .../composer/inlineResults/ArticleResult.tsx | 8 +- .../composer/inlineResults/GifResult.tsx | 8 +- .../composer/inlineResults/MediaResult.tsx | 8 +- .../middle/hooks/useContainerHeight.ts | 8 +- .../middle/hooks/usePinnedMessage.ts | 19 +- src/components/middle/hooks/useScrollHooks.ts | 9 +- src/components/middle/hooks/useStickyDates.ts | 6 +- src/components/middle/message/Album.tsx | 8 +- .../middle/message/CommentButton.tsx | 8 +- src/components/middle/message/Contact.tsx | 8 +- .../middle/message/ContextMenuContainer.tsx | 131 ++++++------- .../middle/message/InvoiceMediaPreview.tsx | 11 +- src/components/middle/message/Location.tsx | 7 +- src/components/middle/message/Message.tsx | 10 +- .../middle/message/MessageContextMenu.tsx | 33 ++-- .../middle/message/MessagePhoneCall.tsx | 8 +- src/components/middle/message/Photo.tsx | 9 +- src/components/middle/message/Poll.tsx | 53 +++--- .../middle/message/ReactionAnimatedEmoji.tsx | 7 +- .../middle/message/ReactionButton.tsx | 8 +- .../middle/message/ReactionPicker.tsx | 18 +- .../middle/message/ReactionSelector.tsx | 10 +- src/components/middle/message/RoundVideo.tsx | 19 +- .../middle/message/SponsoredMessage.tsx | 8 +- .../SponsoredMessageContextMenuContainer.tsx | 15 +- src/components/middle/message/Sticker.tsx | 18 +- src/components/middle/message/Video.tsx | 15 +- src/components/middle/message/WebPage.tsx | 11 +- .../middle/message/hocs/withSelectControl.tsx | 11 +- .../middle/message/hooks/useInnerHandlers.ts | 80 ++++---- .../middle/message/hooks/useVideoAutoPause.ts | 23 +-- src/components/right/Profile.tsx | 32 ++-- src/components/right/RightColumn.tsx | 21 +-- src/components/right/RightHeader.tsx | 40 ++-- src/components/right/hooks/useProfileState.ts | 12 +- .../right/hooks/useTransitionFixes.ts | 12 +- src/components/ui/Button.tsx | 12 +- src/components/ui/InfiniteScroll.tsx | 7 +- src/components/ui/Link.tsx | 8 +- src/components/ui/ListItem.tsx | 39 ++-- src/components/ui/MenuItem.tsx | 12 +- src/components/ui/Modal.tsx | 8 +- src/components/ui/OptimizedVideo.tsx | 11 +- src/components/ui/ResponsiveHoverButton.tsx | 16 +- src/components/ui/RippleEffect.tsx | 10 +- src/components/ui/Tab.tsx | 26 +-- src/hooks/stickers/useDynamicColorListener.ts | 10 +- src/hooks/useAudioPlayer.ts | 21 +-- src/hooks/useBackgroundMode.ts | 2 +- src/hooks/useBeforeUnload.ts | 2 +- src/hooks/useBuffering.ts | 8 +- src/hooks/useContextMenuHandlers.ts | 19 +- src/hooks/useCoordsInSharedCanvas.ts | 7 +- src/hooks/useForumPanelRender.ts | 11 +- src/hooks/useHeavyAnimationCheck.ts | 2 +- src/hooks/useInfiniteScroll.ts | 8 +- src/hooks/useIntersectionObserver.ts | 19 +- src/hooks/useKeyboardListNavigation.ts | 10 +- src/hooks/useLastCallback.ts | 6 +- src/hooks/useMouseInside.ts | 16 +- src/hooks/usePriorityPlaybackCheck.ts | 2 +- src/hooks/useResize.ts | 10 +- src/hooks/useSchedule.tsx | 22 ++- src/hooks/useScrolledState.ts | 8 +- src/hooks/useStateRef.ts | 8 +- 168 files changed, 1623 insertions(+), 1630 deletions(-) diff --git a/src/components/common/AnimatedIcon.tsx b/src/components/common/AnimatedIcon.tsx index aebfe5952..ef283b1c4 100644 --- a/src/components/common/AnimatedIcon.tsx +++ b/src/components/common/AnimatedIcon.tsx @@ -1,10 +1,10 @@ -import React, { - memo, useCallback, useState, -} from '../../lib/teact/teact'; +import React, { memo, useState } from '../../lib/teact/teact'; import type { OwnProps as AnimatedStickerProps } from './AnimatedSticker'; import buildClassName from '../../util/buildClassName'; + +import useLastCallback from '../../hooks/useLastCallback'; import useMediaTransition from '../../hooks/useMediaTransition'; import useFlag from '../../hooks/useFlag'; @@ -31,20 +31,20 @@ function AnimatedIcon(props: OwnProps) { const [isAnimationLoaded, markAnimationLoaded] = useFlag(false); const transitionClassNames = useMediaTransition(noTransition || isAnimationLoaded); - const handleLoad = useCallback(() => { + const handleLoad = useLastCallback(() => { markAnimationLoaded(); onLoad?.(); - }, [markAnimationLoaded, onLoad]); + }); const [playKey, setPlayKey] = useState(String(Math.random())); - const handleClick = useCallback(() => { + const handleClick = useLastCallback(() => { if (play === true) { setPlayKey(String(Math.random())); } onClick?.(); - }, [onClick, play]); + }); return ( { + const handlePreviewLoad = useLastCallback(() => { markPreviewLoaded(); loadedPreviewUrls.add(previewUrl); - }, [markPreviewLoaded, previewUrl]); + }); const { size } = props; diff --git a/src/components/common/AnimatedSticker.tsx b/src/components/common/AnimatedSticker.tsx index 85944d793..510de010a 100644 --- a/src/components/common/AnimatedSticker.tsx +++ b/src/components/common/AnimatedSticker.tsx @@ -5,7 +5,7 @@ import { requestMeasure } from '../../lib/fasterdom/fasterdom'; import { ensureRLottie, getRLottie } from '../../lib/rlottie/RLottie.async'; import React, { - useEffect, useRef, memo, useCallback, useState, useMemo, + useEffect, useRef, memo, useState, useMemo, } from '../../lib/teact/teact'; import buildClassName from '../../util/buildClassName'; @@ -13,6 +13,7 @@ import buildStyle from '../../util/buildStyle'; import generateIdFor from '../../util/generateIdFor'; import { hexToRgb } from '../../util/switchTheme'; +import useLastCallback from '../../hooks/useLastCallback'; import useHeavyAnimationCheck, { isHeavyAnimating } from '../../hooks/useHeavyAnimationCheck'; import usePriorityPlaybackCheck, { isPriorityPlaybackActive } from '../../hooks/usePriorityPlaybackCheck'; import useBackgroundMode, { isBackgroundModeActive } from '../../hooks/useBackgroundMode'; @@ -108,7 +109,7 @@ const AnimatedSticker: FC = ({ }; }, []); - const init = useCallback(() => { + const init = useLastCallback(() => { if ( animationRef.current || isUnmountedRef.current @@ -147,10 +148,7 @@ const AnimatedSticker: FC = ({ setAnimation(newAnimation); animationRef.current = newAnimation; - }, [ - isLowPriority, noLoop, onEnded, onLoad, onLoop, quality, - renderId, sharedCanvas, sharedCanvasCoords, size, speed, tgsUrl, viewId, - ]); + }); useEffect(() => { if (getRLottie()) { @@ -158,7 +156,7 @@ const AnimatedSticker: FC = ({ } else { ensureRLottie().then(init); } - }, [init]); + }, [init, tgsUrl, sharedCanvas, sharedCanvasCoords]); const throttledInit = useThrottledCallback(init, [init], THROTTLE_MS); useSharedIntersectionObserver(sharedCanvas, throttledInit); @@ -175,7 +173,7 @@ const AnimatedSticker: FC = ({ }; }, [viewId]); - const playAnimation = useCallback((shouldRestart = false) => { + const playAnimation = useLastCallback((shouldRestart = false) => { if ( !animation || !(playRef.current || playSegmentRef.current) @@ -189,17 +187,17 @@ const AnimatedSticker: FC = ({ } else { animation.play(shouldRestart, viewId); } - }, [animation, forceOnHeavyAnimation, playRef, playSegmentRef, viewId]); + }); - const playAnimationOnRaf = useCallback(() => { + const playAnimationOnRaf = useLastCallback(() => { requestMeasure(playAnimation); - }, [playAnimation]); + }); - const pauseAnimation = useCallback(() => { + const pauseAnimation = useLastCallback(() => { if (animation?.isPlaying()) { animation.pause(viewId); } - }, [animation, viewId]); + }); useEffectWithPrevDeps(([prevNoLoop]) => { if (prevNoLoop !== undefined && noLoop !== prevNoLoop) { diff --git a/src/components/common/Audio.tsx b/src/components/common/Audio.tsx index 046532b16..80d0e6733 100644 --- a/src/components/common/Audio.tsx +++ b/src/components/common/Audio.tsx @@ -1,6 +1,6 @@ import type { FC } from '../../lib/teact/teact'; import React, { - memo, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, + memo, useEffect, useLayoutEffect, useMemo, useRef, useState, } from '../../lib/teact/teact'; import { getActions } from '../../global'; @@ -25,6 +25,8 @@ import buildClassName from '../../util/buildClassName'; import { formatMediaDateTime, formatMediaDuration, formatPastTimeShort } from '../../util/dateFormat'; import { decodeWaveform, interpolateArray } from '../../util/waveform'; import { makeTrackId } from '../../util/audioPlayer'; + +import useLastCallback from '../../hooks/useLastCallback'; import useMediaWithLoadProgress from '../../hooks/useMediaWithLoadProgress'; import useShowTransition from '../../hooks/useShowTransition'; import type { BufferedRange } from '../../hooks/useBuffering'; @@ -128,14 +130,14 @@ const Audio: FC = ({ getMessageMediaFormat(message, 'download'), ); - const handleForcePlay = useCallback(() => { + const handleForcePlay = useLastCallback(() => { setIsActivated(true); onPlay(message.id, message.chatId); - }, [message, onPlay]); + }); - const handleTrackChange = useCallback(() => { + const handleTrackChange = useLastCallback(() => { setIsActivated(false); - }, []); + }); const { isBuffered, bufferedRanges, bufferingHandlers, checkBuffering, @@ -185,7 +187,7 @@ const Audio: FC = ({ const shouldRenderCross = shouldRenderSpinner && (isLoadingForPlaying || isUploading); - const handleButtonClick = useCallback(() => { + const handleButtonClick = useLastCallback(() => { if (isUploading) { onCancelUpload?.(); return; @@ -198,7 +200,7 @@ const Audio: FC = ({ getActions().setAudioPlayerOrigin({ origin }); setIsActivated(!isActivated); playPause(); - }, [isUploading, isPlaying, isActivated, playPause, onCancelUpload, onPlay, message.id, message.chatId, origin]); + }); useEffect(() => { if (onReadMedia && isMediaUnread && (isPlaying || isDownloading)) { @@ -206,15 +208,15 @@ const Audio: FC = ({ } }, [isPlaying, isMediaUnread, onReadMedia, isDownloading]); - const handleDownloadClick = useCallback(() => { + const handleDownloadClick = useLastCallback(() => { if (isDownloading) { cancelMessageMediaDownload({ message }); } else { downloadMessageMedia({ message }); } - }, [cancelMessageMediaDownload, downloadMessageMedia, isDownloading, message]); + }); - const handleSeek = useCallback((e: MouseEvent | TouchEvent) => { + const handleSeek = useLastCallback((e: MouseEvent | TouchEvent) => { if (isSeeking.current && seekerRef.current) { const { width, left } = seekerRef.current.getBoundingClientRect(); const clientX = e instanceof MouseEvent ? e.clientX : e.targetTouches[0].clientX; @@ -222,25 +224,25 @@ const Audio: FC = ({ // Prevent track skipping while seeking near end setCurrentTime(Math.max(Math.min(duration * ((clientX - left) / width), duration - 0.1), 0.001)); } - }, [duration, setCurrentTime]); + }); - const handleStartSeek = useCallback((e: MouseEvent | TouchEvent) => { + const handleStartSeek = useLastCallback((e: MouseEvent | TouchEvent) => { if (e instanceof MouseEvent && e.button === 2) return; isSeeking.current = true; handleSeek(e); - }, [handleSeek]); + }); - const handleStopSeek = useCallback(() => { + const handleStopSeek = useLastCallback(() => { isSeeking.current = false; - }, []); + }); - const handleDateClick = useCallback(() => { + const handleDateClick = useLastCallback(() => { onDateClick!(message.id, message.chatId); - }, [onDateClick, message.id, message.chatId]); + }); - const handleTranscribe = useCallback(() => { + const handleTranscribe = useLastCallback(() => { transcribeAudio({ chatId: message.chatId, messageId: message.id }); - }, [message.chatId, message.id, transcribeAudio]); + }); useEffect(() => { if (!seekerRef.current || !withSeekline) return undefined; diff --git a/src/components/common/Avatar.tsx b/src/components/common/Avatar.tsx index 8ce096d90..018508e6d 100644 --- a/src/components/common/Avatar.tsx +++ b/src/components/common/Avatar.tsx @@ -1,6 +1,6 @@ import type { MouseEvent as ReactMouseEvent } from 'react'; import React, { - memo, useCallback, useMemo, useRef, + memo, useMemo, useRef, } from '../../lib/teact/teact'; import type { FC, TeactNode } from '../../lib/teact/teact'; @@ -33,6 +33,7 @@ import { useFastClick } from '../../hooks/useFastClick'; import OptimizedVideo from '../ui/OptimizedVideo'; import './Avatar.scss'; +import useLastCallback from '../../hooks/useLastCallback'; const LOOP_COUNT = 3; @@ -111,7 +112,7 @@ const Avatar: FC = ({ return onlineTransitionClassNames.split(' ').map((c) => (c === 'shown' ? 'online' : `online-${c}`)).join(' '); }, [onlineTransitionClassNames]); - const handleVideoEnded = useCallback((e) => { + const handleVideoEnded = useLastCallback((e) => { const video = e.currentTarget; if (!videoBlobUrl) return; @@ -121,7 +122,7 @@ const Avatar: FC = ({ if (videoLoopCountRef.current >= LOOP_COUNT) { video.style.display = 'none'; } - }, [loopIndefinitely, videoBlobUrl]); + }); const lang = useLang(); diff --git a/src/components/common/ChatExtra.tsx b/src/components/common/ChatExtra.tsx index cab860828..2b3909096 100644 --- a/src/components/common/ChatExtra.tsx +++ b/src/components/common/ChatExtra.tsx @@ -1,6 +1,6 @@ import type { FC } from '../../lib/teact/teact'; import React, { - memo, useCallback, useEffect, useMemo, useState, + memo, useEffect, useMemo, useState, } from '../../lib/teact/teact'; import { getActions, withGlobal } from '../../global'; @@ -34,6 +34,8 @@ import { copyTextToClipboard } from '../../util/clipboard'; import { formatPhoneNumberWithCode } from '../../util/phoneNumber'; import { debounce } from '../../util/schedulers'; import stopEvent from '../../util/stopEvent'; + +import useLastCallback from '../../hooks/useLastCallback'; import useLang from '../../hooks/useLang'; import ListItem from '../ui/ListItem'; @@ -123,7 +125,7 @@ const ChatExtra: FC = ({ : getChatLink(chat) || chatInviteLink; }, [chat, isTopicInfo, activeChatUsernames, topicId, chatInviteLink]); - const handleNotificationChange = useCallback(() => { + const handleNotificationChange = useLastCallback(() => { setAreNotificationsEnabled((current) => { const newAreNotificationsEnabled = !current; @@ -141,7 +143,7 @@ const ChatExtra: FC = ({ return newAreNotificationsEnabled; }); - }, [chatId, isTopicInfo, topicId, updateChatMutedState, updateTopicMutedState]); + }); if (!chat || chat.isRestricted || (isSelf && !forceShowSelf)) { return undefined; diff --git a/src/components/common/ChatOrUserPicker.tsx b/src/components/common/ChatOrUserPicker.tsx index 0ea375d3a..d87b5ddc4 100644 --- a/src/components/common/ChatOrUserPicker.tsx +++ b/src/components/common/ChatOrUserPicker.tsx @@ -1,6 +1,6 @@ import type { FC } from '../../lib/teact/teact'; import React, { - memo, useRef, useCallback, useState, useMemo, + memo, useRef, useState, useMemo, } from '../../lib/teact/teact'; import { getActions } from '../../global'; @@ -10,6 +10,8 @@ import { REM } from './helpers/mediaDimensions'; import { CHAT_HEIGHT_PX } from '../../config'; import renderText from './helpers/renderText'; import { getCanPostInChat, isUserId } from '../../global/helpers'; + +import useLastCallback from '../../hooks/useLastCallback'; import useInfiniteScroll from '../../hooks/useInfiniteScroll'; import useLang from '../../hooks/useLang'; import useKeyboardListNavigation from '../../hooks/useKeyboardListNavigation'; @@ -76,9 +78,9 @@ const ChatOrUserPicker: FC = ({ const activeKey = forumId ? TOPIC_LIST_SLIDE : CHAT_LIST_SLIDE; const viewportOffset = chatOrUserIds!.indexOf(viewportIds![0]); - const resetSearch = useCallback(() => { + const resetSearch = useLastCallback(() => { onSearchChange(''); - }, [onSearchChange]); + }); useInputFocusOnOpen(searchRef, isOpen && activeKey === CHAT_LIST_SLIDE, resetSearch); useInputFocusOnOpen(topicSearchRef, isOpen && activeKey === TOPIC_LIST_SLIDE); @@ -106,18 +108,18 @@ const ChatOrUserPicker: FC = ({ return [Object.keys(result).map(Number), result]; }, [chatsById, forumId, topicSearch]); - const handleHeaderBackClick = useCallback(() => { + const handleHeaderBackClick = useLastCallback(() => { setForumId(undefined); setTopicSearch(''); - }, []); + }); - const handleSearchChange = useCallback((e: React.ChangeEvent) => { + const handleSearchChange = useLastCallback((e: React.ChangeEvent) => { onSearchChange(e.currentTarget.value); - }, [onSearchChange]); + }); - const handleTopicSearchChange = useCallback((e: React.ChangeEvent) => { + const handleTopicSearchChange = useLastCallback((e: React.ChangeEvent) => { setTopicSearch(e.currentTarget.value); - }, []); + }); const handleKeyDown = useKeyboardListNavigation(containerRef, isOpen, (index) => { if (viewportIds && viewportIds.length > 0) { @@ -138,7 +140,7 @@ const ChatOrUserPicker: FC = ({ } }, '.ListItem-button', true); - const handleClick = useCallback((e: React.MouseEvent, chatId: string) => { + const handleClick = useLastCallback((e: React.MouseEvent, chatId: string) => { const chat = chatsById?.[chatId]; if (chat?.isForum) { if (!chat.topics) loadTopics({ chatId }); @@ -147,11 +149,11 @@ const ChatOrUserPicker: FC = ({ } else { onSelectChatOrUser(chatId); } - }, [chatsById, loadTopics, onSelectChatOrUser, resetSearch]); + }); - const handleTopicClick = useCallback((e: React.MouseEvent, topicId: number) => { + const handleTopicClick = useLastCallback((e: React.MouseEvent, topicId: number) => { onSelectChatOrUser(forumId!, topicId); - }, [forumId, onSelectChatOrUser]); + }); function renderTopicList() { return ( diff --git a/src/components/common/CustomEmoji.tsx b/src/components/common/CustomEmoji.tsx index 957d0a598..72a6a916a 100644 --- a/src/components/common/CustomEmoji.tsx +++ b/src/components/common/CustomEmoji.tsx @@ -1,6 +1,4 @@ -import React, { - memo, useCallback, useRef, useState, -} from '../../lib/teact/teact'; +import React, { memo, useRef, useState } from '../../lib/teact/teact'; import { getGlobal } from '../../global'; import type { FC, TeactNode } from '../../lib/teact/teact'; @@ -11,6 +9,7 @@ import buildClassName from '../../util/buildClassName'; import safePlay from '../../util/safePlay'; import { selectIsAlwaysHighPriorityEmoji } from '../../global/selectors'; +import useLastCallback from '../../hooks/useLastCallback'; import useCustomEmoji from './hooks/useCustomEmoji'; import useDynamicColorListener from '../../hooks/stickers/useDynamicColorListener'; @@ -79,7 +78,7 @@ const CustomEmoji: FC = ({ const hasCustomColor = customEmoji?.shouldUseTextColor; const customColor = useDynamicColorListener(containerRef, !hasCustomColor); - const handleVideoEnded = useCallback((e) => { + const handleVideoEnded = useLastCallback((e) => { if (!loopLimit) return; loopCountRef.current += 1; @@ -91,9 +90,9 @@ const CustomEmoji: FC = ({ // Loop manually safePlay(e.currentTarget); } - }, [loopLimit]); + }); - const handleStickerLoop = useCallback(() => { + const handleStickerLoop = useLastCallback(() => { if (!loopLimit) return; loopCountRef.current += 1; @@ -102,7 +101,7 @@ const CustomEmoji: FC = ({ if (loopCountRef.current >= loopLimit - 1) { setShouldLoop(false); } - }, [loopLimit]); + }); const isHq = customEmoji?.stickerSetInfo && selectIsAlwaysHighPriorityEmoji(getGlobal(), customEmoji.stickerSetInfo); diff --git a/src/components/common/CustomEmojiPicker.tsx b/src/components/common/CustomEmojiPicker.tsx index 7739932d3..173b8c66c 100644 --- a/src/components/common/CustomEmojiPicker.tsx +++ b/src/components/common/CustomEmojiPicker.tsx @@ -1,5 +1,5 @@ import React, { - useEffect, memo, useRef, useMemo, useCallback, + useEffect, memo, useRef, useMemo, } from '../../lib/teact/teact'; import { getGlobal, withGlobal } from '../../global'; @@ -34,6 +34,7 @@ import { selectIsCurrentUserPremium, } from '../../global/selectors'; +import useLastCallback from '../../hooks/useLastCallback'; import useAsyncRendering from '../right/hooks/useAsyncRendering'; import useHorizontalScroll from '../../hooks/useHorizontalScroll'; import useLang from '../../hooks/useLang'; @@ -276,13 +277,13 @@ const CustomEmojiPicker: FC = ({ animateHorizontalScroll(header, newLeft); }, [areAddedLoaded, activeSetIndex]); - const handleEmojiSelect = useCallback((emoji: ApiSticker) => { + const handleEmojiSelect = useLastCallback((emoji: ApiSticker) => { onCustomEmojiSelect(emoji); - }, [onCustomEmojiSelect]); + }); - const handleReactionSelect = useCallback((reaction: ApiReaction) => { + const handleReactionSelect = useLastCallback((reaction: ApiReaction) => { onReactionSelect?.(reaction); - }, [onReactionSelect]); + }); function renderCover(stickerSet: StickerSetOrReactionsSetOrRecent, index: number) { const firstSticker = stickerSet.stickers?.[0]; diff --git a/src/components/common/Document.tsx b/src/components/common/Document.tsx index 6b6aea582..68410ec8c 100644 --- a/src/components/common/Document.tsx +++ b/src/components/common/Document.tsx @@ -1,6 +1,6 @@ import type { FC } from '../../lib/teact/teact'; import React, { - useCallback, memo, useRef, useEffect, useState, + memo, useRef, useEffect, useState, } from '../../lib/teact/teact'; import { getActions } from '../../global'; @@ -19,6 +19,8 @@ import { isMessageDocumentVideo, } from '../../global/helpers'; import type { ObserveFn } from '../../hooks/useIntersectionObserver'; + +import useLastCallback from '../../hooks/useLastCallback'; import { useIsIntersecting } from '../../hooks/useIntersectionObserver'; import useMediaWithLoadProgress from '../../hooks/useMediaWithLoadProgress'; import useMedia from '../../hooks/useMedia'; @@ -108,7 +110,7 @@ const Document: FC = ({ SUPPORTED_VIDEO_CONTENT_TYPES.has(document.mimeType) || SUPPORTED_IMAGE_CONTENT_TYPES.has(document.mimeType) ); - const handleClick = useCallback(() => { + const handleClick = useLastCallback(() => { if (isUploading) { if (onCancelUpload) { onCancelUpload(); @@ -131,13 +133,11 @@ const Document: FC = ({ } else { dispatch.downloadMessageMedia({ message }); } - }, [ - isUploading, isDownloading, isTransferring, withMediaViewer, onCancelUpload, dispatch, message, onMediaClick, - ]); + }); - const handleDateClick = useCallback(() => { + const handleDateClick = useLastCallback(() => { onDateClick!(message.id, message.chatId); - }, [onDateClick, message.id, message.chatId]); + }); return ( = ({ handleContextMenuClose, handleContextMenuHide, } = useContextMenuHandlers(ref); - const getTriggerElement = useCallback(() => ref.current, []); - - const getRootElement = useCallback( - () => ref.current!.closest('.custom-scroll, .no-scrollbar'), - [], - ); - - const getMenuElement = useCallback( - () => ref.current!.querySelector('.gif-context-menu .bubble'), - [], - ); + const getTriggerElement = useLastCallback(() => ref.current); + const getRootElement = useLastCallback(() => ref.current!.closest('.custom-scroll, .no-scrollbar')); + const getMenuElement = useLastCallback(() => ref.current!.querySelector('.gif-context-menu .bubble')); const { positionX, positionY, transformOriginX, transformOriginY, style: menuStyle, @@ -90,42 +83,42 @@ const GifButton: FC = ({ getMenuElement, ); - const handleClick = useCallback(() => { + const handleClick = useLastCallback(() => { if (isContextMenuOpen || !onClick) return; onClick({ ...gif, blobUrl: videoData, }); - }, [isContextMenuOpen, onClick, gif, videoData]); + }); - const handleUnsaveClick = useCallback((e: React.MouseEvent) => { + const handleUnsaveClick = useLastCallback((e: React.MouseEvent) => { e.stopPropagation(); e.preventDefault(); onUnsaveClick!(gif); - }, [onUnsaveClick, gif]); + }); - const handleContextDelete = useCallback(() => { + const handleContextDelete = useLastCallback(() => { onUnsaveClick?.(gif); - }, [gif, onUnsaveClick]); + }); - const handleSendQuiet = useCallback(() => { + const handleSendQuiet = useLastCallback(() => { onClick!({ ...gif, blobUrl: videoData, }, true); - }, [gif, onClick, videoData]); + }); - const handleSendScheduled = useCallback(() => { + const handleSendScheduled = useLastCallback(() => { onClick!({ ...gif, blobUrl: videoData, }, undefined, true); - }, [gif, onClick, videoData]); + }); - const handleMouseDown = useCallback((e: React.MouseEvent) => { + const handleMouseDown = useLastCallback((e: React.MouseEvent) => { preventMessageInputBlurWithBubbling(e); handleBeforeContextMenu(e); - }, [handleBeforeContextMenu]); + }); useEffect(() => { if (isDisabled) handleContextMenuClose(); diff --git a/src/components/common/GroupChatInfo.tsx b/src/components/common/GroupChatInfo.tsx index 075458241..2ec8ced61 100644 --- a/src/components/common/GroupChatInfo.tsx +++ b/src/components/common/GroupChatInfo.tsx @@ -1,8 +1,6 @@ import type { MouseEvent as ReactMouseEvent } from 'react'; import type { FC } from '../../lib/teact/teact'; -import React, { - useEffect, useCallback, memo, useMemo, -} from '../../lib/teact/teact'; +import React, { useEffect, memo, useMemo } from '../../lib/teact/teact'; import { getActions, withGlobal } from '../../global'; import type { @@ -34,6 +32,7 @@ import TypingStatus from './TypingStatus'; import DotAnimation from './DotAnimation'; import FullNameTitle from './FullNameTitle'; import TopicIcon from './TopicIcon'; +import useLastCallback from '../../hooks/useLastCallback'; const TOPIC_ICON_SIZE = 2.5 * REM; @@ -105,16 +104,18 @@ const GroupChatInfo: FC = ({ } }, [chatId, isMin, lastSyncTime, withFullInfo, loadFullChat, loadProfilePhotos, isSuperGroup, withMediaViewer]); - const handleAvatarViewerOpen = useCallback((e: ReactMouseEvent, hasMedia: boolean) => { - if (chat && hasMedia) { - e.stopPropagation(); - openMediaViewer({ - avatarOwnerId: chat.id, - mediaId: 0, - origin: avatarSize === 'jumbo' ? MediaViewerOrigin.ProfileAvatar : MediaViewerOrigin.MiddleHeaderAvatar, - }); - } - }, [chat, avatarSize, openMediaViewer]); + const handleAvatarViewerOpen = useLastCallback( + (e: ReactMouseEvent, hasMedia: boolean) => { + if (chat && hasMedia) { + e.stopPropagation(); + openMediaViewer({ + avatarOwnerId: chat.id, + mediaId: 0, + origin: avatarSize === 'jumbo' ? MediaViewerOrigin.ProfileAvatar : MediaViewerOrigin.MiddleHeaderAvatar, + }); + } + }, + ); const lang = useLang(); const mainUsername = useMemo(() => chat && withUsername && getMainUsername(chat), [chat, withUsername]); diff --git a/src/components/common/Media.tsx b/src/components/common/Media.tsx index 4692737db..2f67f3d4f 100644 --- a/src/components/common/Media.tsx +++ b/src/components/common/Media.tsx @@ -1,4 +1,4 @@ -import React, { memo, useCallback, useRef } from '../../lib/teact/teact'; +import React, { memo, useRef } from '../../lib/teact/teact'; import type { FC } from '../../lib/teact/teact'; import type { ApiMessage } from '../../api/types'; @@ -15,6 +15,7 @@ import { } from '../../global/helpers'; import buildClassName from '../../util/buildClassName'; +import useLastCallback from '../../hooks/useLastCallback'; import useMedia from '../../hooks/useMedia'; import useMediaTransition from '../../hooks/useMediaTransition'; import useFlag from '../../hooks/useFlag'; @@ -52,10 +53,10 @@ const Media: FC = ({ const hasSpoiler = getMessageIsSpoiler(message); const [isSpoilerShown, , hideSpoiler] = useFlag(hasSpoiler); - const handleClick = useCallback(() => { + const handleClick = useLastCallback(() => { hideSpoiler(); onClick!(message.id, message.chatId); - }, [hideSpoiler, message, onClick]); + }); return (
= ({ ); const canvasRef = useCanvasBlur(thumbDataUri, !shouldRender, undefined, BLUR_RADIUS, width, height); - const handleClick = useCallback((e: React.MouseEvent) => { + const handleClick = useLastCallback((e: React.MouseEvent) => { if (!ref.current) return; const rect = e.currentTarget.getBoundingClientRect(); const x = e.clientX - rect.left; @@ -44,7 +46,7 @@ const MediaSpoiler: FC = ({ const shiftX = x - (rect.width / 2); const shiftY = y - (rect.height / 2); ref.current.setAttribute('style', `--click-shift-x: ${shiftX}px; --click-shift-y: ${shiftY}px`); - }, []); + }); if (!shouldRender) { return undefined; diff --git a/src/components/common/Picker.tsx b/src/components/common/Picker.tsx index ebfbc585c..1581d3795 100644 --- a/src/components/common/Picker.tsx +++ b/src/components/common/Picker.tsx @@ -1,5 +1,5 @@ import React, { - useCallback, useRef, useEffect, memo, useMemo, + useRef, useEffect, memo, useMemo, } from '../../lib/teact/teact'; import { requestMutation } from '../../lib/fasterdom/fasterdom'; @@ -8,6 +8,8 @@ import type { FC } from '../../lib/teact/teact'; import { isUserId } from '../../global/helpers'; import buildClassName from '../../util/buildClassName'; import { MEMO_EMPTY_ARRAY } from '../../util/memo'; + +import useLastCallback from '../../hooks/useLastCallback'; import useInfiniteScroll from '../../hooks/useInfiniteScroll'; import useLang from '../../hooks/useLang'; @@ -99,7 +101,7 @@ const Picker: FC = ({ }); }, [itemIds, lockedIdsSet]); - const handleItemClick = useCallback((id: string) => { + const handleItemClick = useLastCallback((id: string) => { if (lockedIdsSet.has(id)) { onDisabledClick?.(id); return; @@ -113,12 +115,12 @@ const Picker: FC = ({ } onSelectedIdsChange?.(newSelectedIds); onFilterChange?.(''); - }, [lockedIdsSet, selectedIds, onSelectedIdsChange, onFilterChange, onDisabledClick]); + }); - const handleFilterChange = useCallback((e: React.ChangeEvent) => { + const handleFilterChange = useLastCallback((e: React.ChangeEvent) => { const { value } = e.currentTarget; onFilterChange?.(value); - }, [onFilterChange]); + }); const [viewportIds, getMore] = useInfiniteScroll(onLoadMore, sortedItemIds, Boolean(filterValue)); diff --git a/src/components/common/PrivateChatInfo.tsx b/src/components/common/PrivateChatInfo.tsx index 0fd5eecfe..1ae59c7b1 100644 --- a/src/components/common/PrivateChatInfo.tsx +++ b/src/components/common/PrivateChatInfo.tsx @@ -1,6 +1,4 @@ -import React, { - useEffect, useCallback, memo, useMemo, -} from '../../lib/teact/teact'; +import React, { useEffect, memo, useMemo } from '../../lib/teact/teact'; import { getActions, withGlobal } from '../../global'; import type { FC } from '../../lib/teact/teact'; @@ -19,6 +17,7 @@ import { getMainUsername, getUserStatus, isUserOnline } from '../../global/helpe import buildClassName from '../../util/buildClassName'; import renderText from './helpers/renderText'; +import useLastCallback from '../../hooks/useLastCallback'; import useLang from '../../hooks/useLang'; import Avatar from './Avatar'; @@ -88,16 +87,18 @@ const PrivateChatInfo: FC = ({ } }, [userId, loadFullUser, loadProfilePhotos, lastSyncTime, withFullInfo, withMediaViewer]); - const handleAvatarViewerOpen = useCallback((e: React.MouseEvent, hasMedia: boolean) => { - if (user && hasMedia) { - e.stopPropagation(); - openMediaViewer({ - avatarOwnerId: user.id, - mediaId: 0, - origin: avatarSize === 'jumbo' ? MediaViewerOrigin.ProfileAvatar : MediaViewerOrigin.MiddleHeaderAvatar, - }); - } - }, [user, avatarSize, openMediaViewer]); + const handleAvatarViewerOpen = useLastCallback( + (e: React.MouseEvent, hasMedia: boolean) => { + if (user && hasMedia) { + e.stopPropagation(); + openMediaViewer({ + avatarOwnerId: user.id, + mediaId: 0, + origin: avatarSize === 'jumbo' ? MediaViewerOrigin.ProfileAvatar : MediaViewerOrigin.MiddleHeaderAvatar, + }); + } + }, + ); const lang = useLang(); const mainUsername = useMemo(() => user && withUsername && getMainUsername(user), [user, withUsername]); diff --git a/src/components/common/ProfileInfo.tsx b/src/components/common/ProfileInfo.tsx index 3ed78984e..85c684fac 100644 --- a/src/components/common/ProfileInfo.tsx +++ b/src/components/common/ProfileInfo.tsx @@ -1,7 +1,5 @@ import type { FC } from '../../lib/teact/teact'; -import React, { - useEffect, useCallback, memo, useState, -} from '../../lib/teact/teact'; +import React, { useEffect, memo, useState } from '../../lib/teact/teact'; import { getActions, withGlobal } from '../../global'; import type { @@ -29,6 +27,7 @@ import { captureEvents, SwipeDirection } from '../../util/captureEvents'; import buildClassName from '../../util/buildClassName'; import renderText from './helpers/renderText'; +import useLastCallback from '../../hooks/useLastCallback'; import usePhotosPreload from './hooks/usePhotosPreload'; import useLang from '../../hooks/useLang'; import usePrevious from '../../hooks/usePrevious'; @@ -40,6 +39,7 @@ import TopicIcon from './TopicIcon'; import Avatar from './Avatar'; import './ProfileInfo.scss'; + import styles from './ProfileInfo.module.scss'; type OwnProps = { @@ -130,35 +130,35 @@ const ProfileInfo: FC = ({ usePhotosPreload(user || chat, photos, currentPhotoIndex); - const handleProfilePhotoClick = useCallback(() => { + const handleProfilePhotoClick = useLastCallback(() => { openMediaViewer({ avatarOwnerId: userId || chatId, mediaId: currentPhotoIndex, origin: forceShowSelf ? MediaViewerOrigin.SettingsAvatar : MediaViewerOrigin.ProfileAvatar, }); - }, [openMediaViewer, userId, chatId, currentPhotoIndex, forceShowSelf]); + }); - const handleClickPremium = useCallback(() => { + const handleClickPremium = useLastCallback(() => { if (!user) return; openPremiumModal({ fromUserId: user.id }); - }, [openPremiumModal, user]); + }); - const selectPreviousMedia = useCallback(() => { + const selectPreviousMedia = useLastCallback(() => { if (isFirst) { return; } setHasSlideAnimation(true); setCurrentPhotoIndex(currentPhotoIndex - 1); - }, [currentPhotoIndex, isFirst]); + }); - const selectNextMedia = useCallback(() => { + const selectNextMedia = useLastCallback(() => { if (isLast) { return; } setHasSlideAnimation(true); setCurrentPhotoIndex(currentPhotoIndex + 1); - }, [currentPhotoIndex, isLast]); + }); function handleSelectFallbackPhoto() { if (!isFirst) return; diff --git a/src/components/common/ReactionEmoji.tsx b/src/components/common/ReactionEmoji.tsx index a2965ea52..adcd003fe 100644 --- a/src/components/common/ReactionEmoji.tsx +++ b/src/components/common/ReactionEmoji.tsx @@ -1,5 +1,5 @@ import React, { - memo, useCallback, useMemo, useRef, + memo, useMemo, useRef, } from '../../lib/teact/teact'; import type { FC } from '../../lib/teact/teact'; @@ -10,6 +10,7 @@ import { EMOJI_SIZE_PICKER } from '../../config'; import buildClassName from '../../util/buildClassName'; import { getDocumentMediaHash, isSameReaction } from '../../global/helpers'; +import useLastCallback from '../../hooks/useLastCallback'; import useCoordsInSharedCanvas from '../../hooks/useCoordsInSharedCanvas'; import useMediaTransition from '../../hooks/useMediaTransition'; import useMedia from '../../hooks/useMedia'; @@ -54,9 +55,9 @@ const ReactionEmoji: FC = ({ availableReaction?.selectAnimation ? getDocumentMediaHash(availableReaction.selectAnimation) : undefined, !animationId, ); - const handleClick = useCallback(() => { + const handleClick = useLastCallback(() => { onClick(reaction); - }, [onClick, reaction]); + }); const transitionClassNames = useMediaTransition(mediaData); const fullClassName = buildClassName( diff --git a/src/components/common/ReportModal.tsx b/src/components/common/ReportModal.tsx index 2ad1ba0ac..0ee06ceff 100644 --- a/src/components/common/ReportModal.tsx +++ b/src/components/common/ReportModal.tsx @@ -1,13 +1,12 @@ import type { ChangeEvent } from 'react'; import type { FC } from '../../lib/teact/teact'; -import React, { - memo, useCallback, useMemo, useState, -} from '../../lib/teact/teact'; +import React, { memo, useMemo, useState } from '../../lib/teact/teact'; import { getActions } from '../../global'; import type { ApiPhoto, ApiReportReason } from '../../api/types'; +import useLastCallback from '../../hooks/useLastCallback'; import useLang from '../../hooks/useLang'; import Modal from '../ui/Modal'; @@ -44,7 +43,7 @@ const ReportModal: FC = ({ const [selectedReason, setSelectedReason] = useState('spam'); const [description, setDescription] = useState(''); - const handleReport = useCallback(() => { + const handleReport = useLastCallback(() => { switch (subject) { case 'messages': reportMessages({ messageIds: messageIds!, reason: selectedReason, description }); @@ -60,27 +59,15 @@ const ReportModal: FC = ({ break; } onClose(); - }, [ - description, - exitMessageSelectMode, - messageIds, - photo, - onClose, - reportMessages, - selectedReason, - chatId, - reportProfilePhoto, - reportPeer, - subject, - ]); + }); - const handleSelectReason = useCallback((value: string) => { + const handleSelectReason = useLastCallback((value: string) => { setSelectedReason(value as ApiReportReason); - }, []); + }); - const handleDescriptionChange = useCallback((e: ChangeEvent) => { + const handleDescriptionChange = useLastCallback((e: ChangeEvent) => { setDescription(e.target.value); - }, []); + }); const lang = useLang(); diff --git a/src/components/common/SafeLink.tsx b/src/components/common/SafeLink.tsx index faf5be892..93f2ee019 100644 --- a/src/components/common/SafeLink.tsx +++ b/src/components/common/SafeLink.tsx @@ -1,4 +1,4 @@ -import React, { memo, useCallback } from '../../lib/teact/teact'; +import React, { memo } from '../../lib/teact/teact'; import { getActions } from '../../global'; import convertPunycode from '../../lib/punycode'; @@ -10,6 +10,7 @@ import { } from '../../config'; import buildClassName from '../../util/buildClassName'; import { ensureProtocol } from '../../util/ensureProtocol'; +import useLastCallback from '../../hooks/useLastCallback'; type OwnProps = { url?: string; @@ -31,14 +32,14 @@ const SafeLink: FC = ({ const content = children || text; const isSafe = url === text; - const handleClick = useCallback((e: React.MouseEvent) => { + const handleClick = useLastCallback((e: React.MouseEvent) => { if (!url) return true; e.preventDefault(); openUrl({ url, shouldSkipModal: isSafe }); return false; - }, [isSafe, openUrl, url]); + }); if (!url) { return undefined; diff --git a/src/components/common/SeenByModal.tsx b/src/components/common/SeenByModal.tsx index eead0e436..9900321fd 100644 --- a/src/components/common/SeenByModal.tsx +++ b/src/components/common/SeenByModal.tsx @@ -1,9 +1,11 @@ -import React, { useCallback, memo, useMemo } from '../../lib/teact/teact'; +import React, { memo, useMemo } from '../../lib/teact/teact'; import { getActions, withGlobal } from '../../global'; import { selectChatMessage, selectTabState } from '../../global/selectors'; import { formatDateAtTime } from '../../util/dateFormat'; import buildClassName from '../../util/buildClassName'; + +import useLastCallback from '../../hooks/useLastCallback'; import useLang from '../../hooks/useLang'; import useCurrentOrPrev from '../../hooks/useCurrentOrPrev'; @@ -47,17 +49,17 @@ function SeenByModal({ return result; }, [renderingSeenByDates]); - const handleClick = useCallback((userId: string) => { + const handleClick = useLastCallback((userId: string) => { closeSeenByModal(); setTimeout(() => { openChat({ id: userId }); }, CLOSE_ANIMATION_DURATION); - }, [closeSeenByModal, openChat]); + }); - const handleCloseSeenByModal = useCallback(() => { + const handleCloseSeenByModal = useLastCallback(() => { closeSeenByModal(); - }, [closeSeenByModal]); + }); return ( ref.current, []); - - const getRootElement = useCallback( - () => ref.current!.closest('.custom-scroll, .no-scrollbar'), - [], - ); - - const getMenuElement = useCallback( - () => { - return isStatusPicker ? menuRef.current : ref.current!.querySelector('.sticker-context-menu .bubble'); - }, - [isStatusPicker], - ); + const getTriggerElement = useLastCallback(() => ref.current); + const getRootElement = useLastCallback(() => ref.current!.closest('.custom-scroll, .no-scrollbar')); + const getMenuElement = useLastCallback(() => { + return isStatusPicker ? menuRef.current : ref.current!.querySelector('.sticker-context-menu .bubble'); + }); const getLayout = () => ({ withPortal: isStatusPicker, shouldAvoidNegativePosition: true }); @@ -165,38 +159,38 @@ const StickerButton = ) => { + const handleRemoveClick = useLastCallback((e: ReactMouseEvent) => { e.stopPropagation(); e.preventDefault(); onRemoveRecentClick!(sticker); - }, [onRemoveRecentClick, sticker]); + }); - const handleContextRemoveRecent = useCallback(() => { + const handleContextRemoveRecent = useLastCallback(() => { onRemoveRecentClick!(sticker); - }, [onRemoveRecentClick, sticker]); + }); - const handleContextUnfave = useCallback(() => { + const handleContextUnfave = useLastCallback(() => { onUnfaveClick!(sticker); - }, [onUnfaveClick, sticker]); + }); - const handleContextFave = useCallback(() => { + const handleContextFave = useLastCallback(() => { onFaveClick!(sticker); - }, [onFaveClick, sticker]); + }); - const handleSendQuiet = useCallback(() => { + const handleSendQuiet = useLastCallback(() => { onClick?.(clickArg, true); - }, [clickArg, onClick]); + }); - const handleSendScheduled = useCallback(() => { + const handleSendScheduled = useLastCallback(() => { onClick?.(clickArg, undefined, true); - }, [clickArg, onClick]); + }); - const handleOpenSet = useCallback(() => { + const handleOpenSet = useLastCallback(() => { openStickerSet({ stickerSetInfo }); - }, [openStickerSet, stickerSetInfo]); + }); - const handleEmojiStatusExpiresClick = useCallback((e: React.SyntheticEvent, duration = 0) => { + const handleEmojiStatusExpiresClick = useLastCallback((e: React.SyntheticEvent, duration = 0) => { e.preventDefault(); e.stopPropagation(); @@ -206,7 +200,7 @@ const StickerButton = = ({ const isEmoji = stickerSet.isEmoji; const isPremiumSet = !isRecent && selectIsSetPremium(stickerSet); - const handleClearRecent = useCallback(() => { + const handleClearRecent = useLastCallback(() => { if (isReactionPicker) { clearRecentReactions(); } else if (isEmoji) { @@ -149,11 +150,9 @@ const StickerSet: FC = ({ clearRecentStickers(); } closeConfirmModal(); - }, [ - clearRecentCustomEmoji, clearRecentReactions, clearRecentStickers, closeConfirmModal, isEmoji, isReactionPicker, - ]); + }); - const handleAddClick = useCallback(() => { + const handleAddClick = useLastCallback(() => { if (isPremiumSet && !isCurrentUserPremium) { openPremiumModal({ initialSection: 'animated_emoji', @@ -163,9 +162,9 @@ const StickerSet: FC = ({ stickerSetId: stickerSet.id, }); } - }, [isCurrentUserPremium, isPremiumSet, openPremiumModal, stickerSet, toggleStickerSet]); + }); - const handleDefaultTopicIconClick = useCallback(() => { + const handleDefaultTopicIconClick = useLastCallback(() => { onStickerSelect?.({ id: DEFAULT_TOPIC_ICON_STICKER_ID, isLottie: false, @@ -174,9 +173,9 @@ const StickerSet: FC = ({ shortName: 'dummy', }, } satisfies ApiSticker); - }, [onStickerSelect]); + }); - const handleDefaultStatusIconClick = useCallback(() => { + const handleDefaultStatusIconClick = useLastCallback(() => { onStickerSelect?.({ id: DEFAULT_STATUS_ICON_ID, isLottie: false, @@ -185,23 +184,24 @@ const StickerSet: FC = ({ shortName: 'dummy', }, } satisfies ApiSticker); - }, [onStickerSelect]); + }); const itemSize = isEmoji ? EMOJI_SIZE_PICKER : STICKER_SIZE_PICKER; const margin = isEmoji ? emojiMarginPx : stickerMarginPx; const verticalMargin = isEmoji ? emojiVerticalMarginPx : stickerMarginPx; - const calculateItemsPerRow = useCallback((width: number) => { + const calculateItemsPerRow = useLastCallback((width: number) => { if (!width) { return getItemsPerRowFallback(windowWidth); } return Math.floor((width + margin) / (itemSize + margin)); - }, [itemSize, margin, windowWidth]); + }); - const handleResize = useCallback((entry: ResizeObserverEntry) => { + const handleResize = useLastCallback((entry: ResizeObserverEntry) => { setItemsPerRow(calculateItemsPerRow(entry.contentRect.width)); - }, [calculateItemsPerRow]); + }); + useResizeObserver(ref, handleResize); useEffect(() => { diff --git a/src/components/common/WebLink.tsx b/src/components/common/WebLink.tsx index b4d7a895c..486b3265a 100644 --- a/src/components/common/WebLink.tsx +++ b/src/components/common/WebLink.tsx @@ -1,5 +1,5 @@ import type { FC } from '../../lib/teact/teact'; -import React, { memo, useCallback } from '../../lib/teact/teact'; +import React, { memo } from '../../lib/teact/teact'; import type { ApiMessage, ApiWebPage } from '../../api/types'; import type { TextPart } from '../../types'; @@ -13,9 +13,11 @@ import buildClassName from '../../util/buildClassName'; import trimText from '../../util/trimText'; import renderText from './helpers/renderText'; import { formatPastTimeShort } from '../../util/dateFormat'; -import useLang from '../../hooks/useLang'; import { renderMessageSummary } from './helpers/renderMessageText'; +import useLastCallback from '../../hooks/useLastCallback'; +import useLang from '../../hooks/useLang'; + import Media from './Media'; import Link from '../ui/Link'; import SafeLink from './SafeLink'; @@ -58,9 +60,9 @@ const WebLink: FC = ({ } } - const handleMessageClick = useCallback(() => { + const handleMessageClick = useLastCallback(() => { onMessageClick(message.id, message.chatId); - }, [onMessageClick, message.id, message.chatId]); + }); if (!linkData) { return undefined; diff --git a/src/components/common/hooks/useAnimatedEmoji.ts b/src/components/common/hooks/useAnimatedEmoji.ts index 96327dfe8..0484d2aa6 100644 --- a/src/components/common/hooks/useAnimatedEmoji.ts +++ b/src/components/common/hooks/useAnimatedEmoji.ts @@ -1,6 +1,4 @@ -import { - useCallback, useEffect, useRef, -} from '../../../lib/teact/teact'; +import { useEffect, useRef } from '../../../lib/teact/teact'; import { getActions } from '../../../global'; import type { ActiveEmojiInteraction } from '../../../global/types'; @@ -9,6 +7,7 @@ import safePlay from '../../../util/safePlay'; import buildStyle from '../../../util/buildStyle'; import { REM } from '../helpers/mediaDimensions'; +import useLastCallback from '../../../hooks/useLastCallback'; import useMedia from '../../../hooks/useMedia'; const SIZE = 7 * REM; @@ -42,7 +41,7 @@ export default function useAnimatedEmoji( const interactions = useRef(undefined); const startedInteractions = useRef(undefined); - const sendInteractionBunch = useCallback(() => { + const sendInteractionBunch = useLastCallback(() => { const container = ref.current; if (!container) return; @@ -55,9 +54,9 @@ export default function useAnimatedEmoji( }); startedInteractions.current = undefined; interactions.current = undefined; - }, [sendEmojiInteraction, chatId, messageId, emoji]); + }); - const play = useCallback(() => { + const play = useLastCallback(() => { const audio = audioRef.current; if (soundMediaData) { if (audio) { @@ -71,9 +70,9 @@ export default function useAnimatedEmoji( audioRef.current = undefined; }, { once: true }); } - }, [soundMediaData]); + }); - const handleClick = useCallback(() => { + const handleClick = useLastCallback(() => { play(); const container = ref.current; @@ -101,7 +100,7 @@ export default function useAnimatedEmoji( interactions.current.push(startedInteractions.current ? (performance.now() - startedInteractions.current) / MS_DIVIDER : TIME_DEFAULT); - }, [chatId, emoji, interactWithAnimatedEmoji, isOwn, messageId, play, sendInteractionBunch, size]); + }); // Set an end anchor for remote activated interaction useEffect(() => { diff --git a/src/components/common/hooks/useCustomEmoji.ts b/src/components/common/hooks/useCustomEmoji.ts index e05ae23b3..495956ff3 100644 --- a/src/components/common/hooks/useCustomEmoji.ts +++ b/src/components/common/hooks/useCustomEmoji.ts @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useState } from '../../../lib/teact/teact'; +import { useEffect, useState } from '../../../lib/teact/teact'; import { getGlobal } from '../../../global'; import type { GlobalState } from '../../../global/types'; @@ -7,6 +7,7 @@ import type { ApiSticker } from '../../../api/types'; import { selectCanPlayAnimatedEmojis } from '../../../global/selectors'; import { addCustomEmojiCallback, removeCustomEmojiCallback } from '../../../util/customEmojiManager'; +import useLastCallback from '../../../hooks/useLastCallback'; import useEnsureCustomEmoji from '../../../hooks/useEnsureCustomEmoji'; export default function useCustomEmoji(documentId?: string) { @@ -18,13 +19,13 @@ export default function useCustomEmoji(documentId?: string) { useEnsureCustomEmoji(documentId); - const handleGlobalChange = useCallback((customEmojis?: GlobalState['customEmojis']) => { + const handleGlobalChange = useLastCallback((customEmojis?: GlobalState['customEmojis']) => { if (!documentId) return; const newGlobal = getGlobal(); setCustomEmoji((customEmojis ?? newGlobal.customEmojis).byId[documentId]); setCanPlay(selectCanPlayAnimatedEmojis(newGlobal)); - }, [documentId]); + }); useEffect(handleGlobalChange, [documentId, handleGlobalChange]); diff --git a/src/components/common/hooks/useStickerPickerObservers.ts b/src/components/common/hooks/useStickerPickerObservers.ts index 44b86e963..d68889ac4 100644 --- a/src/components/common/hooks/useStickerPickerObservers.ts +++ b/src/components/common/hooks/useStickerPickerObservers.ts @@ -1,12 +1,14 @@ import type { RefObject } from 'react'; +import { useRef, useState } from '../../../lib/teact/teact'; -import { useIntersectionObserver } from '../../../hooks/useIntersectionObserver'; -import useSyncEffect from '../../../hooks/useSyncEffect'; -import { useCallback, useRef, useState } from '../../../lib/teact/teact'; import { ANIMATION_END_DELAY } from '../../../config'; import animateScroll from '../../../util/animateScroll'; import { REM } from '../helpers/mediaDimensions'; +import useLastCallback from '../../../hooks/useLastCallback'; +import { useIntersectionObserver } from '../../../hooks/useIntersectionObserver'; +import useSyncEffect from '../../../hooks/useSyncEffect'; + const STICKER_INTERSECTION_THROTTLE = 200; const STICKER_INTERSECTION_MARGIN = 100; const SLIDE_TRANSITION_DURATION = 350 + ANIMATION_END_DELAY; @@ -85,7 +87,7 @@ export function useStickerPickerObservers( } }, [freezeForSet, freezeForShowingItems, isHidden, unfreezeForSet, unfreezeForShowingItems]); - const selectStickerSet = useCallback((index: number) => { + const selectStickerSet = useLastCallback((index: number) => { setActiveSetIndex((currentIndex) => { const stickerSetEl = document.getElementById(`${idPrefix}-${index}`)!; const isClose = Math.abs(currentIndex - index) === 1; @@ -100,7 +102,7 @@ export function useStickerPickerObservers( return index; }); - }, [containerRef, idPrefix]); + }); return { activeSetIndex, diff --git a/src/components/common/spoiler/Spoiler.tsx b/src/components/common/spoiler/Spoiler.tsx index 9be13a3de..994f59c47 100644 --- a/src/components/common/spoiler/Spoiler.tsx +++ b/src/components/common/spoiler/Spoiler.tsx @@ -1,11 +1,11 @@ -import React, { - memo, useCallback, useEffect, useRef, -} from '../../../lib/teact/teact'; +import React, { memo, useEffect, useRef } from '../../../lib/teact/teact'; import type { FC } from '../../../lib/teact/teact'; import { ApiMessageEntityTypes } from '../../../api/types'; import { createClassNameBuilder } from '../../../util/buildClassName'; + +import useLastCallback from '../../../hooks/useLastCallback'; import useFlag from '../../../hooks/useFlag'; import './Spoiler.scss'; @@ -36,7 +36,7 @@ const Spoiler: FC = ({ const [isRevealed, reveal, conceal] = useFlag(); - const getContentLength = useCallback(() => { + const getContentLength = useLastCallback(() => { if (!contentRef.current) { return 0; } @@ -45,9 +45,9 @@ const Spoiler: FC = ({ const emojiCount = contentRef.current.querySelectorAll('.emoji').length; // Optimization: ignore alt, assume that viewing emoji takes same time as viewing 4 characters return textLength + emojiCount * 4; - }, []); + }); - const handleClick = useCallback((e: React.MouseEvent) => { + const handleClick = useLastCallback((e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); @@ -62,7 +62,7 @@ const Spoiler: FC = ({ actionsByMessageId.get(messageId!)?.forEach((actions) => actions.conceal()); conceal(); }, timeoutMs); - }, [conceal, messageId]); + }); useEffect(() => { if (!messageId) { diff --git a/src/components/left/ArchivedChats.tsx b/src/components/left/ArchivedChats.tsx index f7004bb17..440457896 100644 --- a/src/components/left/ArchivedChats.tsx +++ b/src/components/left/ArchivedChats.tsx @@ -1,4 +1,4 @@ -import React, { memo, useCallback } from '../../lib/teact/teact'; +import React, { memo } from '../../lib/teact/teact'; import { getActions } from '../../global'; import type { FC } from '../../lib/teact/teact'; @@ -7,6 +7,8 @@ import type { FolderEditDispatch } from '../../hooks/reducers/useFoldersReducer' import type { GlobalState } from '../../global/types'; import buildClassName from '../../util/buildClassName'; + +import useLastCallback from '../../hooks/useLastCallback'; import useLang from '../../hooks/useLang'; import useHistoryBack from '../../hooks/useHistoryBack'; import useLeftHeaderButtonRtlForumTransition from './main/hooks/useLeftHeaderButtonRtlForumTransition'; @@ -50,9 +52,9 @@ const ArchivedChats: FC = ({ onBack: onReset, }); - const handleDisplayArchiveInChats = useCallback(() => { + const handleDisplayArchiveInChats = useLastCallback(() => { updateArchiveSettings({ isHidden: false }); - }, [updateArchiveSettings]); + }); const { shouldDisableDropdownMenuTransitionRef, diff --git a/src/components/left/LeftColumn.tsx b/src/components/left/LeftColumn.tsx index 5f355be1c..af76f3e56 100644 --- a/src/components/left/LeftColumn.tsx +++ b/src/components/left/LeftColumn.tsx @@ -1,6 +1,6 @@ import type { RefObject } from 'react'; import React, { - memo, useCallback, useEffect, useState, + memo, useEffect, useState, } from '../../lib/teact/teact'; import { getActions, withGlobal } from '../../global'; @@ -12,6 +12,8 @@ import type { FoldersActions } from '../../hooks/reducers/useFoldersReducer'; import { IS_MAC_OS, IS_APP, LAYERS_ANIMATION_NAME } from '../../util/windowEnvironment'; import captureEscKeyListener from '../../util/captureEscKeyListener'; import { selectCurrentChat, selectIsForumPanelOpen, selectTabState } from '../../global/selectors'; + +import useLastCallback from '../../hooks/useLastCallback'; import useFoldersReducer from '../../hooks/reducers/useFoldersReducer'; import { useHotkeys } from '../../hooks/useHotkeys'; import useSyncEffect from '../../hooks/useSyncEffect'; @@ -114,7 +116,7 @@ function LeftColumn({ break; } - const handleReset = useCallback((forceReturnToChatList?: true | Event) => { + const handleReset = useLastCallback((forceReturnToChatList?: true | Event) => { function fullReset() { setContent(LeftColumnContent.ChatList); setSettingsScreen(SettingsScreens.Main); @@ -323,12 +325,9 @@ function LeftColumn({ } fullReset(); - }, [ - content, isFirstChatFolderActive, setGlobalSearchClosing, resetChatCreation, setGlobalSearchQuery, - setGlobalSearchDate, setGlobalSearchChatId, settingsScreen, hasPasscode, - ]); + }); - const handleSearchQuery = useCallback((query: string) => { + const handleSearchQuery = useLastCallback((query: string) => { if (content === LeftColumnContent.Contacts) { setContactsFilter(query); return; @@ -339,13 +338,13 @@ function LeftColumn({ if (query !== searchQuery) { setGlobalSearchQuery({ query }); } - }, [content, searchQuery, setGlobalSearchQuery]); + }); - const handleTopicSearch = useCallback(() => { + const handleTopicSearch = useLastCallback(() => { setContent(LeftColumnContent.GlobalSearch); setGlobalSearchQuery({ query: '' }); setGlobalSearchChatId({ id: forumPanelChatId }); - }, [forumPanelChatId, setGlobalSearchChatId, setGlobalSearchQuery]); + }); useEffect( () => (content !== LeftColumnContent.ChatList || (isFirstChatFolderActive && !isChatOpen && !isForumPanelOpen) @@ -354,29 +353,29 @@ function LeftColumn({ [isFirstChatFolderActive, content, handleReset, isChatOpen, isForumPanelOpen], ); - const handleHotkeySearch = useCallback((e: KeyboardEvent) => { + const handleHotkeySearch = useLastCallback((e: KeyboardEvent) => { if (content === LeftColumnContent.GlobalSearch) { return; } e.preventDefault(); setContent(LeftColumnContent.GlobalSearch); - }, [content]); + }); - const handleHotkeySavedMessages = useCallback((e: KeyboardEvent) => { + const handleHotkeySavedMessages = useLastCallback((e: KeyboardEvent) => { e.preventDefault(); openChat({ id: currentUserId, shouldReplaceHistory: true }); - }, [currentUserId, openChat]); + }); - const handleArchivedChats = useCallback((e: KeyboardEvent) => { + const handleArchivedChats = useLastCallback((e: KeyboardEvent) => { e.preventDefault(); setContent(LeftColumnContent.Archived); - }, []); + }); - const handleHotkeySettings = useCallback((e: KeyboardEvent) => { + const handleHotkeySettings = useLastCallback((e: KeyboardEvent) => { e.preventDefault(); setContent(LeftColumnContent.Settings); - }, []); + }); useHotkeys({ 'Mod+Shift+F': handleHotkeySearch, @@ -408,10 +407,10 @@ function LeftColumn({ } }, [foldersDispatch, nextFoldersAction, nextSettingsScreen, requestNextSettingsScreen]); - const handleSettingsScreenSelect = useCallback((screen: SettingsScreens) => { + const handleSettingsScreenSelect = useLastCallback((screen: SettingsScreens) => { setContent(LeftColumnContent.Settings); setSettingsScreen(screen); - }, []); + }); function renderContent(isActive: boolean) { switch (contentType) { diff --git a/src/components/left/main/Chat.tsx b/src/components/left/main/Chat.tsx index e3196d471..6b5a98fbc 100644 --- a/src/components/left/main/Chat.tsx +++ b/src/components/left/main/Chat.tsx @@ -1,4 +1,4 @@ -import React, { memo, useCallback, useEffect } from '../../../lib/teact/teact'; +import React, { memo, useEffect } from '../../../lib/teact/teact'; import { getActions, withGlobal } from '../../../global'; import type { FC } from '../../../lib/teact/teact'; @@ -41,6 +41,7 @@ import { import buildClassName from '../../../util/buildClassName'; import { createLocationHash } from '../../../util/routing'; +import useLastCallback from '../../../hooks/useLastCallback'; import useChatContextActions from '../../../hooks/useChatContextActions'; import useFlag from '../../../hooks/useFlag'; import useChatListEntry from './hooks/useChatListEntry'; @@ -158,7 +159,7 @@ const Chat: FC = ({ orderDiff, }); - const handleClick = useCallback(() => { + const handleClick = useLastCallback(() => { if (isForum) { if (isSelectedForum) { closeForumPanel(undefined, { forceOnHeavyAnimation: true }); @@ -174,32 +175,32 @@ const Chat: FC = ({ if (isSelected && canScrollDown) { focusLastMessage(); } - }, [isForum, chatId, isSelected, canScrollDown, isSelectedForum]); + }); - const handleDragEnter = useCallback((e) => { + const handleDragEnter = useLastCallback((e) => { e.preventDefault(); onDragEnter?.(chatId); - }, [chatId, onDragEnter]); + }); - const handleDelete = useCallback(() => { + const handleDelete = useLastCallback(() => { markRenderDeleteModal(); openDeleteModal(); - }, [markRenderDeleteModal, openDeleteModal]); + }); - const handleMute = useCallback(() => { + const handleMute = useLastCallback(() => { markRenderMuteModal(); openMuteModal(); - }, [markRenderMuteModal, openMuteModal]); + }); - const handleChatFolderChange = useCallback(() => { + const handleChatFolderChange = useLastCallback(() => { markRenderChatFolderModal(); openChatFolderModal(); - }, [markRenderChatFolderModal, openChatFolderModal]); + }); - const handleReport = useCallback(() => { + const handleReport = useLastCallback(() => { markRenderReportModal(); openReportModal(); - }, [markRenderReportModal, openReportModal]); + }); const contextActions = useChatContextActions({ chat, diff --git a/src/components/left/main/ChatFolders.tsx b/src/components/left/main/ChatFolders.tsx index be524ef68..850f3b55e 100644 --- a/src/components/left/main/ChatFolders.tsx +++ b/src/components/left/main/ChatFolders.tsx @@ -1,6 +1,6 @@ import type { FC } from '../../../lib/teact/teact'; import React, { - memo, useCallback, useEffect, useMemo, useRef, + memo, useEffect, useMemo, useRef, } from '../../../lib/teact/teact'; import { getActions, getGlobal, withGlobal } from '../../../global'; @@ -18,6 +18,8 @@ import buildClassName from '../../../util/buildClassName'; import captureEscKeyListener from '../../../util/captureEscKeyListener'; import { selectCurrentLimit } from '../../../global/selectors/limits'; import { selectCanShareFolder, selectTabState } from '../../../global/selectors'; + +import useLastCallback from '../../../hooks/useLastCallback'; import useShowTransition from '../../../hooks/useShowTransition'; import useLang from '../../../hooks/useLang'; import useHistoryBack from '../../../hooks/useHistoryBack'; @@ -180,9 +182,9 @@ const ChatFolders: FC = ({ }); }, [displayedFolders, folderCountersById, lang, maxFolders, folderInvitesById, maxFolderInvites]); - const handleSwitchTab = useCallback((index: number) => { + const handleSwitchTab = useLastCallback((index: number) => { setActiveChatFolder({ activeChatFolder: index }, { forceOnHeavyAnimation: true }); - }, [setActiveChatFolder]); + }); // Prevent `activeTab` pointing at non-existing folder after update useEffect(() => { diff --git a/src/components/left/main/ChatList.tsx b/src/components/left/main/ChatList.tsx index 891741b78..bf8e3c954 100644 --- a/src/components/left/main/ChatList.tsx +++ b/src/components/left/main/ChatList.tsx @@ -1,5 +1,5 @@ import React, { - memo, useEffect, useRef, useCallback, + memo, useEffect, useRef, } from '../../../lib/teact/teact'; import { getActions } from '../../../global'; @@ -20,6 +20,7 @@ import { IS_MAC_OS, IS_APP } from '../../../util/windowEnvironment'; import { getPinnedChatsCount, getOrderKey } from '../../../util/folderManager'; import buildClassName from '../../../util/buildClassName'; +import useLastCallback from '../../../hooks/useLastCallback'; import useInfiniteScroll from '../../../hooks/useInfiniteScroll'; import { useFolderManagerForOrderedIds } from '../../../hooks/useFolderManager'; import { useIntersectionObserver } from '../../../hooks/useIntersectionObserver'; @@ -133,18 +134,18 @@ const ChatList: FC = ({ throttleMs: INTERSECTION_THROTTLE, }); - const handleArchivedClick = useCallback(() => { + const handleArchivedClick = useLastCallback(() => { onLeftColumnContentChange(LeftColumnContent.Archived); closeForumPanel(); - }, [closeForumPanel, onLeftColumnContentChange]); + }); - const handleArchivedDragEnter = useCallback(() => { + const handleArchivedDragEnter = useLastCallback(() => { if (shouldIgnoreDragRef.current) { shouldIgnoreDragRef.current = false; return; } handleArchivedClick(); - }, [handleArchivedClick]); + }); const handleDragEnter = useDebouncedCallback((chatId: string) => { if (shouldIgnoreDragRef.current) { @@ -154,13 +155,13 @@ const ChatList: FC = ({ openChat({ id: chatId, shouldReplaceHistory: true }); }, [openChat], DRAG_ENTER_DEBOUNCE, true); - const handleDragLeave = useCallback((e: React.DragEvent) => { + const handleDragLeave = useLastCallback((e: React.DragEvent) => { const rect = e.currentTarget.getBoundingClientRect(); const x = e.clientX - rect.left; const y = e.clientY - rect.top; if (x < rect.width || y < rect.y) return; shouldIgnoreDragRef.current = true; - }, []); + }); function renderChats() { const viewportOffset = orderedIds!.indexOf(viewportIds![0]); diff --git a/src/components/left/main/ForumPanel.tsx b/src/components/left/main/ForumPanel.tsx index ba3ec6586..443e5311c 100644 --- a/src/components/left/main/ForumPanel.tsx +++ b/src/components/left/main/ForumPanel.tsx @@ -1,5 +1,5 @@ import React, { - memo, useCallback, useEffect, useMemo, useRef, useState, + memo, useEffect, useMemo, useRef, useState, } from '../../../lib/teact/teact'; import { getActions, withGlobal } from '../../../global'; import { requestNextMutation } from '../../../lib/fasterdom/fasterdom'; @@ -22,6 +22,7 @@ import { waitForTransitionEnd } from '../../../util/cssAnimationEndListeners'; import { captureEvents, SwipeDirection } from '../../../util/captureEvents'; import { createLocationHash } from '../../../util/routing'; +import useLastCallback from '../../../hooks/useLastCallback'; import useInfiniteScroll from '../../../hooks/useInfiniteScroll'; import { useIntersectionObserver, useOnIntersect } from '../../../hooks/useIntersectionObserver'; import useOrderDiff from './hooks/useOrderDiff'; @@ -92,9 +93,9 @@ const ForumPanel: FC = ({ const [isScrolled, setIsScrolled] = useState(false); const lang = useLang(); - const handleClose = useCallback(() => { + const handleClose = useLastCallback(() => { closeForumPanel(); - }, [closeForumPanel]); + }); useEffect(() => { if (!withInterfaceAnimations && !isOpen) { @@ -102,10 +103,10 @@ const ForumPanel: FC = ({ } }, [withInterfaceAnimations, isOpen, onCloseAnimationEnd]); - const handleToggleChatInfo = useCallback(() => { + const handleToggleChatInfo = useLastCallback(() => { if (!chat) return; openChatWithInfo({ id: chat.id, shouldReplaceHistory: true }); - }, [chat, openChatWithInfo]); + }); const { observe } = useIntersectionObserver({ rootRef: containerRef, diff --git a/src/components/left/main/LeftMain.tsx b/src/components/left/main/LeftMain.tsx index 6a4da081e..7a68d5851 100644 --- a/src/components/left/main/LeftMain.tsx +++ b/src/components/left/main/LeftMain.tsx @@ -1,6 +1,6 @@ import type { FC } from '../../../lib/teact/teact'; import React, { - memo, useCallback, useEffect, useRef, useState, + memo, useEffect, useRef, useState, } from '../../../lib/teact/teact'; import { getActions } from '../../../global'; @@ -11,6 +11,8 @@ import type { FolderEditDispatch } from '../../../hooks/reducers/useFoldersReduc import { IS_ELECTRON } from '../../../config'; import { IS_TOUCH_ENV } from '../../../util/windowEnvironment'; import buildClassName from '../../../util/buildClassName'; + +import useLastCallback from '../../../hooks/useLastCallback'; import useShowTransition from '../../../hooks/useShowTransition'; import useLang from '../../../hooks/useLang'; import useForumPanelRender from '../../../hooks/useForumPanelRender'; @@ -81,15 +83,15 @@ const LeftMain: FC = ({ const isMouseInside = useRef(false); - const handleMouseEnter = useCallback(() => { + const handleMouseEnter = useLastCallback(() => { if (content !== LeftColumnContent.ChatList) { return; } isMouseInside.current = true; setIsNewChatButtonShown(true); - }, [content]); + }); - const handleMouseLeave = useCallback(() => { + const handleMouseLeave = useLastCallback(() => { isMouseInside.current = false; if (closeTimeout) { @@ -102,36 +104,36 @@ const LeftMain: FC = ({ setIsNewChatButtonShown(false); } }, BUTTON_CLOSE_DELAY_MS); - }, []); + }); - const handleSelectSettings = useCallback(() => { + const handleSelectSettings = useLastCallback(() => { onContentChange(LeftColumnContent.Settings); - }, [onContentChange]); + }); - const handleSelectContacts = useCallback(() => { + const handleSelectContacts = useLastCallback(() => { onContentChange(LeftColumnContent.Contacts); - }, [onContentChange]); + }); - const handleSelectArchived = useCallback(() => { + const handleSelectArchived = useLastCallback(() => { onContentChange(LeftColumnContent.Archived); closeForumPanel(); - }, [closeForumPanel, onContentChange]); + }); - const handleUpdateClick = useCallback(() => { + const handleUpdateClick = useLastCallback(() => { if (IS_ELECTRON) { window.electron?.installUpdate(); } else { window.location.reload(); } - }, []); + }); - const handleSelectNewChannel = useCallback(() => { + const handleSelectNewChannel = useLastCallback(() => { onContentChange(LeftColumnContent.NewChannelStep1); - }, [onContentChange]); + }); - const handleSelectNewGroup = useCallback(() => { + const handleSelectNewGroup = useLastCallback(() => { onContentChange(LeftColumnContent.NewGroupStep1); - }, [onContentChange]); + }); useEffect(() => { let autoCloseTimeout: number | undefined; diff --git a/src/components/left/main/LeftMainHeader.tsx b/src/components/left/main/LeftMainHeader.tsx index 42c710f0d..d11fb8d7f 100644 --- a/src/components/left/main/LeftMainHeader.tsx +++ b/src/components/left/main/LeftMainHeader.tsx @@ -1,6 +1,6 @@ import type { FC } from '../../../lib/teact/teact'; import React, { - memo, useCallback, useEffect, useMemo, useRef, + memo, useEffect, useMemo, useRef, } from '../../../lib/teact/teact'; import { getActions, withGlobal } from '../../../global'; @@ -40,6 +40,8 @@ import useConnectionStatus from '../../../hooks/useConnectionStatus'; import { useHotkeys } from '../../../hooks/useHotkeys'; import { getPromptInstall } from '../../../util/installPrompt'; import captureEscKeyListener from '../../../util/captureEscKeyListener'; + +import useLastCallback from '../../../hooks/useLastCallback'; import useLeftHeaderButtonRtlForumTransition from './hooks/useLeftHeaderButtonRtlForumTransition'; import { useFullscreenStatus } from '../../../hooks/useFullscreen'; import useElectronDrag from '../../../hooks/useElectronDrag'; @@ -152,7 +154,7 @@ const LeftMainHeader: FC = ({ lang, connectionState, isSyncing, isMessageListOpen, isConnectionStatusMinimized, !areChatsLoaded, ); - const handleLockScreenHotkey = useCallback((e: KeyboardEvent) => { + const handleLockScreenHotkey = useLastCallback((e: KeyboardEvent) => { e.preventDefault(); e.stopPropagation(); if (hasPasscode) { @@ -160,7 +162,7 @@ const LeftMainHeader: FC = ({ } else { requestNextSettingsScreen({ screen: SettingsScreens.PasscodeDisabled }); } - }, [hasPasscode]); + }); useHotkeys(canSetPasscode ? { 'Ctrl+Shift+L': handleLockScreenHotkey, @@ -193,29 +195,29 @@ const LeftMainHeader: FC = ({ ); }, [hasMenu, isMobile, lang, onReset, shouldSkipTransition]); - const handleSearchFocus = useCallback(() => { + const handleSearchFocus = useLastCallback(() => { if (!searchQuery) { onSearchQuery(''); } - }, [searchQuery, onSearchQuery]); + }); - const toggleConnectionStatus = useCallback(() => { + const toggleConnectionStatus = useLastCallback(() => { setSettingOption({ isConnectionStatusMinimized: !isConnectionStatusMinimized }); - }, [isConnectionStatusMinimized, setSettingOption]); + }); - const handleSelectSaved = useCallback(() => { + const handleSelectSaved = useLastCallback(() => { openChat({ id: currentUserId, shouldReplaceHistory: true }); - }, [currentUserId, openChat]); + }); - const handleDarkModeToggle = useCallback((e: React.SyntheticEvent) => { + const handleDarkModeToggle = useLastCallback((e: React.SyntheticEvent) => { e.stopPropagation(); const newTheme = theme === 'light' ? 'dark' : 'light'; setSettingOption({ theme: newTheme }); setSettingOption({ shouldUseSystemTheme: false }); - }, [setSettingOption, theme]); + }); - const handleAnimationLevelChange = useCallback((e: React.SyntheticEvent) => { + const handleAnimationLevelChange = useLastCallback((e: React.SyntheticEvent) => { e.stopPropagation(); let newLevel = animationLevel + 1; @@ -228,29 +230,29 @@ const LeftMainHeader: FC = ({ setSettingOption({ animationLevel: newLevel as AnimationLevel }); updatePerformanceSettings(performanceSettings); - }, [animationLevel, setSettingOption]); + }); - const handleChangelogClick = useCallback(() => { + const handleChangelogClick = useLastCallback(() => { window.open(BETA_CHANGELOG_URL, '_blank', 'noopener'); - }, []); + }); - const handleSwitchToWebK = useCallback(() => { + const handleSwitchToWebK = useLastCallback(() => { setPermanentWebVersion('K'); clearWebsync(); skipLockOnUnload(); - }, [skipLockOnUnload]); + }); - const handleOpenTipsChat = useCallback(() => { + const handleOpenTipsChat = useLastCallback(() => { openChatByUsername({ username: lang('Settings.TipsUsername') }); - }, [lang, openChatByUsername]); + }); - const handleBugReportClick = useCallback(() => { + const handleBugReportClick = useLastCallback(() => { openUrl({ url: FEEDBACK_URL }); - }, [openUrl]); + }); - const handleLockScreen = useCallback(() => { + const handleLockScreen = useLastCallback(() => { lockScreen(); - }, [lockScreen]); + }); const isSearchFocused = ( Boolean(globalSearchChatId) diff --git a/src/components/left/main/Topic.tsx b/src/components/left/main/Topic.tsx index 21e4078cb..41c2f089a 100644 --- a/src/components/left/main/Topic.tsx +++ b/src/components/left/main/Topic.tsx @@ -1,4 +1,4 @@ -import React, { memo, useCallback } from '../../../lib/teact/teact'; +import React, { memo } from '../../../lib/teact/teact'; import { getActions, withGlobal } from '../../../global'; import type { FC } from '../../../lib/teact/teact'; @@ -28,6 +28,7 @@ import { createLocationHash } from '../../../util/routing'; import renderText from '../../common/helpers/renderText'; import { getMessageAction } from '../../../global/helpers'; +import useLastCallback from '../../../hooks/useLastCallback'; import useChatListEntry from './hooks/useChatListEntry'; import useTopicContextActions from './hooks/useTopicContextActions'; import useFlag from '../../../hooks/useFlag'; @@ -105,19 +106,19 @@ const Topic: FC = ({ } = topic; const isMuted = topic.isMuted || (topic.isMuted === undefined && chat.isMuted); - const handleOpenDeleteModal = useCallback(() => { + const handleOpenDeleteModal = useLastCallback(() => { markRenderDeleteModal(); openDeleteModal(); - }, [markRenderDeleteModal, openDeleteModal]); + }); - const handleDelete = useCallback(() => { + const handleDelete = useLastCallback(() => { deleteTopic({ chatId: chat.id, topicId: topic.id }); - }, [chat.id, deleteTopic, topic.id]); + }); - const handleMute = useCallback(() => { + const handleMute = useLastCallback(() => { markRenderMuteModal(); openMuteModal(); - }, [markRenderMuteModal, openMuteModal]); + }); const { renderSubtitle, ref } = useChatListEntry({ chat, @@ -138,13 +139,13 @@ const Topic: FC = ({ orderDiff, }); - const handleOpenTopic = useCallback(() => { + const handleOpenTopic = useLastCallback(() => { openChat({ id: chatId, threadId: topic.id, shouldReplaceHistory: true }); if (canScrollDown) { focusLastMessage(); } - }, [openChat, chatId, topic.id, canScrollDown, focusLastMessage]); + }); const contextActions = useTopicContextActions({ topic, diff --git a/src/components/left/search/ChatMessage.tsx b/src/components/left/search/ChatMessage.tsx index e753855b8..d8ab39c8d 100644 --- a/src/components/left/search/ChatMessage.tsx +++ b/src/components/left/search/ChatMessage.tsx @@ -1,5 +1,5 @@ import type { FC } from '../../../lib/teact/teact'; -import React, { memo, useCallback } from '../../../lib/teact/teact'; +import React, { memo } from '../../../lib/teact/teact'; import { getActions, withGlobal } from '../../../global'; import type { @@ -21,6 +21,7 @@ import buildClassName from '../../../util/buildClassName'; import { formatPastTimeShort } from '../../../util/dateFormat'; import { renderMessageSummary } from '../../common/helpers/renderMessageText'; +import useLastCallback from '../../../hooks/useLastCallback'; import useMedia from '../../../hooks/useMedia'; import useLang from '../../../hooks/useLang'; import useSelectWithEnter from '../../../hooks/useSelectWithEnter'; @@ -61,9 +62,9 @@ const ChatMessage: FC = ({ const mediaBlobUrl = useMedia(getMessageMediaHash(message, 'micro')); const isRoundVideo = Boolean(getMessageRoundVideo(message)); - const handleClick = useCallback(() => { + const handleClick = useLastCallback(() => { focusMessage({ chatId, messageId: message.id, shouldReplaceHistory: true }); - }, [chatId, focusMessage, message.id]); + }); const lang = useLang(); diff --git a/src/components/left/settings/Settings.tsx b/src/components/left/settings/Settings.tsx index 888d9e982..321f76335 100644 --- a/src/components/left/settings/Settings.tsx +++ b/src/components/left/settings/Settings.tsx @@ -1,4 +1,4 @@ -import React, { memo, useCallback, useState } from '../../../lib/teact/teact'; +import React, { memo, useState } from '../../../lib/teact/teact'; import { getActions, getGlobal } from '../../../global'; import type { FC } from '../../../lib/teact/teact'; @@ -7,6 +7,8 @@ import type { FolderEditDispatch, FoldersState } from '../../../hooks/reducers/u import { LAYERS_ANIMATION_NAME } from '../../../util/windowEnvironment'; import { selectTabState } from '../../../global/selectors'; + +import useLastCallback from '../../../hooks/useLastCallback'; import useTwoFaReducer from '../../../hooks/reducers/useTwoFaReducer'; import Transition from '../../ui/Transition'; @@ -145,7 +147,7 @@ const Settings: FC = ({ const [twoFaState, twoFaDispatch] = useTwoFaReducer(); const [privacyPasscode, setPrivacyPasscode] = useState(''); - const handleReset = useCallback((forceReturnToChatList?: true | Event) => { + const handleReset = useLastCallback((forceReturnToChatList?: true | Event) => { const isFromSettings = selectTabState(getGlobal()).shareFolderScreen?.isFromSettings; if (currentScreen === SettingsScreens.FoldersShare) { @@ -181,10 +183,7 @@ const Settings: FC = ({ } onReset(); - }, [ - foldersState.mode, foldersDispatch, - currentScreen, onReset, onScreenSelect, - ]); + }); function renderCurrentSectionContent(isScreenActive: boolean, screen: SettingsScreens) { const privacyAllowScreens: Record = { diff --git a/src/components/main/DownloadManager.tsx b/src/components/main/DownloadManager.tsx index e252240a6..fb4f28439 100644 --- a/src/components/main/DownloadManager.tsx +++ b/src/components/main/DownloadManager.tsx @@ -1,5 +1,5 @@ import type { FC } from '../../lib/teact/teact'; -import { memo, useCallback, useEffect } from '../../lib/teact/teact'; +import { memo, useEffect } from '../../lib/teact/teact'; import { getActions, getGlobal, withGlobal } from '../../global'; import type { GlobalState, TabState } from '../../global/types'; @@ -15,6 +15,7 @@ import { } from '../../global/helpers'; import { compact } from '../../util/iteratees'; +import useLastCallback from '../../hooks/useLastCallback'; import useRunDebounced from '../../hooks/useRunDebounced'; type StateProps = { @@ -34,7 +35,7 @@ const DownloadManager: FC = ({ const runDebounced = useRunDebounced(GLOBAL_UPDATE_DEBOUNCE, true); - const handleMessageDownloaded = useCallback((message: ApiMessage) => { + const handleMessageDownloaded = useLastCallback((message: ApiMessage) => { downloadedMessages.add(message); runDebounced(() => { if (downloadedMessages.size) { @@ -42,7 +43,7 @@ const DownloadManager: FC = ({ downloadedMessages.clear(); } }); - }, [cancelMessagesMediaDownload, runDebounced]); + }); useEffect(() => { // No need for expensive global updates on messages, so we avoid them diff --git a/src/components/main/Main.tsx b/src/components/main/Main.tsx index fe70f126f..eccbdc616 100644 --- a/src/components/main/Main.tsx +++ b/src/components/main/Main.tsx @@ -1,6 +1,6 @@ import type { FC } from '../../lib/teact/teact'; import React, { - useEffect, memo, useCallback, useState, useRef, useLayoutEffect, + useEffect, memo, useState, useRef, useLayoutEffect, } from '../../lib/teact/teact'; import { addExtraClass } from '../../lib/teact/teact-dom'; import { requestNextMutation } from '../../lib/fasterdom/fasterdom'; @@ -44,6 +44,7 @@ import { parseInitialLocationHash, parseLocationHash } from '../../util/routing' import { Bundles, loadBundle } from '../../util/moduleLoader'; import updateIcon from '../../util/updateIcon'; +import useLastCallback from '../../hooks/useLastCallback'; import useEffectWithPrevDeps from '../../hooks/useEffectWithPrevDeps'; import useBackgroundMode from '../../hooks/useBackgroundMode'; import useBeforeUnload from '../../hooks/useBeforeUnload'; @@ -487,11 +488,11 @@ const Main: FC = ({ isFullscreen && 'is-fullscreen', ); - const handleBlur = useCallback(() => { + const handleBlur = useLastCallback(() => { onTabFocusChange({ isBlurred: true }); - }, [onTabFocusChange]); + }); - const handleFocus = useCallback(() => { + const handleFocus = useLastCallback(() => { onTabFocusChange({ isBlurred: false }); if (!document.title.includes(INACTIVE_MARKER)) { @@ -499,15 +500,15 @@ const Main: FC = ({ } updateIcon(false); - }, [onTabFocusChange, updatePageTitle]); + }); - const handleStickerSetModalClose = useCallback(() => { + const handleStickerSetModalClose = useLastCallback(() => { closeStickerSetModal(); - }, [closeStickerSetModal]); + }); - const handleCustomEmojiSetsModalClose = useCallback(() => { + const handleCustomEmojiSetsModalClose = useLastCallback(() => { closeCustomEmojiSets(); - }, [closeCustomEmojiSets]); + }); // Online status and browser tab indicators useBackgroundMode(handleBlur, handleFocus); diff --git a/src/components/mediaViewer/MediaViewer.tsx b/src/components/mediaViewer/MediaViewer.tsx index 49434a1f5..0cecd8a87 100644 --- a/src/components/mediaViewer/MediaViewer.tsx +++ b/src/components/mediaViewer/MediaViewer.tsx @@ -1,6 +1,6 @@ import type { FC } from '../../lib/teact/teact'; import React, { - memo, useCallback, useEffect, useMemo, useRef, + memo, useEffect, useMemo, useRef, } from '../../lib/teact/teact'; import type { @@ -54,6 +54,7 @@ import MediaViewerSlides from './MediaViewerSlides'; import SenderInfo from './SenderInfo'; import './MediaViewer.scss'; +import useLastCallback from '../../hooks/useLastCallback'; type StateProps = { chatId?: string; @@ -230,10 +231,10 @@ const MediaViewer: FC = ({ bestImageData, prevBestImageData, dimensions, isVideo, hasFooter, ]); - const handleClose = useCallback(() => closeMediaViewer(), [closeMediaViewer]); + const handleClose = useLastCallback(() => closeMediaViewer()); const mediaIdRef = useStateRef(mediaId); - const handleFooterClick = useCallback(() => { + const handleFooterClick = useLastCallback(() => { handleClose(); const currentMediaId = mediaIdRef.current; @@ -248,16 +249,16 @@ const MediaViewer: FC = ({ } else { focusMessage({ chatId, threadId, messageId: currentMediaId }); } - }, [handleClose, mediaIdRef, chatId, isMobile, threadId]); + }); - const handleForward = useCallback(() => { + const handleForward = useLastCallback(() => { openForwardMenu({ fromChatId: chatId!, messageIds: [mediaId!], }); - }, [openForwardMenu, chatId, mediaId]); + }); - const selectMedia = useCallback((id?: number) => { + const selectMedia = useLastCallback((id?: number) => { openMediaViewer({ chatId, threadId, @@ -267,7 +268,7 @@ const MediaViewer: FC = ({ }, { forceOnHeavyAnimation: true, }); - }, [avatarOwner?.id, chatId, openMediaViewer, origin, threadId]); + }); useEffect(() => (isOpen ? captureEscKeyListener(() => { handleClose(); @@ -281,7 +282,7 @@ const MediaViewer: FC = ({ const mediaIdsRef = useStateRef(mediaIds); - const getMediaId = useCallback((fromId?: number, direction?: number): number | undefined => { + const getMediaId = useLastCallback((fromId?: number, direction?: number): number | undefined => { if (fromId === undefined) return undefined; const mIds = mediaIdsRef.current; const index = mIds.indexOf(fromId); @@ -289,9 +290,9 @@ const MediaViewer: FC = ({ return mIds[index + direction]; } return undefined; - }, [mediaIdsRef]); + }); - const handleBeforeDelete = useCallback(() => { + const handleBeforeDelete = useLastCallback(() => { if (mediaIds.length <= 1) { handleClose(); return; @@ -300,7 +301,7 @@ const MediaViewer: FC = ({ // Before deleting, select previous media or the first one index = index > 0 ? index - 1 : 0; selectMedia(mediaIds[index]); - }, [handleClose, mediaId, mediaIds, selectMedia]); + }); const lang = useLang(); diff --git a/src/components/mediaViewer/MediaViewerActions.tsx b/src/components/mediaViewer/MediaViewerActions.tsx index a27c0c490..22d3624cf 100644 --- a/src/components/mediaViewer/MediaViewerActions.tsx +++ b/src/components/mediaViewer/MediaViewerActions.tsx @@ -1,8 +1,4 @@ -import React, { - memo, - useCallback, - useMemo, -} from '../../lib/teact/teact'; +import React, { memo, useMemo } from '../../lib/teact/teact'; import { getActions, withGlobal } from '../../global'; import type { FC } from '../../lib/teact/teact'; @@ -21,6 +17,7 @@ import { } from '../../global/selectors'; import { getMessageMediaFormat, getMessageMediaHash, isUserId } from '../../global/helpers'; +import useLastCallback from '../../hooks/useLastCallback'; import useLang from '../../hooks/useLang'; import useMediaWithLoadProgress from '../../hooks/useMediaWithLoadProgress'; import useFlag from '../../hooks/useFlag'; @@ -100,27 +97,27 @@ const MediaViewerActions: FC = ({ message && getMessageMediaFormat(message, 'download'), ); - const handleDownloadClick = useCallback(() => { + const handleDownloadClick = useLastCallback(() => { if (isDownloading) { cancelMessageMediaDownload({ message: message! }); } else { downloadMessageMedia({ message: message! }); } - }, [cancelMessageMediaDownload, downloadMessageMedia, isDownloading, message]); + }); - const handleZoomOut = useCallback(() => { + const handleZoomOut = useLastCallback(() => { const zoomChange = getZoomChange(); const change = zoomChange < 0 ? zoomChange : 0; setZoomChange(change - 1); - }, [getZoomChange, setZoomChange]); + }); - const handleZoomIn = useCallback(() => { + const handleZoomIn = useLastCallback(() => { const zoomChange = getZoomChange(); const change = zoomChange > 0 ? zoomChange : 0; setZoomChange(change + 1); - }, [getZoomChange, setZoomChange]); + }); - const handleUpdate = useCallback(() => { + const handleUpdate = useLastCallback(() => { if (!avatarPhoto || !avatarOwnerId) return; if (isUserId(avatarOwnerId)) { updateProfilePhoto({ photo: avatarPhoto }); @@ -128,7 +125,7 @@ const MediaViewerActions: FC = ({ updateChatPhoto({ chatId: avatarOwnerId, photo: avatarPhoto }); } selectMedia(0); - }, [avatarPhoto, avatarOwnerId, selectMedia, updateProfilePhoto, updateChatPhoto]); + }); const lang = useLang(); diff --git a/src/components/mediaViewer/MediaViewerContent.tsx b/src/components/mediaViewer/MediaViewerContent.tsx index c291a9a80..30ef45a85 100644 --- a/src/components/mediaViewer/MediaViewerContent.tsx +++ b/src/components/mediaViewer/MediaViewerContent.tsx @@ -1,5 +1,5 @@ import type { FC } from '../../lib/teact/teact'; -import React, { memo, useCallback } from '../../lib/teact/teact'; +import React, { memo } from '../../lib/teact/teact'; import { withGlobal } from '../../global'; import type { @@ -18,6 +18,8 @@ import { renderMessageText } from '../common/helpers/renderMessageText'; import stopEvent from '../../util/stopEvent'; import buildClassName from '../../util/buildClassName'; import { useMediaProps } from './hooks/useMediaProps'; + +import useLastCallback from '../../hooks/useLastCallback'; import useAppLayout from '../../hooks/useAppLayout'; import useLang from '../../hooks/useLang'; import useControlsSignal from './hooks/useControlsSignal'; @@ -102,9 +104,9 @@ const MediaViewerContent: FC = (props) => { const isOpen = Boolean(avatarOwner || mediaId); const { isMobile } = useAppLayout(); - const toggleControlsOnMove = useCallback(() => { + const toggleControlsOnMove = useLastCallback(() => { toggleControls(true); - }, [toggleControls]); + }); if (avatarOwner || actionPhoto) { if (!isVideoAvatar) { diff --git a/src/components/mediaViewer/MediaViewerSlides.tsx b/src/components/mediaViewer/MediaViewerSlides.tsx index 743d6a7ee..e5e9f9583 100644 --- a/src/components/mediaViewer/MediaViewerSlides.tsx +++ b/src/components/mediaViewer/MediaViewerSlides.tsx @@ -1,6 +1,6 @@ import type { FC } from '../../lib/teact/teact'; import React, { - memo, useCallback, useEffect, useLayoutEffect, useRef, useState, + memo, useEffect, useLayoutEffect, useRef, useState, } from '../../lib/teact/teact'; import type { MediaViewerOrigin } from '../../types'; @@ -13,6 +13,7 @@ import { IS_IOS, IS_TOUCH_ENV } from '../../util/windowEnvironment'; import { clamp, isBetween, round } from '../../util/math'; import { debounce } from '../../util/schedulers'; +import useLastCallback from '../../hooks/useLastCallback'; import useDebouncedCallback from '../../hooks/useDebouncedCallback'; import useLang from '../../hooks/useLang'; import useTimeout from '../../hooks/useTimeout'; @@ -134,13 +135,13 @@ const MediaViewerSlides: FC = ({ const shouldCloseOnVideo = Boolean(isGif && !IS_IOS); const clickXThreshold = IS_TOUCH_ENV ? 40 : windowWidth / 10; - const handleControlsVisibility = useCallback((e: React.MouseEvent) => { + const handleControlsVisibility = useLastCallback((e: React.MouseEvent) => { if (!IS_TOUCH_ENV) return; const isFooter = windowHeight - e.pageY < CLICK_Y_THRESHOLD; if (!isFooter && e.pageX < clickXThreshold) return; if (!isFooter && e.pageX > windowWidth - clickXThreshold) return; setControlsVisible(!getControlsVisible()); - }, [clickXThreshold, getControlsVisible, setControlsVisible, windowHeight, windowWidth]); + }); useTimeout(() => setControlsVisible(true), ANIMATION_DURATION); diff --git a/src/components/mediaViewer/SeekLine.tsx b/src/components/mediaViewer/SeekLine.tsx index d2f1d03a5..8bc900301 100644 --- a/src/components/mediaViewer/SeekLine.tsx +++ b/src/components/mediaViewer/SeekLine.tsx @@ -1,10 +1,11 @@ import React, { - useRef, useState, useCallback, useEffect, memo, useMemo, useLayoutEffect, + useRef, useState, useEffect, memo, useMemo, useLayoutEffect, } from '../../lib/teact/teact'; import type { BufferedRange } from '../../hooks/useBuffering'; import type { ApiDimensions } from '../../api/types'; +import useLastCallback from '../../hooks/useLastCallback'; import useSignal from '../../hooks/useSignal'; import useCurrentTimeSignal from './hooks/currentTimeSignal'; @@ -65,11 +66,11 @@ const SeekLine: React.FC = ({ return getPreviewDimensions(posterSize?.width || 0, posterSize?.height || 0); }, [posterSize]); - const setPreview = useCallback((time: number) => { + const setPreview = useLastCallback((time: number) => { time = Math.floor(time); setPreviewTime(time); renderVideoPreview(time); - }, [setPreviewTime]); + }); useEffect(() => { if (isPreviewDisabled || !url || !isReady) return undefined; diff --git a/src/components/mediaViewer/SenderInfo.tsx b/src/components/mediaViewer/SenderInfo.tsx index 568dc9890..5463df9dd 100644 --- a/src/components/mediaViewer/SenderInfo.tsx +++ b/src/components/mediaViewer/SenderInfo.tsx @@ -1,5 +1,5 @@ import type { FC } from '../../lib/teact/teact'; -import React, { useCallback } from '../../lib/teact/teact'; +import React from '../../lib/teact/teact'; import { getActions, withGlobal } from '../../global'; import type { ApiChat, ApiMessage, ApiUser } from '../../api/types'; @@ -13,6 +13,8 @@ import { selectSender, selectUser, } from '../../global/selectors'; + +import useLastCallback from '../../hooks/useLastCallback'; import useLang from '../../hooks/useLang'; import useAppLayout from '../../hooks/useAppLayout'; @@ -50,7 +52,7 @@ const SenderInfo: FC = ({ const { isMobile } = useAppLayout(); - const handleFocusMessage = useCallback(() => { + const handleFocusMessage = useLastCallback(() => { closeMediaViewer(); if (!chatId || !messageId) return; @@ -63,7 +65,7 @@ const SenderInfo: FC = ({ } else { focusMessage({ chatId, messageId }); } - }, [chatId, isMobile, focusMessage, toggleChatInfo, messageId, closeMediaViewer]); + }); const lang = useLang(); diff --git a/src/components/mediaViewer/VideoPlayer.tsx b/src/components/mediaViewer/VideoPlayer.tsx index 718dda98a..205b712bc 100644 --- a/src/components/mediaViewer/VideoPlayer.tsx +++ b/src/components/mediaViewer/VideoPlayer.tsx @@ -1,6 +1,6 @@ import type { FC } from '../../lib/teact/teact'; import React, { - memo, useCallback, useEffect, useRef, useState, + memo, useEffect, useRef, useState, } from '../../lib/teact/teact'; import { getActions } from '../../global'; @@ -10,6 +10,8 @@ import { IS_IOS, IS_TOUCH_ENV, IS_YA_BROWSER } from '../../util/windowEnvironmen import safePlay from '../../util/safePlay'; import stopEvent from '../../util/stopEvent'; import { clamp } from '../../util/math'; + +import useLastCallback from '../../hooks/useLastCallback'; import useBuffering from '../../hooks/useBuffering'; import useFullscreen from '../../hooks/useFullscreen'; import usePictureInPicture from '../../hooks/usePictureInPicture'; @@ -83,16 +85,16 @@ const VideoPlayer: FC = ({ const duration = videoRef.current?.duration || 0; const isLooped = isGif || duration <= MAX_LOOP_DURATION; - const handleEnterFullscreen = useCallback(() => { + const handleEnterFullscreen = useLastCallback(() => { // Yandex browser doesn't support PIP when video is hidden if (IS_YA_BROWSER) return; setMediaViewerHidden({ isHidden: true }); - }, [setMediaViewerHidden]); + }); - const handleLeaveFullscreen = useCallback(() => { + const handleLeaveFullscreen = useLastCallback(() => { if (IS_YA_BROWSER) return; setMediaViewerHidden({ isHidden: false }); - }, [setMediaViewerHidden]); + }); const [ isPictureInPictureSupported, @@ -102,17 +104,17 @@ const VideoPlayer: FC = ({ const [, toggleControls, lockControls] = useControlsSignal(); - const handleVideoMove = useCallback(() => { + const handleVideoMove = useLastCallback(() => { toggleControls(true); - }, [toggleControls]); + }); - const handleVideoLeave = useCallback((e) => { + const handleVideoLeave = useLastCallback((e) => { const bounds = videoRef.current?.getBoundingClientRect(); if (!bounds) return; if (e.clientX < bounds.left || e.clientX > bounds.right || e.clientY < bounds.top || e.clientY > bounds.bottom) { toggleControls(false); } - }, [toggleControls]); + }); const { isReady, isBuffered, bufferedRanges, bufferingHandlers, bufferedProgress, @@ -150,7 +152,7 @@ const VideoPlayer: FC = ({ videoRef.current!.playbackRate = playbackRate; }, [playbackRate]); - const togglePlayState = useCallback((e: React.MouseEvent | KeyboardEvent) => { + const togglePlayState = useLastCallback((e: React.MouseEvent | KeyboardEvent) => { e.stopPropagation(); if (isPlaying) { videoRef.current!.pause(); @@ -159,9 +161,9 @@ const VideoPlayer: FC = ({ safePlay(videoRef.current!); setIsPlaying(true); } - }, [isPlaying]); + }); - const handleClick = useCallback((e: React.MouseEvent) => { + const handleClick = useLastCallback((e: React.MouseEvent) => { if (isClickDisabled) { return; } @@ -170,12 +172,12 @@ const VideoPlayer: FC = ({ } else { togglePlayState(e); } - }, [onClose, shouldCloseOnClick, togglePlayState, isClickDisabled]); + }); useVideoCleanup(videoRef, []); const [, setCurrentTime] = useCurrentTimeSignal(); - const handleTimeUpdate = useCallback((e: React.SyntheticEvent) => { + const handleTimeUpdate = useLastCallback((e: React.SyntheticEvent) => { const video = e.currentTarget; if (video.readyState >= MIN_READY_STATE) { setCurrentTime(video.currentTime); @@ -184,40 +186,40 @@ const VideoPlayer: FC = ({ setCurrentTime(0); setIsPlaying(false); } - }, [isLooped, setCurrentTime]); + }); - const handleEnded = useCallback(() => { + const handleEnded = useLastCallback(() => { if (isLooped) return; setCurrentTime(0); setIsPlaying(false); toggleControls(true); - }, [isLooped, setCurrentTime, toggleControls]); + }); - const handleFullscreenChange = useCallback(() => { + const handleFullscreenChange = useLastCallback(() => { if (isFullscreen && exitFullscreen) { exitFullscreen(); } else if (!isFullscreen && setFullscreen) { setFullscreen(); } - }, [exitFullscreen, isFullscreen, setFullscreen]); + }); - const handleSeek = useCallback((position: number) => { + const handleSeek = useLastCallback((position: number) => { videoRef.current!.currentTime = position; - }, []); + }); - const handleVolumeChange = useCallback((newVolume: number) => { + const handleVolumeChange = useLastCallback((newVolume: number) => { setMediaViewerVolume({ volume: newVolume / 100 }); - }, [setMediaViewerVolume]); + }); - const handleVolumeMuted = useCallback(() => { + const handleVolumeMuted = useLastCallback(() => { // Browser requires explicit user interaction to keep video playing after unmuting videoRef.current!.muted = !videoRef.current!.muted; setMediaViewerMuted({ isMuted: !isMuted }); - }, [isMuted, setMediaViewerMuted]); + }); - const handlePlaybackRateChange = useCallback((newPlaybackRate: number) => { + const handlePlaybackRateChange = useLastCallback((newPlaybackRate: number) => { setMediaViewerPlaybackRate({ playbackRate: newPlaybackRate }); - }, [setMediaViewerPlaybackRate]); + }); useEffect(() => { if (!isMediaViewerOpen) return undefined; diff --git a/src/components/mediaViewer/VideoPlayerControls.tsx b/src/components/mediaViewer/VideoPlayerControls.tsx index b9524bd39..e41b43dd5 100644 --- a/src/components/mediaViewer/VideoPlayerControls.tsx +++ b/src/components/mediaViewer/VideoPlayerControls.tsx @@ -1,11 +1,12 @@ import type { FC } from '../../lib/teact/teact'; import React, { - useCallback, memo, useEffect, useMemo, useLayoutEffect, + memo, useEffect, useMemo, useLayoutEffect, } from '../../lib/teact/teact'; import type { BufferedRange } from '../../hooks/useBuffering'; import type { ApiDimensions } from '../../api/types'; +import useLastCallback from '../../hooks/useLastCallback'; import useLang from '../../hooks/useLang'; import useFlag from '../../hooks/useFlag'; import useAppLayout from '../../hooks/useAppLayout'; @@ -46,7 +47,7 @@ type OwnProps = { playbackRate: number; posterSize?: ApiDimensions; onChangeFullscreen: (e: React.MouseEvent) => void; - onPictureInPictureChange?: () => void ; + onPictureInPictureChange?: () => void; onVolumeClick: () => void; onVolumeChange: (volume: number) => void; onPlaybackRateChange: (playbackRate: number) => void; @@ -136,14 +137,14 @@ const VideoPlayerControls: FC = ({ const lang = useLang(); - const handleSeek = useCallback((position: number) => { + const handleSeek = useLastCallback((position: number) => { setIsSeeking(false); onSeek(position); - }, [onSeek, setIsSeeking]); + }); - const handleSeekStart = useCallback(() => { + const handleSeekStart = useLastCallback(() => { setIsSeeking(true); - }, [setIsSeeking]); + }); const volumeIcon = useMemo(() => { if (volume === 0 || isMuted) return 'icon-muted'; @@ -265,4 +266,5 @@ function renderTime(currentTime: number, duration: number) {
); } + export default memo(VideoPlayerControls); diff --git a/src/components/middle/ActionMessage.tsx b/src/components/middle/ActionMessage.tsx index ab6c1d25f..9361c929d 100644 --- a/src/components/middle/ActionMessage.tsx +++ b/src/components/middle/ActionMessage.tsx @@ -1,6 +1,6 @@ import type { FC } from '../../lib/teact/teact'; import React, { - memo, useCallback, useEffect, useMemo, useRef, + memo, useEffect, useMemo, useRef, } from '../../lib/teact/teact'; import { getActions, getGlobal, withGlobal } from '../../global'; @@ -24,6 +24,8 @@ import { getMessageHtmlId, isChatChannel } from '../../global/helpers'; import buildClassName from '../../util/buildClassName'; import { renderActionMessageText } from '../common/helpers/renderActionMessageText'; import { preventMessageInputBlur } from './helpers/preventMessageInputBlur'; + +import useLastCallback from '../../hooks/useLastCallback'; import useEnsureMessage from '../../hooks/useEnsureMessage'; import useContextMenuHandlers from '../../hooks/useContextMenuHandlers'; import type { ObserveFn } from '../../hooks/useIntersectionObserver'; @@ -149,7 +151,7 @@ const ActionMessage: FC = ({ : undefined; }, [targetUserIds, usersById]); - const renderContent = useCallback(() => { + const renderContent = useLastCallback(() => { return renderActionMessageText( lang, message, @@ -163,11 +165,7 @@ const ActionMessage: FC = ({ observeIntersectionForLoading, observeIntersectionForPlaying, ); - }, - [ - isEmbedded, lang, message, observeIntersectionForLoading, observeIntersectionForPlaying, senderChat, - senderUser, targetChatId, targetMessage, targetUsers, topic, - ]); + }); const { isContextMenuOpen, contextMenuPosition, diff --git a/src/components/middle/ActionMessageSuggestedAvatar.tsx b/src/components/middle/ActionMessageSuggestedAvatar.tsx index 177a75ba0..0578dca48 100644 --- a/src/components/middle/ActionMessageSuggestedAvatar.tsx +++ b/src/components/middle/ActionMessageSuggestedAvatar.tsx @@ -1,4 +1,4 @@ -import React, { memo, useCallback, useState } from '../../lib/teact/teact'; +import React, { memo, useState } from '../../lib/teact/teact'; import { getActions } from '../../global'; import type { FC } from '../../lib/teact/teact'; @@ -9,6 +9,8 @@ import { ApiMediaFormat, MAIN_THREAD_ID } from '../../api/types'; import { getMessageMediaHash } from '../../global/helpers'; import * as mediaLoader from '../../util/mediaLoader'; + +import useLastCallback from '../../hooks/useLastCallback'; import useMedia from '../../hooks/useMedia'; import useLang from '../../hooks/useLang'; import useFlag from '../../hooks/useFlag'; @@ -38,7 +40,7 @@ const ActionMessageSuggestedAvatar: FC = ({ const suggestedPhotoUrl = useMedia(getMessageMediaHash(message, 'full')); const isVideo = message.content.action!.photo?.isVideo; - const showAvatarNotification = useCallback(() => { + const showAvatarNotification = useLastCallback(() => { showNotification({ title: lang('ApplyAvatarHintTitle'), message: lang('ApplyAvatarHint'), @@ -50,19 +52,19 @@ const ActionMessageSuggestedAvatar: FC = ({ }, actionText: lang('Open'), }); - }, [lang, showNotification]); + }); - const handleSetSuggestedAvatar = useCallback((file: File) => { + const handleSetSuggestedAvatar = useLastCallback((file: File) => { setCropModalBlob(undefined); uploadProfilePhoto({ file }); showAvatarNotification(); - }, [showAvatarNotification, uploadProfilePhoto]); + }); - const handleCloseCropModal = useCallback(() => { + const handleCloseCropModal = useLastCallback(() => { setCropModalBlob(undefined); - }, []); + }); - const handleSetVideo = useCallback(async () => { + const handleSetVideo = useLastCallback(async () => { closeVideoModal(); showAvatarNotification(); @@ -75,7 +77,7 @@ const ActionMessageSuggestedAvatar: FC = ({ isVideo: true, videoTs: photo.videoSizes?.find((l) => l.videoStartTs !== undefined)?.videoStartTs, }); - }, [closeVideoModal, message.content.action, showAvatarNotification, uploadProfilePhoto]); + }); const handleViewSuggestedAvatar = async () => { if (!isOutgoing && suggestedPhotoUrl) { diff --git a/src/components/middle/AudioPlayer.tsx b/src/components/middle/AudioPlayer.tsx index 3d2d97908..173c66f77 100644 --- a/src/components/middle/AudioPlayer.tsx +++ b/src/components/middle/AudioPlayer.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useMemo, useRef } from '../../lib/teact/teact'; +import React, { useMemo, useRef } from '../../lib/teact/teact'; import { getActions, withGlobal } from '../../global'; import type { FC } from '../../lib/teact/teact'; @@ -33,6 +33,7 @@ import DropdownMenu from '../ui/DropdownMenu'; import MenuItem from '../ui/MenuItem'; import './AudioPlayer.scss'; +import useLastCallback from '../../hooks/useLastCallback'; type OwnProps = { message: ApiMessage; @@ -124,39 +125,39 @@ const AudioPlayer: FC = ({ handleContextMenuClose, handleContextMenuHide, } = useContextMenuHandlers(ref); - const handleClick = useCallback(() => { + const handleClick = useLastCallback(() => { focusMessage({ chatId: message.chatId, messageId: message.id }); - }, [focusMessage, message.chatId, message.id]); + }); - const handleClose = useCallback(() => { + const handleClose = useLastCallback(() => { if (isPlaying) { playPause(); } closeAudioPlayer(); clearMediaSession(); stop(); - }, [closeAudioPlayer, isPlaying, playPause, stop]); + }); - const handleVolumeChange = useCallback((value: number) => { + const handleVolumeChange = useLastCallback((value: number) => { setAudioPlayerVolume({ volume: value / 100 }); setVolume(value / 100); - }, [setAudioPlayerVolume, setVolume]); + }); - const handleVolumeClick = useCallback(() => { + const handleVolumeClick = useLastCallback(() => { if (IS_TOUCH_ENV && !IS_IOS) return; toggleMuted(); setAudioPlayerMuted({ isMuted: !isMuted }); - }, [isMuted, setAudioPlayerMuted, toggleMuted]); + }); - const updatePlaybackRate = useCallback((newRate: number, isActive = true) => { + const updatePlaybackRate = useLastCallback((newRate: number, isActive = true) => { const rate = PLAYBACK_RATES[newRate]; const shouldBeActive = newRate !== REGULAR_PLAYBACK_RATE && isActive; setAudioPlayerPlaybackRate({ playbackRate: rate, isPlaybackRateActive: shouldBeActive }); setPlaybackRate(shouldBeActive ? rate : REGULAR_PLAYBACK_RATE); - }, [setAudioPlayerPlaybackRate, setPlaybackRate]); + }); - const handlePlaybackClick = useCallback(() => { + const handlePlaybackClick = useLastCallback(() => { handleContextMenuClose(); const oldRate = Number(Object.entries(PLAYBACK_RATES).find(([, rate]) => rate === playbackRate)?.[0]) || REGULAR_PLAYBACK_RATE; @@ -166,9 +167,9 @@ const AudioPlayer: FC = ({ newIsActive && oldRate === REGULAR_PLAYBACK_RATE ? DEFAULT_FAST_PLAYBACK_RATE : oldRate, newIsActive, ); - }, [handleContextMenuClose, isPlaybackRateActive, playbackRate, updatePlaybackRate]); + }); - const PlaybackRateButton = useCallback(() => { + const PlaybackRateButton = useLastCallback(() => { const displayRate = Object.entries(PLAYBACK_RATES).find(([, rate]) => rate === playbackRate)?.[0] || REGULAR_PLAYBACK_RATE; const text = `${playbackRate === REGULAR_PLAYBACK_RATE ? DEFAULT_FAST_PLAYBACK_RATE : displayRate}Ð¥`; @@ -201,10 +202,7 @@ const AudioPlayer: FC = ({ ); - }, [ - handleBeforeContextMenu, handleContextMenu, handleContextMenuClose, handlePlaybackClick, isContextMenuOpen, - isMobile, isPlaybackRateActive, playbackRate, - ]); + }); const volumeIcon = useMemo(() => { if (volume === 0 || isMuted) return 'icon-muted'; diff --git a/src/components/middle/ChatReportPanel.tsx b/src/components/middle/ChatReportPanel.tsx index cccfebd26..edc2ecb1d 100644 --- a/src/components/middle/ChatReportPanel.tsx +++ b/src/components/middle/ChatReportPanel.tsx @@ -1,5 +1,5 @@ import type { FC } from '../../lib/teact/teact'; -import React, { memo, useCallback, useState } from '../../lib/teact/teact'; +import React, { memo, useState } from '../../lib/teact/teact'; import { withGlobal, getActions } from '../../global'; import type { ApiChat, ApiChatSettings, ApiUser } from '../../api/types'; @@ -9,6 +9,8 @@ import { getChatTitle, getUserFirstOrLastName, getUserFullName, isChatBasicGroup, isUserId, } from '../../global/helpers'; import buildClassName from '../../util/buildClassName'; + +import useLastCallback from '../../hooks/useLastCallback'; import useLang from '../../hooks/useLang'; import useFlag from '../../hooks/useFlag'; @@ -55,14 +57,14 @@ const ChatReportPanel: FC = ({ } = settings || {}; const isBasicGroup = chat && isChatBasicGroup(chat); - const handleAddContact = useCallback(() => { + const handleAddContact = useLastCallback(() => { openAddContactDialog({ userId: chatId }); if (isAutoArchived) { toggleChatArchived({ id: chatId }); } - }, [openAddContactDialog, isAutoArchived, toggleChatArchived, chatId]); + }); - const handleConfirmBlock = useCallback(() => { + const handleConfirmBlock = useLastCallback(() => { closeBlockUserModal(); blockContact({ contactId: chatId, accessHash: accessHash! }); if (canReportSpam && shouldReportSpam) { @@ -71,16 +73,13 @@ const ChatReportPanel: FC = ({ if (shouldDeleteChat) { deleteChat({ chatId }); } - }, [ - accessHash, blockContact, closeBlockUserModal, deleteChat, reportSpam, canReportSpam, shouldDeleteChat, - shouldReportSpam, chatId, - ]); + }); - const handleCloseReportPanel = useCallback(() => { + const handleCloseReportPanel = useLastCallback(() => { hideChatReportPanel({ chatId }); - }, [chatId, hideChatReportPanel]); + }); - const handleChatReportSpam = useCallback(() => { + const handleChatReportSpam = useLastCallback(() => { closeBlockUserModal(); reportSpam({ chatId }); if (isBasicGroup) { @@ -89,9 +88,7 @@ const ChatReportPanel: FC = ({ } else { leaveChannel({ chatId }); } - }, [ - chatId, closeBlockUserModal, currentUserId, deleteChatUser, deleteHistory, isBasicGroup, leaveChannel, reportSpam, - ]); + }); if (!settings) { return undefined; diff --git a/src/components/middle/ContactGreeting.tsx b/src/components/middle/ContactGreeting.tsx index b98e81ee1..9367d0583 100644 --- a/src/components/middle/ContactGreeting.tsx +++ b/src/components/middle/ContactGreeting.tsx @@ -1,16 +1,16 @@ import type { FC } from '../../lib/teact/teact'; -import React, { - memo, useCallback, useEffect, useRef, -} from '../../lib/teact/teact'; +import React, { memo, useEffect, useRef } from '../../lib/teact/teact'; import { getActions, withGlobal } from '../../global'; import type { ApiSticker, ApiUpdateConnectionStateType } from '../../api/types'; import { selectChat } from '../../global/selectors'; -import { useIntersectionObserver } from '../../hooks/useIntersectionObserver'; -import useLang from '../../hooks/useLang'; import { getUserIdDividend } from '../../global/helpers'; +import useLastCallback from '../../hooks/useLastCallback'; +import useLang from '../../hooks/useLang'; +import { useIntersectionObserver } from '../../hooks/useIntersectionObserver'; + import StickerButton from '../common/StickerButton'; import './ContactGreeting.scss'; @@ -61,13 +61,13 @@ const ContactGreeting: FC = ({ } }, [connectionState, markMessageListRead, lastUnreadMessageId]); - const handleStickerSelect = useCallback((selectedSticker: ApiSticker) => { + const handleStickerSelect = useLastCallback((selectedSticker: ApiSticker) => { selectedSticker = { ...selectedSticker, isPreloadedGlobally: true, }; sendMessage({ sticker: selectedSticker }); - }, [sendMessage]); + }); return (
diff --git a/src/components/middle/DeleteSelectedMessageModal.tsx b/src/components/middle/DeleteSelectedMessageModal.tsx index 886fe3ae9..ba545f8c4 100644 --- a/src/components/middle/DeleteSelectedMessageModal.tsx +++ b/src/components/middle/DeleteSelectedMessageModal.tsx @@ -1,5 +1,5 @@ import type { FC } from '../../lib/teact/teact'; -import React, { useCallback, memo, useEffect } from '../../lib/teact/teact'; +import React, { memo, useEffect } from '../../lib/teact/teact'; import { getActions, withGlobal } from '../../global'; import { @@ -13,6 +13,8 @@ import { isChatSuperGroup, } from '../../global/helpers'; import renderText from '../common/helpers/renderText'; + +import useLastCallback from '../../hooks/useLastCallback'; import useLang from '../../hooks/useLang'; import usePrevious from '../../hooks/usePrevious'; @@ -51,12 +53,12 @@ const DeleteSelectedMessageModal: FC = ({ const prevIsOpen = usePrevious(isOpen); - const handleDeleteMessageForAll = useCallback(() => { + const handleDeleteMessageForAll = useLastCallback(() => { onClose(); deleteMessages({ messageIds: selectedMessageIds!, shouldDeleteForAll: true }); - }, [deleteMessages, selectedMessageIds, onClose]); + }); - const handleDeleteMessageForSelf = useCallback(() => { + const handleDeleteMessageForSelf = useLastCallback(() => { if (isSchedule) { deleteScheduledMessages({ messageIds: selectedMessageIds! }); } else { @@ -64,7 +66,7 @@ const DeleteSelectedMessageModal: FC = ({ } onClose(); - }, [isSchedule, onClose, deleteScheduledMessages, selectedMessageIds, deleteMessages]); + }); const lang = useLang(); diff --git a/src/components/middle/EmojiInteractionAnimation.tsx b/src/components/middle/EmojiInteractionAnimation.tsx index 1744e12c5..0914bd0ff 100644 --- a/src/components/middle/EmojiInteractionAnimation.tsx +++ b/src/components/middle/EmojiInteractionAnimation.tsx @@ -1,20 +1,23 @@ import type { FC } from '../../lib/teact/teact'; import React, { - memo, useCallback, useEffect, useLayoutEffect, useRef, + memo, useEffect, useLayoutEffect, useRef, } from '../../lib/teact/teact'; import { getActions, withGlobal } from '../../global'; import type { ActiveEmojiInteraction } from '../../global/types'; import { IS_ANDROID } from '../../util/windowEnvironment'; -import useFlag from '../../hooks/useFlag'; -import useMedia from '../../hooks/useMedia'; -import buildClassName from '../../util/buildClassName'; + import { selectAnimatedEmojiEffect, } from '../../global/selectors'; import { dispatchHeavyAnimationEvent } from '../../hooks/useHeavyAnimationCheck'; +import useLastCallback from '../../hooks/useLastCallback'; +import useFlag from '../../hooks/useFlag'; +import useMedia from '../../hooks/useMedia'; +import buildClassName from '../../util/buildClassName'; + import AnimatedSticker from '../common/AnimatedSticker'; import './EmojiInteractionAnimation.scss'; @@ -41,7 +44,7 @@ const EmojiInteractionAnimation: FC = ({ const [isPlaying, startPlaying] = useFlag(false); const timeoutRef = useRef(); - const stop = useCallback(() => { + const stop = useLastCallback(() => { startHiding(); if (timeoutRef.current) { clearTimeout(timeoutRef.current); @@ -49,13 +52,13 @@ const EmojiInteractionAnimation: FC = ({ setTimeout(() => { stopActiveEmojiInteraction({ id: activeEmojiInteraction.id }); }, HIDE_ANIMATION_DURATION); - }, [activeEmojiInteraction.id, startHiding, stopActiveEmojiInteraction]); + }); - const handleCancelAnimation = useCallback((e: UIEvent) => { + const handleCancelAnimation = useLastCallback((e: UIEvent) => { if (!(e.target as HTMLElement)?.closest('.AnimatedEmoji')) { stop(); } - }, [stop]); + }); useEffect(() => { document.addEventListener('touchstart', handleCancelAnimation); diff --git a/src/components/middle/FloatingActionButtons.tsx b/src/components/middle/FloatingActionButtons.tsx index 22c2f341b..c38662ff7 100644 --- a/src/components/middle/FloatingActionButtons.tsx +++ b/src/components/middle/FloatingActionButtons.tsx @@ -1,7 +1,5 @@ import type { FC } from '../../lib/teact/teact'; -import React, { - useCallback, memo, useRef, useEffect, -} from '../../lib/teact/teact'; +import React, { memo, useRef, useEffect } from '../../lib/teact/teact'; import { getActions, withGlobal } from '../../global'; import type { MessageListType } from '../../global/types'; @@ -11,6 +9,8 @@ import { selectChat, selectCurrentMessageList } from '../../global/selectors'; import buildClassName from '../../util/buildClassName'; import animateScroll from '../../util/animateScroll'; +import useLastCallback from '../../hooks/useLastCallback'; + import ScrollDownButton from './ScrollDownButton'; import styles from './FloatingActionButtons.module.scss'; @@ -64,7 +64,7 @@ const FloatingActionButtons: FC = ({ } }, [chatId, fetchUnreadMentions, hasUnreadMentions]); - const handleClick = useCallback(() => { + const handleClick = useLastCallback(() => { if (!isShown) { return; } @@ -81,7 +81,7 @@ const FloatingActionButtons: FC = ({ animateScroll(messagesContainer, lastMessageElement, 'end', FOCUS_MARGIN); } - }, [isShown, messageListType, focusNextReply]); + }); const fabClassName = buildClassName( styles.root, diff --git a/src/components/middle/HeaderActions.tsx b/src/components/middle/HeaderActions.tsx index 048674bac..960ee2faa 100644 --- a/src/components/middle/HeaderActions.tsx +++ b/src/components/middle/HeaderActions.tsx @@ -1,10 +1,5 @@ import type { FC } from '../../lib/teact/teact'; -import React, { - memo, - useRef, - useCallback, - useState, -} from '../../lib/teact/teact'; +import React, { memo, useRef, useState } from '../../lib/teact/teact'; import { requestMeasure, requestNextMutation } from '../../lib/fasterdom/fasterdom'; import { getActions, withGlobal } from '../../global'; @@ -28,6 +23,8 @@ import { selectIsRightColumnShown, selectIsUserBlocked, } from '../../global/selectors'; + +import useLastCallback from '../../hooks/useLastCallback'; import useLang from '../../hooks/useLang'; import { useHotkeys } from '../../hooks/useHotkeys'; @@ -108,42 +105,42 @@ const HeaderActions: FC = ({ const [isMenuOpen, setIsMenuOpen] = useState(false); const [menuPosition, setMenuPosition] = useState(undefined); - const handleHeaderMenuOpen = useCallback(() => { + const handleHeaderMenuOpen = useLastCallback(() => { setIsMenuOpen(true); const rect = menuButtonRef.current!.getBoundingClientRect(); setMenuPosition({ x: rect.right, y: rect.bottom }); - }, []); + }); - const handleHeaderMenuClose = useCallback(() => { + const handleHeaderMenuClose = useLastCallback(() => { setIsMenuOpen(false); - }, []); + }); - const handleHeaderMenuHide = useCallback(() => { + const handleHeaderMenuHide = useLastCallback(() => { setMenuPosition(undefined); - }, []); + }); - const handleSubscribeClick = useCallback(() => { + const handleSubscribeClick = useLastCallback(() => { joinChannel({ chatId }); if (shouldSendJoinRequest) { showNotification({ message: isChannel ? lang('RequestToJoinChannelSentDescription') : lang('RequestToJoinGroupSentDescription'), }); } - }, [joinChannel, chatId, shouldSendJoinRequest, showNotification, isChannel, lang]); + }); - const handleStartBot = useCallback(() => { + const handleStartBot = useLastCallback(() => { sendBotCommand({ command: '/start' }); - }, [sendBotCommand]); + }); - const handleRestartBot = useCallback(() => { + const handleRestartBot = useLastCallback(() => { restartBot({ chatId }); - }, [chatId, restartBot]); + }); - const handleJoinRequestsClick = useCallback(() => { + const handleJoinRequestsClick = useLastCallback(() => { requestNextManagementScreen({ screen: ManagementScreens.JoinRequests }); - }, [requestNextManagementScreen]); + }); - const handleSearchClick = useCallback(() => { + const handleSearchClick = useLastCallback(() => { if (withForumActions) { onTopicSearch?.(); return; @@ -163,24 +160,24 @@ const HeaderActions: FC = ({ } else { setTimeout(setFocusInSearchInput, SEARCH_FOCUS_DELAY_MS); } - }, [isMobile, noAnimation, onTopicSearch, openLocalTextSearch, withForumActions]); + }); - const handleAsMessagesClick = useCallback(() => { + const handleAsMessagesClick = useLastCallback(() => { openChat({ id: chatId, threadId: MAIN_THREAD_ID }); - }, [chatId, openChat]); + }); function handleRequestCall() { requestMasterAndRequestCall({ userId: chatId }); } - const handleHotkeySearchClick = useCallback((e: KeyboardEvent) => { + const handleHotkeySearchClick = useLastCallback((e: KeyboardEvent) => { if (!canSearch || !IS_APP || e.shiftKey) { return; } e.preventDefault(); handleSearchClick(); - }, [canSearch, handleSearchClick]); + }); useHotkeys({ 'Mod+F': handleHotkeySearchClick, diff --git a/src/components/middle/HeaderMenuContainer.tsx b/src/components/middle/HeaderMenuContainer.tsx index 68fa410d0..e32a83f63 100644 --- a/src/components/middle/HeaderMenuContainer.tsx +++ b/src/components/middle/HeaderMenuContainer.tsx @@ -1,6 +1,6 @@ import type { FC } from '../../lib/teact/teact'; import React, { - memo, useCallback, useEffect, useMemo, useState, + memo, useEffect, useMemo, useState, } from '../../lib/teact/teact'; import { getActions, withGlobal } from '../../global'; @@ -33,6 +33,8 @@ import { isUserRightBanned, selectIsChatMuted, } from '../../global/helpers'; + +import useLastCallback from '../../hooks/useLastCallback'; import useShowTransition from '../../hooks/useShowTransition'; import usePrevDuringAnimation from '../../hooks/usePrevDuringAnimation'; import useLang from '../../hooks/useLang'; @@ -181,76 +183,76 @@ const HeaderMenuContainer: FC = ({ (!isChatInfoShown && isForum) ? true : undefined, CLOSE_MENU_ANIMATION_DURATION, ); - const handleReport = useCallback(() => { + const handleReport = useLastCallback(() => { setIsMenuOpen(false); setIsReportModalOpen(true); - }, []); + }); - const closeReportModal = useCallback(() => { + const closeReportModal = useLastCallback(() => { setIsReportModalOpen(false); onClose(); - }, [onClose]); + }); - const closeMuteModal = useCallback(() => { + const closeMuteModal = useLastCallback(() => { setIsMuteModalOpen(false); onClose(); - }, [onClose]); + }); - const handleDelete = useCallback(() => { + const handleDelete = useLastCallback(() => { setIsMenuOpen(false); setIsDeleteModalOpen(true); - }, []); + }); - const closeMenu = useCallback(() => { + const closeMenu = useLastCallback(() => { setIsMenuOpen(false); onClose(); - }, [onClose]); + }); - const handleViewGroupInfo = useCallback(() => { + const handleViewGroupInfo = useLastCallback(() => { openChatWithInfo({ id: chatId, threadId }); closeMenu(); - }, [chatId, closeMenu, openChatWithInfo, threadId]); + }); - const closeDeleteModal = useCallback(() => { + const closeDeleteModal = useLastCallback(() => { setIsDeleteModalOpen(false); onClose(); - }, [onClose]); + }); - const handleStartBot = useCallback(() => { + const handleStartBot = useLastCallback(() => { sendBotCommand({ command: '/start' }); - }, [sendBotCommand]); + }); - const handleRestartBot = useCallback(() => { + const handleRestartBot = useLastCallback(() => { restartBot({ chatId }); - }, [chatId, restartBot]); + }); - const handleUnmuteClick = useCallback(() => { + const handleUnmuteClick = useLastCallback(() => { updateChatMutedState({ chatId, isMuted: false }); closeMenu(); - }, [chatId, closeMenu, updateChatMutedState]); + }); - const handleMuteClick = useCallback(() => { + const handleMuteClick = useLastCallback(() => { markRenderMuteModal(); setIsMuteModalOpen(true); setIsMenuOpen(false); - }, []); + }); - const handleCreateTopicClick = useCallback(() => { + const handleCreateTopicClick = useLastCallback(() => { openCreateTopicPanel({ chatId }); closeMenu(); - }, [openCreateTopicPanel, chatId, closeMenu]); + }); - const handleEditTopicClick = useCallback(() => { + const handleEditTopicClick = useLastCallback(() => { openEditTopicPanel({ chatId, topicId: threadId }); closeMenu(); - }, [openEditTopicPanel, chatId, threadId, closeMenu]); + }); - const handleViewAsTopicsClick = useCallback(() => { + const handleViewAsTopicsClick = useLastCallback(() => { openChat({ id: undefined }); closeMenu(); - }, [closeMenu, openChat]); + }); - const handleEnterVoiceChatClick = useCallback(() => { + const handleEnterVoiceChatClick = useLastCallback(() => { if (canCreateVoiceChat) { // TODO Show popup to schedule createGroupCall({ @@ -262,57 +264,57 @@ const HeaderMenuContainer: FC = ({ }); } closeMenu(); - }, [closeMenu, canCreateVoiceChat, chatId, requestMasterAndJoinGroupCall, createGroupCall]); + }); - const handleLinkedChatClick = useCallback(() => { + const handleLinkedChatClick = useLastCallback(() => { openLinkedChat({ id: chatId }); closeMenu(); - }, [chatId, closeMenu, openLinkedChat]); + }); - const handleGiftPremiumClick = useCallback(() => { + const handleGiftPremiumClick = useLastCallback(() => { openGiftPremiumModal({ forUserId: chatId }); closeMenu(); - }, [openGiftPremiumModal, chatId, closeMenu]); + }); - const handleAddContactClick = useCallback(() => { + const handleAddContactClick = useLastCallback(() => { openAddContactDialog({ userId: chatId }); closeMenu(); - }, [openAddContactDialog, chatId, closeMenu]); + }); - const handleSubscribe = useCallback(() => { + const handleSubscribe = useLastCallback(() => { onSubscribeChannel(); closeMenu(); - }, [closeMenu, onSubscribeChannel]); + }); - const handleVideoCall = useCallback(() => { + const handleVideoCall = useLastCallback(() => { requestMasterAndRequestCall({ userId: chatId, isVideo: true }); closeMenu(); - }, [chatId, closeMenu, requestMasterAndRequestCall]); + }); - const handleCall = useCallback(() => { + const handleCall = useLastCallback(() => { requestMasterAndRequestCall({ userId: chatId }); closeMenu(); - }, [chatId, closeMenu, requestMasterAndRequestCall]); + }); - const handleSearch = useCallback(() => { + const handleSearch = useLastCallback(() => { onSearchClick(); closeMenu(); - }, [closeMenu, onSearchClick]); + }); - const handleStatisticsClick = useCallback(() => { + const handleStatisticsClick = useLastCallback(() => { toggleStatistics(); closeMenu(); - }, [closeMenu, toggleStatistics]); + }); - const handleSelectMessages = useCallback(() => { + const handleSelectMessages = useLastCallback(() => { enterMessageSelectMode(); closeMenu(); - }, [closeMenu, enterMessageSelectMode]); + }); - const handleOpenAsMessages = useCallback(() => { + const handleOpenAsMessages = useLastCallback(() => { onAsMessagesClick(); closeMenu(); - }, [closeMenu, onAsMessagesClick]); + }); useEffect(() => { disableScrolling(); diff --git a/src/components/middle/HeaderPinnedMessage.tsx b/src/components/middle/HeaderPinnedMessage.tsx index 89854e415..e0f1885eb 100644 --- a/src/components/middle/HeaderPinnedMessage.tsx +++ b/src/components/middle/HeaderPinnedMessage.tsx @@ -1,5 +1,5 @@ import type { FC } from '../../lib/teact/teact'; -import React, { memo, useCallback } from '../../lib/teact/teact'; +import React, { memo } from '../../lib/teact/teact'; import { getActions } from '../../global'; import type { ApiMessage } from '../../api/types'; @@ -13,6 +13,7 @@ import buildClassName from '../../util/buildClassName'; import { IS_TOUCH_ENV } from '../../util/windowEnvironment'; import renderText from '../common/helpers/renderText'; +import useLastCallback from '../../hooks/useLastCallback'; import useMedia from '../../hooks/useMedia'; import useThumbnail from '../../hooks/useThumbnail'; import useFlag from '../../hooks/useFlag'; @@ -61,21 +62,21 @@ const HeaderPinnedMessage: FC = ({ const [isUnpinDialogOpen, openUnpinDialog, closeUnpinDialog] = useFlag(); - const handleUnpinMessage = useCallback(() => { + const handleUnpinMessage = useLastCallback(() => { closeUnpinDialog(); if (onUnpinMessage) { onUnpinMessage(message.id); } - }, [closeUnpinDialog, onUnpinMessage, message.id]); + }); const inlineButton = getMessageSingleInlineButton(message); - const handleInlineButtonClick = useCallback(() => { + const handleInlineButtonClick = useLastCallback(() => { if (inlineButton) { clickBotInlineButton({ messageId: message.id, button: inlineButton }); } - }, [clickBotInlineButton, inlineButton, message.id]); + }); const [noHoverColor, markNoHoverColor, unmarkNoHoverColor] = useFlag(); diff --git a/src/components/middle/MessageLanguageModal.tsx b/src/components/middle/MessageLanguageModal.tsx index 3447bc464..b76bc3145 100644 --- a/src/components/middle/MessageLanguageModal.tsx +++ b/src/components/middle/MessageLanguageModal.tsx @@ -1,5 +1,5 @@ import React, { - memo, useCallback, useEffect, useMemo, useState, + memo, useEffect, useMemo, useState, } from '../../lib/teact/teact'; import { getActions, withGlobal } from '../../global'; @@ -10,6 +10,7 @@ import { SUPPORTED_TRANSLATION_LANGUAGES } from '../../config'; import buildClassName from '../../util/buildClassName'; import renderText from '../common/helpers/renderText'; +import useLastCallback from '../../hooks/useLastCallback'; import useLang from '../../hooks/useLang'; import Modal from '../ui/Modal'; @@ -47,16 +48,16 @@ const MessageLanguageModal: FC = ({ const [search, setSearch] = useState(''); const lang = useLang(); - const handleSelect = useCallback((toLanguageCode: string) => { + const handleSelect = useLastCallback((toLanguageCode: string) => { if (!chatId || !messageId) return; requestMessageTranslation({ chatId, id: messageId, toLanguageCode }); closeMessageLanguageModal(); - }, [chatId, closeMessageLanguageModal, messageId, requestMessageTranslation]); + }); - const handleSearch = useCallback((e: React.ChangeEvent) => { + const handleSearch = useLastCallback((e: React.ChangeEvent) => { setSearch(e.target.value); - }, []); + }); const translateLanguages = useMemo(() => SUPPORTED_TRANSLATION_LANGUAGES.map((langCode: string) => { const translatedNames = new Intl.DisplayNames([currentLanguageCode], { type: 'language' }); diff --git a/src/components/middle/MessageList.tsx b/src/components/middle/MessageList.tsx index 2156dc9fe..2e3fadae5 100644 --- a/src/components/middle/MessageList.tsx +++ b/src/components/middle/MessageList.tsx @@ -1,6 +1,5 @@ import React, { memo, - useCallback, useEffect, useMemo, useRef, @@ -69,6 +68,7 @@ import resetScroll from '../../util/resetScroll'; import animateScroll, { isAnimatingScroll, restartCurrentScrollAnimation } from '../../util/animateScroll'; import renderText from '../common/helpers/renderText'; +import useLastCallback from '../../hooks/useLastCallback'; import { useStateRef } from '../../hooks/useStateRef'; import useSyncEffect from '../../hooks/useSyncEffect'; import useStickyDates from './hooks/useStickyDates'; @@ -321,7 +321,7 @@ const MessageList: FC = ({ const { isScrolled, updateStickyDates } = useStickyDates(); - const handleScroll = useCallback(() => { + const handleScroll = useLastCallback(() => { if (isScrollTopJustUpdatedRef.current) { isScrollTopJustUpdatedRef.current = false; return; @@ -353,9 +353,7 @@ const MessageList: FC = ({ setScrollOffset({ chatId, threadId, scrollOffset: scrollOffsetRef.current }); } }); - }, [ - updateStickyDates, hasTools, getForceNextPinnedInHeader, onPinnedIntersectionChange, type, chatId, threadId, - ]); + }); const [getContainerHeight, prevContainerHeightRef] = useContainerHeight(containerRef, canPost && !isSelectModeActive); diff --git a/src/components/middle/MessageSelectToolbar.tsx b/src/components/middle/MessageSelectToolbar.tsx index 3e1463c47..81f39358c 100644 --- a/src/components/middle/MessageSelectToolbar.tsx +++ b/src/components/middle/MessageSelectToolbar.tsx @@ -1,5 +1,5 @@ import type { FC } from '../../lib/teact/teact'; -import React, { memo, useCallback, useEffect } from '../../lib/teact/teact'; +import React, { memo, useEffect } from '../../lib/teact/teact'; import { getActions, withGlobal } from '../../global'; import type { MessageListType } from '../../global/types'; @@ -16,6 +16,7 @@ import { import captureKeyboardListeners from '../../util/captureKeyboardListeners'; import buildClassName from '../../util/buildClassName'; +import useLastCallback from '../../hooks/useLastCallback'; import useFlag from '../../hooks/useFlag'; import usePrevious from '../../hooks/usePrevious'; import useLang from '../../hooks/useLang'; @@ -73,9 +74,9 @@ const MessageSelectToolbar: FC = ({ useCopySelectedMessages(isActive); - const handleExitMessageSelectMode = useCallback(() => { + const handleExitMessageSelectMode = useLastCallback(() => { exitMessageSelectMode(); - }, [exitMessageSelectMode]); + }); useEffect(() => { return isActive && !isDeleteModalOpen && !isReportModalOpen && !isAnyModalOpen @@ -90,18 +91,18 @@ const MessageSelectToolbar: FC = ({ canDeleteMessages, ]); - const handleCopy = useCallback(() => { + const handleCopy = useLastCallback(() => { copySelectedMessages(); showNotification({ message: lang('Share.Link.Copied'), }); exitMessageSelectMode(); - }, [copySelectedMessages, exitMessageSelectMode, lang, showNotification]); + }); - const handleDownload = useCallback(() => { + const handleDownload = useLastCallback(() => { downloadSelectedMessages(); exitMessageSelectMode(); - }, [downloadSelectedMessages, exitMessageSelectMode]); + }); const prevSelectedMessagesCount = usePrevious(selectedMessagesCount || undefined, true); const renderingSelectedMessagesCount = isActive ? selectedMessagesCount : prevSelectedMessagesCount; diff --git a/src/components/middle/MiddleColumn.tsx b/src/components/middle/MiddleColumn.tsx index d4fe8c5a8..96a6312b1 100644 --- a/src/components/middle/MiddleColumn.tsx +++ b/src/components/middle/MiddleColumn.tsx @@ -1,6 +1,6 @@ import type { RefObject } from 'react'; import React, { - useEffect, useState, memo, useMemo, useCallback, + useEffect, useState, memo, useMemo, } from '../../lib/teact/teact'; import { requestMutation } from '../../lib/fasterdom/fasterdom'; import { getActions, withGlobal } from '../../global'; @@ -59,6 +59,8 @@ import { import calculateMiddleFooterTransforms from './helpers/calculateMiddleFooterTransforms'; import captureEscKeyListener from '../../util/captureEscKeyListener'; import buildClassName from '../../util/buildClassName'; + +import useLastCallback from '../../hooks/useLastCallback'; import useCustomBackground from '../../hooks/useCustomBackground'; import useWindowSize from '../../hooks/useWindowSize'; import usePrevDuringAnimation from '../../hooks/usePrevDuringAnimation'; @@ -307,7 +309,7 @@ function MiddleColumn({ return () => { visualViewport.removeEventListener('resize', handleResize); }; - }, []); + }); useEffect(() => { if (isPrivate) { @@ -333,7 +335,7 @@ function MiddleColumn({ leftColumnWidth: n, }), resetLeftColumnWidth, leftColumnWidth, '--left-column-width'); - const handleDragEnter = useCallback((e: React.DragEvent) => { + const handleDragEnter = useLastCallback((e: React.DragEvent) => { const { items } = e.dataTransfer || {}; const shouldDrawQuick = items && items.length > 0 && Array.from(items) // Filter unnecessary element for drag and drop images in Firefox (https://github.com/Ajaxy/telegram-tt/issues/49) @@ -343,46 +345,46 @@ function MiddleColumn({ .every(isImage); setDropAreaState(shouldDrawQuick ? DropAreaState.QuickFile : DropAreaState.Document); - }, []); + }); - const handleHideDropArea = useCallback(() => { + const handleHideDropArea = useLastCallback(() => { setDropAreaState(DropAreaState.None); - }, []); + }); - const handleOpenUnpinModal = useCallback(() => { + const handleOpenUnpinModal = useLastCallback(() => { setIsUnpinModalOpen(true); - }, []); + }); - const closeUnpinModal = useCallback(() => { + const closeUnpinModal = useLastCallback(() => { setIsUnpinModalOpen(false); - }, []); + }); - const handleUnpinAllMessages = useCallback(() => { + const handleUnpinAllMessages = useLastCallback(() => { unpinAllMessages({ chatId: chatId!, threadId: threadId! }); closeUnpinModal(); openPreviousChat(); - }, [unpinAllMessages, chatId, threadId, closeUnpinModal, openPreviousChat]); + }); - const handleTabletFocus = useCallback(() => { + const handleTabletFocus = useLastCallback(() => { openChat({ id: chatId }); - }, [openChat, chatId]); + }); - const handleSubscribeClick = useCallback(() => { + const handleSubscribeClick = useLastCallback(() => { joinChannel({ chatId: chatId! }); if (renderingShouldSendJoinRequest) { showNotification({ message: isChannel ? lang('RequestToJoinChannelSentDescription') : lang('RequestToJoinGroupSentDescription'), }); } - }, [joinChannel, chatId, renderingShouldSendJoinRequest, showNotification, isChannel, lang]); + }); - const handleStartBot = useCallback(() => { + const handleStartBot = useLastCallback(() => { sendBotCommand({ command: '/start' }); - }, [sendBotCommand]); + }); - const handleRestartBot = useCallback(() => { + const handleRestartBot = useLastCallback(() => { restartBot({ chatId: chatId! }); - }, [chatId, restartBot]); + }); const customBackgroundValue = useCustomBackground(theme, customBackground); diff --git a/src/components/middle/MiddleHeader.tsx b/src/components/middle/MiddleHeader.tsx index 7d1dd365a..c94012748 100644 --- a/src/components/middle/MiddleHeader.tsx +++ b/src/components/middle/MiddleHeader.tsx @@ -1,6 +1,6 @@ import type { FC } from '../../lib/teact/teact'; import React, { - memo, useCallback, useEffect, useLayoutEffect, useRef, + memo, useEffect, useLayoutEffect, useRef, } from '../../lib/teact/teact'; import { requestMutation } from '../../lib/fasterdom/fasterdom'; import { getActions, withGlobal } from '../../global'; @@ -44,6 +44,9 @@ import { selectThreadTopMessageId, } from '../../global/selectors'; import cycleRestrict from '../../util/cycleRestrict'; + +import useLastCallback from '../../hooks/useLastCallback'; +import { useFastClick } from '../../hooks/useFastClick'; import useEnsureMessage from '../../hooks/useEnsureMessage'; import useWindowSize from '../../hooks/useWindowSize'; import useShowTransition from '../../hooks/useShowTransition'; @@ -68,7 +71,6 @@ import GroupCallTopPane from '../calls/group/GroupCallTopPane'; import ChatReportPanel from './ChatReportPanel'; import './MiddleHeader.scss'; -import { useFastClick } from '../../hooks/useFastClick'; const ANIMATION_DURATION = 350; const BACK_BUTTON_INACTIVE_TIME = 450; @@ -184,11 +186,11 @@ const MiddleHeader: FC = ({ openChatWithInfo({ id: chatId, threadId }); }); - const handleUnpinMessage = useCallback((messageId: number) => { + const handleUnpinMessage = useLastCallback((messageId: number) => { pinMessage({ messageId, isUnpin: true }); - }, [pinMessage]); + }); - const handlePinnedMessageClick = useCallback((e: React.MouseEvent): void => { + const handlePinnedMessageClick = useLastCallback((e: React.MouseEvent): void => { const messageId = e.shiftKey && Array.isArray(pinnedMessageIds) ? pinnedMessageIds[cycleRestrict(pinnedMessageIds.length, pinnedMessageIds.indexOf(pinnedMessageId!) - 2)] : pinnedMessageId!; @@ -198,19 +200,19 @@ const MiddleHeader: FC = ({ chatId, threadId, messageId, noForumTopicPanel: true, }); } - }, [pinnedMessageIds, pinnedMessageId, onFocusPinnedMessage, chatId, threadId]); + }); - const handleAllPinnedClick = useCallback(() => { + const handleAllPinnedClick = useLastCallback(() => { openChat({ id: chatId, threadId, type: 'pinned' }); - }, [openChat, chatId, threadId]); + }); - const setBackButtonActive = useCallback(() => { + const setBackButtonActive = useLastCallback(() => { setTimeout(() => { isBackButtonActive.current = true; }, BACK_BUTTON_INACTIVE_TIME); - }, []); + }); - const handleBackClick = useCallback((e: React.MouseEvent) => { + const handleBackClick = useLastCallback((e: React.MouseEvent) => { if (!isBackButtonActive.current) return; // Workaround for missing UI when quickly clicking the Back button @@ -241,10 +243,7 @@ const MiddleHeader: FC = ({ openPreviousChat(); setBackButtonActive(); - }, [ - isMobile, isSelectModeActive, messageListType, currentTransitionKey, setBackButtonActive, isTablet, - shouldShowCloseButton, - ]); + }); const canToolsCollideWithChatInfo = ( windowWidth >= MIN_SCREEN_WIDTH_FOR_STATIC_LEFT_COLUMN diff --git a/src/components/middle/MobileSearch.tsx b/src/components/middle/MobileSearch.tsx index 49a1e3740..bbad6ae2d 100644 --- a/src/components/middle/MobileSearch.tsx +++ b/src/components/middle/MobileSearch.tsx @@ -1,6 +1,6 @@ import type { FC } from '../../lib/teact/teact'; import React, { - memo, useCallback, useEffect, useRef, useState, useLayoutEffect, + memo, useEffect, useRef, useState, useLayoutEffect, } from '../../lib/teact/teact'; import { requestMutation } from '../../lib/fasterdom/fasterdom'; import { getActions, withGlobal } from '../../global'; @@ -17,6 +17,8 @@ import { } from '../../global/selectors'; import { getDayStartAt } from '../../util/dateFormat'; +import useLastCallback from '../../hooks/useLastCallback'; + import Button from '../ui/Button'; import SearchInput from '../ui/SearchInput'; @@ -123,33 +125,33 @@ const MobileSearchFooter: FC = ({ searchInput.blur(); }, [isHistoryCalendarOpen]); - const handleMessageSearchQueryChange = useCallback((newQuery: string) => { + const handleMessageSearchQueryChange = useLastCallback((newQuery: string) => { setLocalTextSearchQuery({ query: newQuery }); if (newQuery.length) { runDebouncedForSearch(searchTextMessagesLocal); } - }, [searchTextMessagesLocal, setLocalTextSearchQuery]); + }); - const handleUp = useCallback(() => { + const handleUp = useLastCallback(() => { if (chat && foundIds) { const newFocusIndex = focusedIndex + 1; focusMessage({ chatId: chat.id, messageId: foundIds[newFocusIndex], threadId }); setFocusedIndex(newFocusIndex); } - }, [chat, foundIds, focusedIndex, threadId]); + }); - const handleDown = useCallback(() => { + const handleDown = useLastCallback(() => { if (chat && foundIds) { const newFocusIndex = focusedIndex - 1; focusMessage({ chatId: chat.id, messageId: foundIds[newFocusIndex], threadId }); setFocusedIndex(newFocusIndex); } - }, [chat, foundIds, focusedIndex, threadId]); + }); - const handleCloseLocalTextSearch = useCallback(() => { + const handleCloseLocalTextSearch = useLastCallback(() => { closeLocalTextSearch(); - }, [closeLocalTextSearch]); + }); return (
diff --git a/src/components/middle/ReactorListModal.tsx b/src/components/middle/ReactorListModal.tsx index 1d3472f04..828c55463 100644 --- a/src/components/middle/ReactorListModal.tsx +++ b/src/components/middle/ReactorListModal.tsx @@ -1,6 +1,6 @@ import type { FC } from '../../lib/teact/teact'; import React, { - useCallback, memo, useMemo, useEffect, useState, useRef, + memo, useMemo, useEffect, useState, useRef, } from '../../lib/teact/teact'; import { getActions, getGlobal, withGlobal } from '../../global'; @@ -32,6 +32,7 @@ import FullNameTitle from '../common/FullNameTitle'; import PrivateChatInfo from '../common/PrivateChatInfo'; import './ReactorListModal.scss'; +import useLastCallback from '../../hooks/useLastCallback'; const MIN_REACTIONS_COUNT_FOR_FILTERS = 10; @@ -81,28 +82,28 @@ const ReactorListModal: FC = ({ } }, [isClosing, isOpen, stopClosing]); - const handleCloseAnimationEnd = useCallback(() => { + const handleCloseAnimationEnd = useLastCallback(() => { if (chatIdRef.current) { openChat({ id: chatIdRef.current }); } closeReactorListModal(); - }, [closeReactorListModal, openChat]); + }); - const handleClose = useCallback(() => { + const handleClose = useLastCallback(() => { startClosing(); - }, [startClosing]); + }); - const handleClick = useCallback((userId: string) => { + const handleClick = useLastCallback((userId: string) => { chatIdRef.current = userId; handleClose(); - }, [handleClose]); + }); - const handleLoadMore = useCallback(() => { + const handleLoadMore = useLastCallback(() => { loadReactors({ chatId: chatId!, messageId: messageId!, }); - }, [chatId, loadReactors, messageId]); + }); const allReactions = useMemo(() => { const uniqueReactions: ApiReaction[] = []; diff --git a/src/components/middle/composer/AttachBotItem.tsx b/src/components/middle/composer/AttachBotItem.tsx index e7137ad78..453626a60 100644 --- a/src/components/middle/composer/AttachBotItem.tsx +++ b/src/components/middle/composer/AttachBotItem.tsx @@ -1,12 +1,13 @@ import type { FC } from '../../../lib/teact/teact'; import React, { - memo, useCallback, useMemo, useState, + memo, useMemo, useState, } from '../../../lib/teact/teact'; import { getActions } from '../../../global'; import type { IAnchorPosition, ISettings } from '../../../types'; import type { ApiAttachBot } from '../../../api/types'; +import useLastCallback from '../../../hooks/useLastCallback'; import useFlag from '../../../hooks/useFlag'; import useLang from '../../../hooks/useLang'; @@ -43,29 +44,29 @@ const AttachBotItem: FC = ({ const [isMenuOpen, openMenu, closeMenu] = useFlag(); const [menuPosition, setMenuPosition] = useState(undefined); - const handleContextMenu = useCallback((e: React.UIEvent) => { + const handleContextMenu = useLastCallback((e: React.UIEvent) => { e.preventDefault(); const rect = e.currentTarget.getBoundingClientRect(); setMenuPosition({ x: rect.right, y: rect.bottom }); onMenuOpened(); openMenu(); - }, [onMenuOpened, openMenu]); + }); - const handleCloseMenu = useCallback(() => { + const handleCloseMenu = useLastCallback(() => { closeMenu(); onMenuClosed(); - }, [closeMenu, onMenuClosed]); + }); - const handleCloseAnimationEnd = useCallback(() => { + const handleCloseAnimationEnd = useLastCallback(() => { setMenuPosition(undefined); - }, []); + }); - const handleRemoveBot = useCallback(() => { + const handleRemoveBot = useLastCallback(() => { toggleAttachBot({ botId: bot.id, isEnabled: false, }); - }, [bot.id, toggleAttachBot]); + }); return ( = ({ } }, [isAttachMenuOpen, markMouseInside]); - const handleToggleAttachMenu = useCallback(() => { + const handleToggleAttachMenu = useLastCallback(() => { if (isAttachMenuOpen) { closeAttachMenu(); } else { openAttachMenu(); } - }, [isAttachMenuOpen, openAttachMenu, closeAttachMenu]); + }); - const handleFileSelect = useCallback((e: Event, shouldSuggestCompression?: boolean) => { + const handleFileSelect = useLastCallback((e: Event, shouldSuggestCompression?: boolean) => { const { files } = e.target as HTMLInputElement; const validatedFiles = validateFiles(files); if (validatedFiles?.length) { onFileSelect(validatedFiles, shouldSuggestCompression); } - }, [onFileSelect]); + }); - const handleQuickSelect = useCallback(() => { + const handleQuickSelect = useLastCallback(() => { openSystemFilesDialog( Array.from(canSendVideoAndPhoto ? CONTENT_TYPES_WITH_PREVIEW : ( canSendPhotos ? SUPPORTED_IMAGE_CONTENT_TYPES : SUPPORTED_VIDEO_CONTENT_TYPES )).join(','), (e) => handleFileSelect(e, true), ); - }, [canSendPhotos, canSendVideoAndPhoto, handleFileSelect]); + }); - const handleDocumentSelect = useCallback(() => { + const handleDocumentSelect = useLastCallback(() => { openSystemFilesDialog(!canSendDocuments && canSendAudios ? Array.from(SUPPORTED_AUDIO_CONTENT_TYPES).join(',') : ( '*' ), (e) => handleFileSelect(e, false)); - }, [canSendAudios, canSendDocuments, handleFileSelect]); + }); const bots = useMemo(() => { return Object.values(attachBots).filter((bot) => { diff --git a/src/components/middle/composer/AttachmentModal.tsx b/src/components/middle/composer/AttachmentModal.tsx index a914c1c3c..360f8cda9 100644 --- a/src/components/middle/composer/AttachmentModal.tsx +++ b/src/components/middle/composer/AttachmentModal.tsx @@ -1,5 +1,5 @@ import React, { - memo, useCallback, useEffect, useMemo, useRef, useState, + memo, useEffect, useMemo, useRef, useState, } from '../../../lib/teact/teact'; import { requestMutation } from '../../../lib/fasterdom/fasterdom'; import { getActions, withGlobal } from '../../../global'; @@ -30,6 +30,7 @@ import buildClassName from '../../../util/buildClassName'; import { validateFiles } from '../../../util/files'; import { removeAllSelections } from '../../../util/selection'; +import useLastCallback from '../../../hooks/useLastCallback'; import usePrevious from '../../../hooks/usePrevious'; import useMentionTooltip from './hooks/useMentionTooltip'; import useEmojiTooltip from './hooks/useEmojiTooltip'; @@ -253,7 +254,7 @@ const AttachmentModal: FC = ({ handleContextMenuHide, } = useContextMenuHandlers(mainButtonRef, !canShowCustomSendMenu || !isOpen); - const sendAttachments = useCallback((isSilent?: boolean, shouldSendScheduled?: boolean) => { + const sendAttachments = useLastCallback((isSilent?: boolean, shouldSendScheduled?: boolean) => { if (isOpen) { const send = (shouldSchedule || shouldSendScheduled) ? onSendScheduled : isSilent ? onSendSilent : onSend; @@ -263,22 +264,19 @@ const AttachmentModal: FC = ({ shouldSendGrouped, }); } - }, [ - isOpen, shouldSchedule, onSendScheduled, onSendSilent, onSend, isSendingCompressed, shouldSendGrouped, - updateAttachmentSettings, - ]); + }); - const handleSendSilent = useCallback(() => { + const handleSendSilent = useLastCallback(() => { sendAttachments(true); - }, [sendAttachments]); + }); - const handleSendClick = useCallback(() => { + const handleSendClick = useLastCallback(() => { sendAttachments(); - }, [sendAttachments]); + }); - const handleScheduleClick = useCallback(() => { + const handleScheduleClick = useLastCallback(() => { sendAttachments(false, true); - }, [sendAttachments]); + }); const handleDragLeave = (e: React.DragEvent) => { const { relatedTarget: toTarget, target: fromTarget } = e; @@ -300,7 +298,7 @@ const AttachmentModal: FC = ({ unmarkHovered(); }; - const handleFilesDrop = useCallback(async (e: React.DragEvent) => { + const handleFilesDrop = useLastCallback(async (e: React.DragEvent) => { e.preventDefault(); unmarkHovered(); @@ -310,7 +308,7 @@ const AttachmentModal: FC = ({ if (files?.length) { onFileAppend(files, isEverySpoiler); } - }, [isEverySpoiler, onFileAppend, unmarkHovered]); + }); function handleDragOver(e: React.MouseEvent) { e.preventDefault(); @@ -321,35 +319,35 @@ const AttachmentModal: FC = ({ } } - const handleFileSelect = useCallback((e: Event) => { + const handleFileSelect = useLastCallback((e: Event) => { const { files } = e.target as HTMLInputElement; const validatedFiles = validateFiles(files); if (validatedFiles?.length) { onFileAppend(validatedFiles, isEverySpoiler); } - }, [isEverySpoiler, onFileAppend]); + }); - const handleDocumentSelect = useCallback(() => { + const handleDocumentSelect = useLastCallback(() => { openSystemFilesDialog('*', (e) => handleFileSelect(e)); - }, [handleFileSelect]); + }); - const handleDelete = useCallback((index: number) => { + const handleDelete = useLastCallback((index: number) => { onAttachmentsUpdate(attachments.filter((a, i) => i !== index)); - }, [attachments, onAttachmentsUpdate]); + }); - const handleEnableSpoilers = useCallback(() => { + const handleEnableSpoilers = useLastCallback(() => { onAttachmentsUpdate(attachments.map((a) => ({ ...a, shouldSendAsSpoiler: a.mimeType !== GIF_MIME_TYPE ? true : undefined, }))); - }, [attachments, onAttachmentsUpdate]); + }); - const handleDisableSpoilers = useCallback(() => { + const handleDisableSpoilers = useLastCallback(() => { onAttachmentsUpdate(attachments.map((a) => ({ ...a, shouldSendAsSpoiler: undefined }))); - }, [attachments, onAttachmentsUpdate]); + }); - const handleToggleSpoiler = useCallback((index: number) => { + const handleToggleSpoiler = useLastCallback((index: number) => { onAttachmentsUpdate(attachments.map((attachment, i) => { if (i === index) { return { @@ -360,7 +358,7 @@ const AttachmentModal: FC = ({ return attachment; })); - }, [attachments, onAttachmentsUpdate]); + }); useEffect(() => { const mainButton = mainButtonRef.current; diff --git a/src/components/middle/composer/AttachmentModalItem.tsx b/src/components/middle/composer/AttachmentModalItem.tsx index 86f2b80f7..cbd0e6a98 100644 --- a/src/components/middle/composer/AttachmentModalItem.tsx +++ b/src/components/middle/composer/AttachmentModalItem.tsx @@ -1,4 +1,4 @@ -import React, { memo, useCallback, useMemo } from '../../../lib/teact/teact'; +import React, { memo, useMemo } from '../../../lib/teact/teact'; import type { FC } from '../../../lib/teact/teact'; import type { ApiAttachment } from '../../../api/types'; @@ -9,6 +9,8 @@ import buildClassName from '../../../util/buildClassName'; import { formatMediaDuration } from '../../../util/dateFormat'; import { REM } from '../../common/helpers/mediaDimensions'; +import useLastCallback from '../../../hooks/useLastCallback'; + import File from '../../common/File'; import MediaSpoiler from '../../common/MediaSpoiler'; @@ -39,9 +41,9 @@ const AttachmentModalItem: FC = ({ }) => { const displayType = getDisplayType(attachment, shouldDisplayCompressed); - const handleSpoilerClick = useCallback(() => { + const handleSpoilerClick = useLastCallback(() => { onToggleSpoiler?.(index); - }, [index, onToggleSpoiler]); + }); const content = useMemo(() => { switch (displayType) { diff --git a/src/components/middle/composer/BotCommandMenu.tsx b/src/components/middle/composer/BotCommandMenu.tsx index 3d8e0197e..8117beaa2 100644 --- a/src/components/middle/composer/BotCommandMenu.tsx +++ b/src/components/middle/composer/BotCommandMenu.tsx @@ -1,10 +1,12 @@ -import React, { memo, useCallback } from '../../../lib/teact/teact'; +import React, { memo } from '../../../lib/teact/teact'; import { getActions } from '../../../global'; import type { FC } from '../../../lib/teact/teact'; import type { ApiBotCommand } from '../../../api/types'; import { IS_TOUCH_ENV } from '../../../util/windowEnvironment'; + +import useLastCallback from '../../../hooks/useLastCallback'; import useMouseInside from '../../../hooks/useMouseInside'; import useAppLayout from '../../../hooks/useAppLayout'; @@ -27,12 +29,12 @@ const BotCommandMenu: FC = ({ const [handleMouseEnter, handleMouseLeave] = useMouseInside(isOpen, onClose, undefined, isMobile); - const handleClick = useCallback((botCommand: ApiBotCommand) => { + const handleClick = useLastCallback((botCommand: ApiBotCommand) => { sendBotCommand({ command: `/${botCommand.command}`, }); onClose(); - }, [onClose, sendBotCommand]); + }); return ( = ({ const containerRef = useRef(null); const { shouldRender, transitionClassNames } = useShowTransition(isOpen, undefined, undefined, false); - const handleSendCommand = useCallback(({ botId, command }: ApiBotCommand) => { + const handleSendCommand = useLastCallback(({ botId, command }: ApiBotCommand) => { // No need for expensive global updates on users and chats, so we avoid them const usersById = getGlobal().users.byId; const bot = usersById[botId]; @@ -49,9 +49,9 @@ const BotCommandTooltip: FC = ({ command: `/${command}${withUsername && bot ? `@${bot.usernames![0].username}` : ''}`, }); onClick(); - }, [onClick, sendBotCommand, withUsername]); + }); - const handleSelect = useCallback((botCommand: ApiBotCommand) => { + const handleSelect = useLastCallback((botCommand: ApiBotCommand) => { // We need an additional check because tooltip is updated with throttling if (!botCommand.command.startsWith(getHtml().slice(1))) { return false; @@ -59,7 +59,7 @@ const BotCommandTooltip: FC = ({ handleSendCommand(botCommand); return true; - }, [getHtml, handleSendCommand]); + }); const selectedCommandIndex = useKeyboardNavigation({ isActive: isOpen, diff --git a/src/components/middle/composer/Composer.tsx b/src/components/middle/composer/Composer.tsx index 263e45e46..45e866c65 100644 --- a/src/components/middle/composer/Composer.tsx +++ b/src/components/middle/composer/Composer.tsx @@ -1,6 +1,6 @@ import type { FC } from '../../../lib/teact/teact'; import React, { - memo, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, + memo, useEffect, useLayoutEffect, useMemo, useRef, useState, } from '../../../lib/teact/teact'; import { requestMeasure, requestNextMutation } from '../../../lib/fasterdom/fasterdom'; import { getActions, withGlobal } from '../../../global'; @@ -89,6 +89,7 @@ import { buildCustomEmojiHtml } from './helpers/customEmoji'; import { processMessageInputForCustomEmoji } from '../../../util/customEmojiManager'; import { getTextWithEntitiesAsHtml } from '../../common/helpers/renderTextWithEntities'; +import useLastCallback from '../../../hooks/useLastCallback'; import useSignal from '../../../hooks/useSignal'; import useFlag from '../../../hooks/useFlag'; import usePrevious from '../../../hooks/usePrevious'; @@ -335,12 +336,11 @@ const Composer: FC = ({ const customEmojiNotificationNumber = useRef(0); - const handleScheduleCancel = useCallback(() => { - cancelForceShowSymbolMenu(); - }, [cancelForceShowSymbolMenu]); - const [requestCalendar, calendar] = useSchedule(canScheduleUntilOnline, handleScheduleCancel); + const [requestCalendar, calendar] = useSchedule(canScheduleUntilOnline, cancelForceShowSymbolMenu); - useTimeout(() => { setIsMounted(true); }, MOUNT_ANIMATION_DURATION); + useTimeout(() => { + setIsMounted(true); + }, MOUNT_ANIMATION_DURATION); useEffect(() => { lastMessageSendTimeSeconds.current = undefined; @@ -524,7 +524,7 @@ const Composer: FC = ({ chatBotCommands, ); - const insertHtmlAndUpdateCursor = useCallback((newHtml: string, inputId: string = EDITABLE_INPUT_ID) => { + const insertHtmlAndUpdateCursor = useLastCallback((newHtml: string, inputId: string = EDITABLE_INPUT_ID) => { if (inputId === EDITABLE_INPUT_ID && isComposerBlocked) return; const selection = window.getSelection()!; let messageInput: HTMLDivElement; @@ -549,22 +549,22 @@ const Composer: FC = ({ requestNextMutation(() => { focusEditableElement(messageInput); }); - }, [isComposerBlocked, getHtml, setHtml]); + }); - const insertFormattedTextAndUpdateCursor = useCallback(( + const insertFormattedTextAndUpdateCursor = useLastCallback(( text: ApiFormattedText, inputId: string = EDITABLE_INPUT_ID, ) => { const newHtml = getTextWithEntitiesAsHtml(text); insertHtmlAndUpdateCursor(newHtml, inputId); - }, [insertHtmlAndUpdateCursor]); + }); - const insertCustomEmojiAndUpdateCursor = useCallback((emoji: ApiSticker, inputId: string = EDITABLE_INPUT_ID) => { + const insertCustomEmojiAndUpdateCursor = useLastCallback((emoji: ApiSticker, inputId: string = EDITABLE_INPUT_ID) => { insertHtmlAndUpdateCursor(buildCustomEmojiHtml(emoji), inputId); - }, [insertHtmlAndUpdateCursor]); + }); useDraft(draft, chatId, threadId, getHtml, setHtml, editingMessage, lastSyncTime); - const resetComposer = useCallback((shouldPreserveInput = false) => { + const resetComposer = useLastCallback((shouldPreserveInput = false) => { if (!shouldPreserveInput) { setHtml(''); } @@ -582,10 +582,7 @@ const Composer: FC = ({ } else { closeSymbolMenu(); } - }, [ - setHtml, isMobile, closeStickerTooltip, closeCustomEmojiTooltip, closeMentionTooltip, closeEmojiTooltip, - closeSymbolMenu, - ]); + }); const [handleEditComplete, handleEditCancel, shouldForceShowEditing] = useEditing( getHtml, @@ -613,7 +610,7 @@ const Composer: FC = ({ }; }, [chatId, threadId, resetComposerRef, stopRecordingVoiceRef]); - const showCustomEmojiPremiumNotification = useCallback(() => { + const showCustomEmojiPremiumNotification = useLastCallback(() => { const notificationNumber = customEmojiNotificationNumber.current; if (!notificationNumber) { showNotification({ @@ -635,7 +632,7 @@ const Composer: FC = ({ }); } customEmojiNotificationNumber.current = Number(!notificationNumber); - }, [currentUserId, lang, showNotification]); + }); const mainButtonState = useDerivedState(() => { if (editingMessage && shouldForceShowEditing) { @@ -672,13 +669,13 @@ const Composer: FC = ({ showCustomEmojiPremiumNotification, ); - const handleEmbeddedClear = useCallback(() => { + const handleEmbeddedClear = useLastCallback(() => { if (editingMessage) { handleEditCancel(); } - }, [editingMessage, handleEditCancel]); + }); - const validateTextLength = useCallback((text: string, isAttachmentModal?: boolean) => { + const validateTextLength = useLastCallback((text: string, isAttachmentModal?: boolean) => { const maxLength = isAttachmentModal ? captionLimit : MESSAGE_MAX_LENGTH; if (text?.length > maxLength) { const extraLength = text.length - maxLength; @@ -696,9 +693,9 @@ const Composer: FC = ({ return false; } return true; - }, [captionLimit, showDialog]); + }); - const checkSlowMode = useCallback(() => { + const checkSlowMode = useLastCallback(() => { if (slowMode && !isAdmin) { const messageInput = document.querySelector(EDITABLE_INPUT_CSS_SELECTOR); @@ -728,9 +725,9 @@ const Composer: FC = ({ } } return true; - }, [isAdmin, lang, showDialog, slowMode]); + }); - const sendAttachments = useCallback(({ + const sendAttachments = useLastCallback(({ attachments: attachmentsToSend, sendCompressed = attachmentSettings.shouldCompress, sendGrouped = attachmentSettings.shouldSendGrouped, @@ -772,12 +769,9 @@ const Composer: FC = ({ requestMeasure(() => { resetComposer(); }); - }, [ - attachmentSettings.shouldCompress, attachmentSettings.shouldSendGrouped, connectionState, getHtml, - validateTextLength, checkSlowMode, sendMessage, clearDraft, chatId, resetComposer, shouldUpdateStickerSetOrder, - ]); + }); - const handleSendAttachments = useCallback(( + const handleSendAttachments = useLastCallback(( sendCompressed: boolean, sendGrouped: boolean, isSilent?: boolean, @@ -790,9 +784,9 @@ const Composer: FC = ({ isSilent, scheduledAt, }); - }, [attachments, sendAttachments]); + }); - const handleSend = useCallback(async (isSilent = false, scheduledAt?: number) => { + const handleSend = useLastCallback(async (isSilent = false, scheduledAt?: number) => { if (connectionState !== 'connectionStateReady') { return; } @@ -859,13 +853,9 @@ const Composer: FC = ({ requestMeasure(() => { resetComposer(); }); - }, [ - connectionState, attachments, activeVoiceRecording, getHtml, isForwarding, validateTextLength, clearDraft, - chatId, stopRecordingVoice, sendAttachments, checkSlowMode, sendMessage, forwardMessages, resetComposer, - shouldUpdateStickerSetOrder, - ]); + }); - const handleClickBotMenu = useCallback(() => { + const handleClickBotMenu = useLastCallback(() => { if (botMenuButton?.type !== 'webApp') { return; } @@ -873,14 +863,14 @@ const Composer: FC = ({ callAttachBot({ chatId, url: botMenuButton.url, threadId, }); - }, [botMenuButton, callAttachBot, chatId, threadId]); + }); - const handleActivateBotCommandMenu = useCallback(() => { + const handleActivateBotCommandMenu = useLastCallback(() => { closeSymbolMenu(); openBotCommandMenu(); - }, [closeSymbolMenu, openBotCommandMenu]); + }); - const handleMessageSchedule = useCallback(( + const handleMessageSchedule = useLastCallback(( args: ScheduledMessageArgs, scheduledAt: number, ) => { if (args && 'queryId' in args) { @@ -907,7 +897,7 @@ const Composer: FC = ({ scheduledAt, }); } - }, [handleSendAttachments, handleSend, sendInlineBotResult, sendMessage]); + }); useEffectWithPrevDeps(([prevContentToBeScheduled]) => { if (contentToBeScheduled && contentToBeScheduled !== prevContentToBeScheduled) { @@ -936,20 +926,20 @@ const Composer: FC = ({ } }, [handleFileSelect, requestedDraftFiles, resetOpenChatWithDraft]); - const handleCustomEmojiSelect = useCallback((emoji: ApiSticker, inputId?: string) => { + const handleCustomEmojiSelect = useLastCallback((emoji: ApiSticker, inputId?: string) => { if (!emoji.isFree && !isCurrentUserPremium && !isChatWithSelf) { showCustomEmojiPremiumNotification(); return; } insertCustomEmojiAndUpdateCursor(emoji, inputId); - }, [insertCustomEmojiAndUpdateCursor, isChatWithSelf, isCurrentUserPremium, showCustomEmojiPremiumNotification]); + }); - const handleCustomEmojiSelectAttachmentModal = useCallback((emoji: ApiSticker) => { + const handleCustomEmojiSelectAttachmentModal = useLastCallback((emoji: ApiSticker) => { handleCustomEmojiSelect(emoji, EDITABLE_INPUT_MODAL_ID); - }, [handleCustomEmojiSelect]); + }); - const handleGifSelect = useCallback((gif: ApiVideo, isSilent?: boolean, isScheduleRequested?: boolean) => { + const handleGifSelect = useLastCallback((gif: ApiVideo, isSilent?: boolean, isScheduleRequested?: boolean) => { if (shouldSchedule || isScheduleRequested) { forceShowSymbolMenu(); requestCalendar((scheduledAt) => { @@ -965,12 +955,9 @@ const Composer: FC = ({ resetComposer(true); }); } - }, [ - shouldSchedule, forceShowSymbolMenu, requestCalendar, cancelForceShowSymbolMenu, handleMessageSchedule, - resetComposer, sendMessage, - ]); + }); - const handleStickerSelect = useCallback(( + const handleStickerSelect = useLastCallback(( sticker: ApiSticker, isSilent?: boolean, isScheduleRequested?: boolean, @@ -1001,12 +988,9 @@ const Composer: FC = ({ resetComposer(shouldPreserveInput); }); } - }, [ - shouldSchedule, forceShowSymbolMenu, requestCalendar, cancelForceShowSymbolMenu, handleMessageSchedule, - resetComposer, sendMessage, shouldUpdateStickerSetOrder, - ]); + }); - const handleInlineBotSelect = useCallback(( + const handleInlineBotSelect = useLastCallback(( inlineResult: ApiBotInlineResult | ApiBotInlineMediaResult, isSilent?: boolean, isScheduleRequested?: boolean, ) => { if (connectionState !== 'connectionStateReady') { @@ -1038,19 +1022,16 @@ const Composer: FC = ({ requestMeasure(() => { resetComposer(); }); - }, [ - chatId, clearDraft, connectionState, handleMessageSchedule, requestCalendar, resetComposer, sendInlineBotResult, - shouldSchedule, - ]); + }); - const handleBotCommandSelect = useCallback(() => { + const handleBotCommandSelect = useLastCallback(() => { clearDraft({ chatId, localOnly: true }); requestMeasure(() => { resetComposer(); }); - }, [chatId, clearDraft, resetComposer]); + }); - const handlePollSend = useCallback((poll: ApiNewPoll) => { + const handlePollSend = useLastCallback((poll: ApiNewPoll) => { if (shouldSchedule) { requestCalendar((scheduledAt) => { handleMessageSchedule({ poll }, scheduledAt); @@ -1060,9 +1041,9 @@ const Composer: FC = ({ sendMessage({ poll }); closePollModal(); } - }, [closePollModal, handleMessageSchedule, requestCalendar, sendMessage, shouldSchedule]); + }); - const sendSilent = useCallback((additionalArgs?: ScheduledMessageArgs) => { + const sendSilent = useLastCallback((additionalArgs?: ScheduledMessageArgs) => { if (shouldSchedule) { requestCalendar((scheduledAt) => { handleMessageSchedule({ ...additionalArgs, isSilent: true }, scheduledAt); @@ -1073,9 +1054,9 @@ const Composer: FC = ({ } else { void handleSend(true); } - }, [handleMessageSchedule, handleSend, handleSendAttachments, requestCalendar, shouldSchedule]); + }); - const handleSendAsMenuOpen = useCallback(() => { + const handleSendAsMenuOpen = useLastCallback(() => { const messageInput = document.querySelector(EDITABLE_INPUT_CSS_SELECTOR); if (!isMobile || messageInput !== document.activeElement) { @@ -1091,14 +1072,14 @@ const Composer: FC = ({ closeSymbolMenu(); openSendAsMenu(); }, MOBILE_KEYBOARD_HIDE_DELAY_MS); - }, [closeBotCommandMenu, closeSymbolMenu, openSendAsMenu, isMobile]); + }); - const insertTextAndUpdateCursor = useCallback((text: string, inputId: string = EDITABLE_INPUT_ID) => { + const insertTextAndUpdateCursor = useLastCallback((text: string, inputId: string = EDITABLE_INPUT_ID) => { const newHtml = renderText(text, ['escape_html', 'emoji_html', 'br_html']) .join('') .replace(/\u200b+/g, '\u200b'); insertHtmlAndUpdateCursor(newHtml, inputId); - }, [insertHtmlAndUpdateCursor]); + }); useEffect(() => { if (!isComposerBlocked) return; @@ -1106,11 +1087,11 @@ const Composer: FC = ({ setHtml(''); }, [isComposerBlocked, setHtml, attachments]); - const insertTextAndUpdateCursorAttachmentModal = useCallback((text: string) => { + const insertTextAndUpdateCursorAttachmentModal = useLastCallback((text: string) => { insertTextAndUpdateCursor(text, EDITABLE_INPUT_MODAL_ID); - }, [insertTextAndUpdateCursor]); + }); - const removeSymbol = useCallback((inputId = EDITABLE_INPUT_ID) => { + const removeSymbol = useLastCallback((inputId = EDITABLE_INPUT_ID) => { const selection = window.getSelection()!; if (selection.rangeCount) { @@ -1122,17 +1103,17 @@ const Composer: FC = ({ } setHtml(deleteLastCharacterOutsideSelection(getHtml())); - }, [getHtml, setHtml]); + }); - const removeSymbolAttachmentModal = useCallback(() => { + const removeSymbolAttachmentModal = useLastCallback(() => { removeSymbol(EDITABLE_INPUT_MODAL_ID); - }, [removeSymbol]); + }); - const handleAllScheduledClick = useCallback(() => { + const handleAllScheduledClick = useLastCallback(() => { openChat({ id: chatId, threadId, type: 'scheduled', noForumTopicPanel: true, }); - }, [openChat, chatId, threadId]); + }); useEffect(() => { if (isRightColumnShown && isMobile) { @@ -1155,7 +1136,7 @@ const Composer: FC = ({ const areVoiceMessagesNotAllowed = mainButtonState === MainButtonState.Record && (!canAttachMedia || !canSendVoiceByPrivacy || !canSendVoices); - const mainButtonHandler = useCallback(() => { + const mainButtonHandler = useLastCallback(() => { switch (mainButtonState) { case MainButtonState.Send: void handleSend(); @@ -1188,11 +1169,7 @@ const Composer: FC = ({ default: break; } - }, [ - mainButtonState, handleSend, handleEditComplete, activeVoiceRecording, requestCalendar, areVoiceMessagesNotAllowed, - canSendVoiceByPrivacy, showNotification, lang, chat?.title, startRecordingVoice, pauseRecordingVoice, - handleMessageSchedule, chatId, showAllowedMessageTypesNotification, canSendVoices, - ]); + }); const prevEditedMessage = usePrevious(editingMessage, true); const renderedEditedMessage = editingMessage || prevEditedMessage; @@ -1222,29 +1199,29 @@ const Composer: FC = ({ isMounted && 'mounted', ); - const handleSendScheduled = useCallback(() => { + const handleSendScheduled = useLastCallback(() => { requestCalendar((scheduledAt) => { handleMessageSchedule({}, scheduledAt); }); - }, [handleMessageSchedule, requestCalendar]); + }); - const handleSendSilent = useCallback(() => { + const handleSendSilent = useLastCallback(() => { sendSilent(); - }, [sendSilent]); + }); - const handleSendWhenOnline = useCallback(() => { - handleMessageSchedule({ }, SCHEDULED_WHEN_ONLINE); - }, [handleMessageSchedule]); + const handleSendWhenOnline = useLastCallback(() => { + handleMessageSchedule({}, SCHEDULED_WHEN_ONLINE); + }); - const handleSendScheduledAttachments = useCallback((sendCompressed: boolean, sendGrouped: boolean) => { + const handleSendScheduledAttachments = useLastCallback((sendCompressed: boolean, sendGrouped: boolean) => { requestCalendar((scheduledAt) => { handleMessageSchedule({ sendCompressed, sendGrouped }, scheduledAt); }); - }, [handleMessageSchedule, requestCalendar]); + }); - const handleSendSilentAttachments = useCallback((sendCompressed: boolean, sendGrouped: boolean) => { + const handleSendSilentAttachments = useLastCallback((sendCompressed: boolean, sendGrouped: boolean) => { sendSilent({ sendCompressed, sendGrouped }); - }, [sendSilent]); + }); const onSend = mainButtonState === MainButtonState.Edit ? handleEditComplete diff --git a/src/components/middle/composer/ComposerEmbeddedMessage.tsx b/src/components/middle/composer/ComposerEmbeddedMessage.tsx index 1f2a6c7e2..e0775221c 100644 --- a/src/components/middle/composer/ComposerEmbeddedMessage.tsx +++ b/src/components/middle/composer/ComposerEmbeddedMessage.tsx @@ -1,5 +1,5 @@ import React, { - memo, useCallback, useEffect, useMemo, useRef, + memo, useEffect, useMemo, useRef, } from '../../../lib/teact/teact'; import { getActions, withGlobal } from '../../../global'; @@ -26,6 +26,7 @@ import captureEscKeyListener from '../../../util/captureEscKeyListener'; import buildClassName from '../../../util/buildClassName'; import { isUserId, stripCustomEmoji } from '../../../global/helpers'; +import useLastCallback from '../../../hooks/useLastCallback'; import useAsyncRendering from '../../right/hooks/useAsyncRendering'; import useShowTransition from '../../../hooks/useShowTransition'; import useLang from '../../../hooks/useLang'; @@ -101,7 +102,7 @@ const ComposerEmbeddedMessage: FC = ({ shouldRender, transitionClassNames, } = useShowTransition(canAnimate && isShown, undefined, !shouldAnimate, undefined, !shouldAnimate); - const clearEmbedded = useCallback(() => { + const clearEmbedded = useLastCallback(() => { if (replyingToId && !shouldForceShowEditing) { setReplyingToId({ messageId: undefined }); } else if (editingId) { @@ -110,35 +111,32 @@ const ComposerEmbeddedMessage: FC = ({ exitForwardMode(); } onClear?.(); - }, [ - replyingToId, shouldForceShowEditing, editingId, forwardedMessagesCount, onClear, setReplyingToId, setEditingId, - exitForwardMode, - ]); + }); useEffect(() => (isShown ? captureEscKeyListener(clearEmbedded) : undefined), [isShown, clearEmbedded]); - const handleMessageClick = useCallback((): void => { + const handleMessageClick = useLastCallback((): void => { if (isForwarding) return; focusMessage({ chatId: message!.chatId, messageId: message!.id, noForumTopicPanel: true }); - }, [focusMessage, isForwarding, message]); + }); - const handleClearClick = useCallback((e: React.MouseEvent): void => { + const handleClearClick = useLastCallback((e: React.MouseEvent): void => { e.stopPropagation(); clearEmbedded(); - }, [clearEmbedded]); + }); - const handleChangeRecipientClick = useCallback(() => { + const handleChangeRecipientClick = useLastCallback(() => { changeForwardRecipient(); - }, [changeForwardRecipient]); + }); const { isContextMenuOpen, contextMenuPosition, handleContextMenu, handleContextMenuClose, handleContextMenuHide, } = useContextMenuHandlers(ref); - const getTriggerElement = useCallback(() => ref.current, []); - const getRootElement = useCallback(() => ref.current!, []); - const getMenuElement = useCallback(() => ref.current!.querySelector('.forward-context-menu .bubble'), []); + const getTriggerElement = useLastCallback(() => ref.current); + const getRootElement = useLastCallback(() => ref.current!); + const getMenuElement = useLastCallback(() => ref.current!.querySelector('.forward-context-menu .bubble')); const { positionX, positionY, transformOriginX, transformOriginY, style: menuStyle, diff --git a/src/components/middle/composer/CustomEmojiButton.tsx b/src/components/middle/composer/CustomEmojiButton.tsx index 290caa4c4..8330e4372 100644 --- a/src/components/middle/composer/CustomEmojiButton.tsx +++ b/src/components/middle/composer/CustomEmojiButton.tsx @@ -1,4 +1,4 @@ -import React, { memo, useCallback } from '../../../lib/teact/teact'; +import React, { memo } from '../../../lib/teact/teact'; import type { FC } from '../../../lib/teact/teact'; import type { ApiSticker } from '../../../api/types'; @@ -6,6 +6,8 @@ import type { ObserveFn } from '../../../hooks/useIntersectionObserver'; import buildClassName from '../../../util/buildClassName'; +import useLastCallback from '../../../hooks/useLastCallback'; + import CustomEmoji from '../../common/CustomEmoji'; import './EmojiButton.scss'; @@ -22,12 +24,12 @@ type OwnProps = { const CustomEmojiButton: FC = ({ emoji, focus, onClick, observeIntersection, }) => { - const handleClick = useCallback((e: React.MouseEvent) => { + const handleClick = useLastCallback((e: React.MouseEvent) => { // Preventing safari from losing focus on Composer MessageInput e.preventDefault(); onClick?.(emoji); - }, [emoji, onClick]); + }); const className = buildClassName( 'EmojiButton', diff --git a/src/components/middle/composer/CustomEmojiTooltip.tsx b/src/components/middle/composer/CustomEmojiTooltip.tsx index 355ef56ce..35c2a2043 100644 --- a/src/components/middle/composer/CustomEmojiTooltip.tsx +++ b/src/components/middle/composer/CustomEmojiTooltip.tsx @@ -1,6 +1,4 @@ -import React, { - memo, useCallback, useEffect, useRef, -} from '../../../lib/teact/teact'; +import React, { memo, useEffect, useRef } from '../../../lib/teact/teact'; import { getActions, withGlobal } from '../../../global'; import type { FC } from '../../../lib/teact/teact'; @@ -12,6 +10,7 @@ import { selectIsChatWithSelf, selectIsCurrentUserPremium } from '../../../globa import buildClassName from '../../../util/buildClassName'; import captureEscKeyListener from '../../../util/captureEscKeyListener'; +import useLastCallback from '../../../hooks/useLastCallback'; import { useIntersectionObserver } from '../../../hooks/useIntersectionObserver'; import useShowTransition from '../../../hooks/useShowTransition'; import usePrevious from '../../../hooks/usePrevious'; @@ -65,14 +64,14 @@ const CustomEmojiTooltip: FC = ({ useEffect(() => (isOpen ? captureEscKeyListener(onClose) : undefined), [isOpen, onClose]); - const handleCustomEmojiSelect = useCallback((ce: ApiSticker) => { + const handleCustomEmojiSelect = useLastCallback((ce: ApiSticker) => { if (!isOpen) return; onCustomEmojiSelect(ce); addRecentCustomEmoji({ documentId: ce.id, }); clearCustomEmojiForEmoji(); - }, [addRecentCustomEmoji, clearCustomEmojiForEmoji, isOpen, onCustomEmojiSelect]); + }); const className = buildClassName( styles.root, diff --git a/src/components/middle/composer/DropArea.tsx b/src/components/middle/composer/DropArea.tsx index c431158cd..95656e3cb 100644 --- a/src/components/middle/composer/DropArea.tsx +++ b/src/components/middle/composer/DropArea.tsx @@ -1,13 +1,13 @@ import type { FC } from '../../../lib/teact/teact'; -import React, { - memo, useCallback, useEffect, useRef, -} from '../../../lib/teact/teact'; +import React, { memo, useEffect, useRef } from '../../../lib/teact/teact'; import useShowTransition from '../../../hooks/useShowTransition'; import buildClassName from '../../../util/buildClassName'; import getFilesFromDataTransferItems from './helpers/getFilesFromDataTransferItems'; import captureEscKeyListener from '../../../util/captureEscKeyListener'; + +import useLastCallback from '../../../hooks/useLastCallback'; import usePrevious from '../../../hooks/usePrevious'; import Portal from '../../ui/Portal'; @@ -40,7 +40,7 @@ const DropArea: FC = ({ useEffect(() => (isOpen ? captureEscKeyListener(onHide) : undefined), [isOpen, onHide]); - const handleFilesDrop = useCallback(async (e: React.DragEvent) => { + const handleFilesDrop = useLastCallback(async (e: React.DragEvent) => { const { dataTransfer: dt } = e; let files: File[] = []; @@ -55,18 +55,18 @@ const DropArea: FC = ({ onHide(); onFileSelect(files, withQuick ? false : undefined); - }, [onFileSelect, onHide, withQuick]); + }); - const handleQuickFilesDrop = useCallback((e: React.DragEvent) => { + const handleQuickFilesDrop = useLastCallback((e: React.DragEvent) => { const { dataTransfer: dt } = e; if (dt.files && dt.files.length > 0) { onHide(); onFileSelect(Array.from(dt.files), true); } - }, [onFileSelect, onHide]); + }); - const handleDragLeave = useCallback((e: React.DragEvent) => { + const handleDragLeave = useLastCallback((e: React.DragEvent) => { e.stopPropagation(); const { target: fromTarget, relatedTarget: toTarget } = e; @@ -77,7 +77,7 @@ const DropArea: FC = ({ onHide(); }, DROP_LEAVE_TIMEOUT_MS); } - }, [onHide]); + }); const handleDragOver = () => { if (hideTimeoutRef.current) { diff --git a/src/components/middle/composer/EmojiButton.tsx b/src/components/middle/composer/EmojiButton.tsx index 69987c347..f03a10a9c 100644 --- a/src/components/middle/composer/EmojiButton.tsx +++ b/src/components/middle/composer/EmojiButton.tsx @@ -1,4 +1,4 @@ -import React, { memo, useCallback } from '../../../lib/teact/teact'; +import React, { memo } from '../../../lib/teact/teact'; import type { FC } from '../../../lib/teact/teact'; @@ -7,6 +7,8 @@ import { IS_EMOJI_SUPPORTED } from '../../../util/windowEnvironment'; import { handleEmojiLoad, LOADED_EMOJIS } from '../../../util/emoji'; import buildClassName from '../../../util/buildClassName'; +import useLastCallback from '../../../hooks/useLastCallback'; + import './EmojiButton.scss'; type OwnProps = { @@ -18,12 +20,12 @@ type OwnProps = { const EmojiButton: FC = ({ emoji, focus, onClick, }) => { - const handleClick = useCallback((e: React.MouseEvent) => { + const handleClick = useLastCallback((e: React.MouseEvent) => { // Preventing safari from losing focus on Composer MessageInput e.preventDefault(); onClick(emoji.native, emoji.id); - }, [emoji, onClick]); + }); const className = buildClassName( 'EmojiButton', diff --git a/src/components/middle/composer/EmojiPicker.tsx b/src/components/middle/composer/EmojiPicker.tsx index d1d4d3c29..ae6ab1a72 100644 --- a/src/components/middle/composer/EmojiPicker.tsx +++ b/src/components/middle/composer/EmojiPicker.tsx @@ -1,5 +1,5 @@ import React, { - useState, useEffect, memo, useRef, useMemo, useCallback, + useState, useEffect, memo, useRef, useMemo, } from '../../../lib/teact/teact'; import { withGlobal } from '../../../global'; @@ -20,6 +20,8 @@ import animateScroll from '../../../util/animateScroll'; import { pick } from '../../../util/iteratees'; import buildClassName from '../../../util/buildClassName'; import animateHorizontalScroll from '../../../util/animateHorizontalScroll'; + +import useLastCallback from '../../../hooks/useLastCallback'; import useAsyncRendering from '../../right/hooks/useAsyncRendering'; import { useIntersectionObserver } from '../../../hooks/useIntersectionObserver'; import useHorizontalScroll from '../../../hooks/useHorizontalScroll'; @@ -167,16 +169,16 @@ const EmojiPicker: FC = ({ }, OPEN_ANIMATION_DELAY); }, []); - const selectCategory = useCallback((index: number) => { + const selectCategory = useLastCallback((index: number) => { setActiveCategoryIndex(index); const categoryEl = containerRef.current!.closest('.SymbolMenu-main')! .querySelector(`#emoji-category-${index}`)! as HTMLElement; animateScroll(containerRef.current!, categoryEl, 'start', FOCUS_MARGIN, SMOOTH_SCROLL_DISTANCE); - }, []); + }); - const handleEmojiSelect = useCallback((emoji: string, name: string) => { + const handleEmojiSelect = useLastCallback((emoji: string, name: string) => { onEmojiSelect(emoji, name); - }, [onEmojiSelect]); + }); function renderCategoryButton(category: EmojiCategoryData, index: number) { const icon = ICONS_BY_CATEGORY[category.id]; diff --git a/src/components/middle/composer/EmojiTooltip.tsx b/src/components/middle/composer/EmojiTooltip.tsx index 3fc6dc32b..840f03f4c 100644 --- a/src/components/middle/composer/EmojiTooltip.tsx +++ b/src/components/middle/composer/EmojiTooltip.tsx @@ -1,6 +1,4 @@ -import React, { - memo, useCallback, useRef, -} from '../../../lib/teact/teact'; +import React, { memo, useRef } from '../../../lib/teact/teact'; import type { ApiSticker } from '../../../api/types'; import type { FC } from '../../../lib/teact/teact'; @@ -10,6 +8,7 @@ import findInViewport from '../../../util/findInViewport'; import isFullyVisible from '../../../util/isFullyVisible'; import animateHorizontalScroll from '../../../util/animateHorizontalScroll'; +import useLastCallback from '../../../hooks/useLastCallback'; import useEffectWithPrevDeps from '../../../hooks/useEffectWithPrevDeps'; import useShowTransition from '../../../hooks/useShowTransition'; import usePrevDuringAnimation from '../../../hooks/usePrevDuringAnimation'; @@ -91,33 +90,33 @@ const EmojiTooltip: FC = ({ observe: observeIntersection, } = useIntersectionObserver({ rootRef: containerRef, throttleMs: INTERSECTION_THROTTLE, isDisabled: !isOpen }); - const handleSelectEmoji = useCallback((emoji: Emoji) => { + const handleSelectEmoji = useLastCallback((emoji: Emoji) => { onEmojiSelect(emoji.native); addRecentEmoji({ emoji: emoji.id }); - }, [addRecentEmoji, onEmojiSelect]); + }); - const handleCustomEmojiSelect = useCallback((emoji: ApiSticker) => { + const handleCustomEmojiSelect = useLastCallback((emoji: ApiSticker) => { onCustomEmojiSelect(emoji); addRecentCustomEmoji({ documentId: emoji.id }); - }, [addRecentCustomEmoji, onCustomEmojiSelect]); + }); - const handleSelect = useCallback((emoji: Emoji | ApiSticker) => { + const handleSelect = useLastCallback((emoji: Emoji | ApiSticker) => { if ('native' in emoji) { handleSelectEmoji(emoji); } else { handleCustomEmojiSelect(emoji); } - }, [handleCustomEmojiSelect, handleSelectEmoji]); + }); - const handleClick = useCallback((native: string, id: string) => { + const handleClick = useLastCallback((native: string, id: string) => { onEmojiSelect(native); addRecentEmoji({ emoji: id }); - }, [addRecentEmoji, onEmojiSelect]); + }); - const handleCustomEmojiClick = useCallback((emoji: ApiSticker) => { + const handleCustomEmojiClick = useLastCallback((emoji: ApiSticker) => { onCustomEmojiSelect(emoji); addRecentCustomEmoji({ documentId: emoji.id }); - }, [addRecentCustomEmoji, onCustomEmojiSelect]); + }); const selectedIndex = useKeyboardNavigation({ isActive: isOpen, diff --git a/src/components/middle/composer/GifPicker.tsx b/src/components/middle/composer/GifPicker.tsx index b6d6bbef1..b50d9e9c4 100644 --- a/src/components/middle/composer/GifPicker.tsx +++ b/src/components/middle/composer/GifPicker.tsx @@ -1,6 +1,6 @@ import type { FC } from '../../../lib/teact/teact'; import React, { - useEffect, memo, useRef, useCallback, + useEffect, memo, useRef, } from '../../../lib/teact/teact'; import { getActions, withGlobal } from '../../../global'; @@ -11,6 +11,7 @@ import { IS_TOUCH_ENV } from '../../../util/windowEnvironment'; import buildClassName from '../../../util/buildClassName'; import { selectCurrentMessageList, selectIsChatWithSelf } from '../../../global/selectors'; +import useLastCallback from '../../../hooks/useLastCallback'; import { useIntersectionObserver } from '../../../hooks/useIntersectionObserver'; import useAsyncRendering from '../../right/hooks/useAsyncRendering'; @@ -56,9 +57,9 @@ const GifPicker: FC = ({ } }, [loadAndPlay, loadSavedGifs]); - const handleUnsaveClick = useCallback((gif: ApiVideo) => { + const handleUnsaveClick = useLastCallback((gif: ApiVideo) => { saveGif({ gif, shouldUnsave: true }); - }, [saveGif]); + }); const canRenderContents = useAsyncRendering([], SLIDE_TRANSITION_DURATION); diff --git a/src/components/middle/composer/InlineBotTooltip.tsx b/src/components/middle/composer/InlineBotTooltip.tsx index 3eadf87a8..dfffcd887 100644 --- a/src/components/middle/composer/InlineBotTooltip.tsx +++ b/src/components/middle/composer/InlineBotTooltip.tsx @@ -1,6 +1,4 @@ -import React, { - memo, useCallback, useEffect, useRef, -} from '../../../lib/teact/teact'; +import React, { memo, useEffect, useRef } from '../../../lib/teact/teact'; import { getActions } from '../../../global'; import type { FC } from '../../../lib/teact/teact'; @@ -15,6 +13,7 @@ import buildClassName from '../../../util/buildClassName'; import { extractCurrentThemeParams } from '../../../util/themeStyle'; import { throttle } from '../../../util/schedulers'; +import useLastCallback from '../../../hooks/useLastCallback'; import useShowTransition from '../../../hooks/useShowTransition'; import { useIntersectionObserver } from '../../../hooks/useIntersectionObserver'; import usePrevious from '../../../hooks/usePrevious'; @@ -43,7 +42,7 @@ export type OwnProps = { isSavedMessages?: boolean; canSendGifs?: boolean; onSelectResult: ( - inlineResult: ApiBotInlineMediaResult | ApiBotInlineResult, isSilent?: boolean, shouldSchedule?: boolean + inlineResult: ApiBotInlineMediaResult | ApiBotInlineResult, isSilent?: boolean, shouldSchedule?: boolean, ) => void; loadMore: NoneToVoidFunction; onClose: NoneToVoidFunction; @@ -82,11 +81,11 @@ const InlineBotTooltip: FC = ({ isDisabled: !isOpen, }); - const handleLoadMore = useCallback(({ direction }: { direction: LoadMoreDirection }) => { + const handleLoadMore = useLastCallback(({ direction }: { direction: LoadMoreDirection }) => { if (direction === LoadMoreDirection.Backwards) { runThrottled(loadMore); } - }, [loadMore]); + }); const selectedIndex = useKeyboardNavigation({ isActive: isOpen, @@ -101,12 +100,12 @@ const InlineBotTooltip: FC = ({ setTooltipItemVisible('.chat-item-clickable', selectedIndex, containerRef); }, [selectedIndex]); - const handleSendPm = useCallback(() => { + const handleSendPm = useLastCallback(() => { openChat({ id: botId }); startBot({ botId: botId!, param: switchPm!.startParam }); - }, [botId, openChat, startBot, switchPm]); + }); - const handleOpenWebview = useCallback(() => { + const handleOpenWebview = useLastCallback(() => { const theme = extractCurrentThemeParams(); requestSimpleWebView({ @@ -115,7 +114,7 @@ const InlineBotTooltip: FC = ({ buttonText: switchWebview!.text, theme, }); - }, [botId, switchWebview]); + }); const prevInlineBotResults = usePrevious( inlineBotResults?.length diff --git a/src/components/middle/composer/MentionTooltip.tsx b/src/components/middle/composer/MentionTooltip.tsx index 7b65bb29e..0f88c0f3f 100644 --- a/src/components/middle/composer/MentionTooltip.tsx +++ b/src/components/middle/composer/MentionTooltip.tsx @@ -1,13 +1,13 @@ import type { FC } from '../../../lib/teact/teact'; -import React, { - useCallback, useEffect, useRef, memo, -} from '../../../lib/teact/teact'; +import React, { useEffect, useRef, memo } from '../../../lib/teact/teact'; import { getGlobal } from '../../../global'; import type { ApiUser } from '../../../api/types'; import buildClassName from '../../../util/buildClassName'; import setTooltipItemVisible from '../../../util/setTooltipItemVisible'; + +import useLastCallback from '../../../hooks/useLastCallback'; import usePrevious from '../../../hooks/usePrevious'; import useShowTransition from '../../../hooks/useShowTransition'; import { useKeyboardNavigation } from './hooks/useKeyboardNavigation'; @@ -34,7 +34,7 @@ const MentionTooltip: FC = ({ const containerRef = useRef(null); const { shouldRender, transitionClassNames } = useShowTransition(isOpen, undefined, undefined, false); - const handleUserSelect = useCallback((userId: string, forceFocus = false) => { + const handleUserSelect = useLastCallback((userId: string, forceFocus = false) => { // No need for expensive global updates on users, so we avoid them const usersById = getGlobal().users.byId; const user = usersById[userId]; @@ -43,17 +43,17 @@ const MentionTooltip: FC = ({ } onInsertUserName(user, forceFocus); - }, [onInsertUserName]); + }); - const handleClick = useCallback((e: React.MouseEvent, id: string) => { + const handleClick = useLastCallback((e: React.MouseEvent, id: string) => { e.preventDefault(); handleUserSelect(id); - }, [handleUserSelect]); + }); - const handleSelectMention = useCallback((member: ApiUser) => { + const handleSelectMention = useLastCallback((member: ApiUser) => { handleUserSelect(member.id, true); - }, [handleUserSelect]); + }); const selectedMentionIndex = useKeyboardNavigation({ isActive: isOpen, diff --git a/src/components/middle/composer/MessageInput.tsx b/src/components/middle/composer/MessageInput.tsx index f1302a6b2..18dbf8030 100644 --- a/src/components/middle/composer/MessageInput.tsx +++ b/src/components/middle/composer/MessageInput.tsx @@ -1,7 +1,7 @@ import type { RefObject, ChangeEvent } from 'react'; import type { FC } from '../../../lib/teact/teact'; import React, { - useEffect, useRef, memo, useState, useCallback, useLayoutEffect, + useEffect, useRef, memo, useState, useLayoutEffect, } from '../../../lib/teact/teact'; import { requestMutation, requestForcedReflow } from '../../../lib/fasterdom/fasterdom'; import { getActions, withGlobal } from '../../../global'; @@ -23,6 +23,7 @@ import parseEmojiOnlyString from '../../../util/parseEmojiOnlyString'; import { isSelectionInsideInput } from './helpers/selection'; import renderText from '../../common/helpers/renderText'; +import useLastCallback from '../../../hooks/useLastCallback'; import useFlag from '../../../hooks/useFlag'; import { isHeavyAnimating } from '../../../hooks/useHeavyAnimationCheck'; import useLang from '../../../hooks/useLang'; @@ -167,7 +168,7 @@ const MessageInput: FC = ({ ); const maxInputHeight = isAttachmentModalInput ? MAX_ATTACHMENT_MODAL_INPUT_HEIGHT : (isMobile ? 256 : 416); - const updateInputHeight = useCallback((willSend = false) => { + const updateInputHeight = useLastCallback((willSend = false) => { requestForcedReflow(() => { const scroller = inputRef.current!.closest(`.${SCROLLER_CLASS}`)!; const currentHeight = Number(scroller.style.height.replace('px', '')); @@ -198,7 +199,7 @@ const MessageInput: FC = ({ return exec; } }); - }, [maxInputHeight]); + }); useLayoutEffect(() => { if (!isAttachmentModalInput) return; @@ -226,7 +227,7 @@ const MessageInput: FC = ({ const chatIdRef = useRef(chatId); chatIdRef.current = chatId; - const focusInput = useCallback(() => { + const focusInput = useLastCallback(() => { if (!inputRef.current) { return; } @@ -237,12 +238,12 @@ const MessageInput: FC = ({ } focusEditableElement(inputRef.current!); - }, []); + }); - const handleCloseTextFormatter = useCallback(() => { + const handleCloseTextFormatter = useLastCallback(() => { closeTextFormatter(); clearSelection(); - }, [closeTextFormatter]); + }); function checkSelection() { // Disable the formatter on iOS devices for now. diff --git a/src/components/middle/composer/PollModal.tsx b/src/components/middle/composer/PollModal.tsx index 7e6787aca..a013f8cf5 100644 --- a/src/components/middle/composer/PollModal.tsx +++ b/src/components/middle/composer/PollModal.tsx @@ -1,7 +1,7 @@ import type { ChangeEvent, RefObject } from 'react'; import type { FC } from '../../../lib/teact/teact'; import React, { - memo, useCallback, useEffect, useLayoutEffect, useRef, useState, + memo, useEffect, useLayoutEffect, useRef, useState, } from '../../../lib/teact/teact'; import { requestNextMutation } from '../../../lib/fasterdom/fasterdom'; @@ -9,6 +9,8 @@ import type { ApiNewPoll } from '../../../api/types'; import captureEscKeyListener from '../../../util/captureEscKeyListener'; import parseMessageInput from '../../../util/parseMessageInput'; + +import useLastCallback from '../../../hooks/useLastCallback'; import useLang from '../../../hooks/useLang'; import Button from '../../ui/Button'; @@ -54,11 +56,11 @@ const PollModal: FC = ({ const lang = useLang(); - const focusInput = useCallback((ref: RefObject) => { + const focusInput = useLastCallback((ref: RefObject) => { if (isOpen && ref.current) { ref.current.focus(); } - }, [isOpen]); + }); useEffect(() => (isOpen ? captureEscKeyListener(onClear) : undefined), [isOpen, onClear]); useEffect(() => { @@ -84,7 +86,7 @@ const PollModal: FC = ({ } }, [solution]); - const addNewOption = useCallback((newOptions: string[] = []) => { + const addNewOption = useLastCallback((newOptions: string[] = []) => { setOptions([...newOptions, '']); requestNextMutation(() => { @@ -96,9 +98,9 @@ const PollModal: FC = ({ list.classList.toggle('overflown', list.scrollHeight > MAX_LIST_HEIGHT); list.scrollTo({ top: list.scrollHeight, behavior: 'smooth' }); }); - }, []); + }); - const handleCreate = useCallback(() => { + const handleCreate = useLastCallback(() => { setHasErrors(false); if (!isOpen) { return; @@ -155,20 +157,9 @@ const PollModal: FC = ({ } onSend(payload); - }, [ - isOpen, - question, - options, - isQuizMode, - correctOption, - isAnonymous, - isMultipleAnswers, - onSend, - addNewOption, - solution, - ]); + }); - const updateOption = useCallback((index: number, text: string) => { + const updateOption = useLastCallback((index: number, text: string) => { const newOptions = [...options]; newOptions[index] = text; if (newOptions[newOptions.length - 1].trim().length && newOptions.length < MAX_OPTIONS_COUNT) { @@ -176,9 +167,9 @@ const PollModal: FC = ({ } else { setOptions(newOptions); } - }, [options, addNewOption]); + }); - const removeOption = useCallback((index: number) => { + const removeOption = useLastCallback((index: number) => { const newOptions = [...options]; newOptions.splice(index, 1); setOptions(newOptions); @@ -198,49 +189,49 @@ const PollModal: FC = ({ optionsListRef.current.classList.toggle('overflown', optionsListRef.current.scrollHeight > MAX_LIST_HEIGHT); }); - }, [correctOption, options]); + }); - const handleCorrectOptionChange = useCallback((newValue: string) => { + const handleCorrectOptionChange = useLastCallback((newValue: string) => { setCorrectOption(Number(newValue)); - }, [setCorrectOption]); + }); - const handleIsAnonymousChange = useCallback((e: ChangeEvent) => { + const handleIsAnonymousChange = useLastCallback((e: ChangeEvent) => { setIsAnonymous(e.target.checked); - }, []); + }); - const handleMultipleAnswersChange = useCallback((e: ChangeEvent) => { + const handleMultipleAnswersChange = useLastCallback((e: ChangeEvent) => { setIsMultipleAnswers(e.target.checked); - }, []); + }); - const handleQuizModeChange = useCallback((e: ChangeEvent) => { + const handleQuizModeChange = useLastCallback((e: ChangeEvent) => { setIsQuizMode(e.target.checked); - }, []); + }); - const handleKeyPress = useCallback((e: React.KeyboardEvent) => { + const handleKeyPress = useLastCallback((e: React.KeyboardEvent) => { if (e.keyCode === 13) { handleCreate(); } - }, [handleCreate]); + }); - const handleQuestionChange = useCallback((e: ChangeEvent) => { + const handleQuestionChange = useLastCallback((e: ChangeEvent) => { setQuestion(e.target.value); - }, []); + }); - const getQuestionError = useCallback(() => { + const getQuestionError = useLastCallback(() => { if (hasErrors && !question.trim().length) { return lang('lng_polls_choose_question'); } return undefined; - }, [hasErrors, lang, question]); + }); - const getOptionsError = useCallback((index: number) => { + const getOptionsError = useLastCallback((index: number) => { const optionsTrimmed = options.map((o) => o.trim()).filter((o) => o.length); if (hasErrors && optionsTrimmed.length < 2 && !options[index].trim().length) { return lang('lng_polls_choose_answers'); } return undefined; - }, [hasErrors, lang, options]); + }); function renderHeader() { return ( diff --git a/src/components/middle/composer/SendAsMenu.tsx b/src/components/middle/composer/SendAsMenu.tsx index 251e52e7f..7919bee15 100644 --- a/src/components/middle/composer/SendAsMenu.tsx +++ b/src/components/middle/composer/SendAsMenu.tsx @@ -1,6 +1,4 @@ -import React, { - useCallback, useEffect, useRef, memo, -} from '../../../lib/teact/teact'; +import React, { useEffect, useRef, memo } from '../../../lib/teact/teact'; import { getActions, getGlobal } from '../../../global'; import type { FC } from '../../../lib/teact/teact'; @@ -9,8 +7,10 @@ import type { ApiSendAsPeerId } from '../../../api/types'; import { IS_TOUCH_ENV } from '../../../util/windowEnvironment'; import buildClassName from '../../../util/buildClassName'; import setTooltipItemVisible from '../../../util/setTooltipItemVisible'; -import { useKeyboardNavigation } from './hooks/useKeyboardNavigation'; import { isUserId } from '../../../global/helpers'; + +import useLastCallback from '../../../hooks/useLastCallback'; +import { useKeyboardNavigation } from './hooks/useKeyboardNavigation'; import useMouseInside from '../../../hooks/useMouseInside'; import useLang from '../../../hooks/useLang'; @@ -56,10 +56,10 @@ const SendAsMenu: FC = ({ } }, [isOpen, markMouseInside]); - const handleUserSelect = useCallback((id: string) => { + const handleUserSelect = useLastCallback((id: string) => { onClose(); saveDefaultSendAs({ chatId: chatId!, sendAsId: id }); - }, [chatId, onClose, saveDefaultSendAs]); + }); const selectedSendAsIndex = useKeyboardNavigation({ isActive: isOpen, diff --git a/src/components/middle/composer/StickerPicker.tsx b/src/components/middle/composer/StickerPicker.tsx index 93e73c7c3..451d97d9c 100644 --- a/src/components/middle/composer/StickerPicker.tsx +++ b/src/components/middle/composer/StickerPicker.tsx @@ -1,5 +1,5 @@ import React, { - useEffect, memo, useRef, useMemo, useCallback, + useEffect, memo, useRef, useMemo, } from '../../../lib/teact/teact'; import { getActions, withGlobal } from '../../../global'; @@ -27,6 +27,7 @@ import { selectChat, selectChatFullInfo, selectIsChatWithSelf, selectIsCurrentUserPremium, selectShouldLoopStickers, } from '../../../global/selectors'; +import useLastCallback from '../../../hooks/useLastCallback'; import useAsyncRendering from '../../right/hooks/useAsyncRendering'; import useHorizontalScroll from '../../../hooks/useHorizontalScroll'; import useLang from '../../../hooks/useLang'; @@ -229,27 +230,27 @@ const StickerPicker: FC = ({ animateHorizontalScroll(header, newLeft); }, [areAddedLoaded, activeSetIndex]); - const handleStickerSelect = useCallback((sticker: ApiSticker, isSilent?: boolean, shouldSchedule?: boolean) => { + const handleStickerSelect = useLastCallback((sticker: ApiSticker, isSilent?: boolean, shouldSchedule?: boolean) => { onStickerSelect(sticker, isSilent, shouldSchedule, true); addRecentSticker({ sticker }); - }, [addRecentSticker, onStickerSelect]); + }); - const handleStickerUnfave = useCallback((sticker: ApiSticker) => { + const handleStickerUnfave = useLastCallback((sticker: ApiSticker) => { unfaveSticker({ sticker }); - }, [unfaveSticker]); + }); - const handleStickerFave = useCallback((sticker: ApiSticker) => { + const handleStickerFave = useLastCallback((sticker: ApiSticker) => { faveSticker({ sticker }); - }, [faveSticker]); + }); - const handleMouseMove = useCallback(() => { + const handleMouseMove = useLastCallback(() => { if (!canSendStickers) return; sendMessageAction({ type: 'chooseSticker' }); - }, [canSendStickers, sendMessageAction]); + }); - const handleRemoveRecentSticker = useCallback((sticker: ApiSticker) => { + const handleRemoveRecentSticker = useLastCallback((sticker: ApiSticker) => { removeRecentSticker({ sticker }); - }, [removeRecentSticker]); + }); function renderCover(stickerSet: StickerSetOrReactionsSetOrRecent, index: number) { const firstSticker = stickerSet.stickers?.[0]; diff --git a/src/components/middle/composer/SymbolMenu.tsx b/src/components/middle/composer/SymbolMenu.tsx index 3b8ca4879..4deca366e 100644 --- a/src/components/middle/composer/SymbolMenu.tsx +++ b/src/components/middle/composer/SymbolMenu.tsx @@ -1,5 +1,5 @@ import React, { - memo, useCallback, useEffect, useLayoutEffect, useRef, useState, + memo, useEffect, useLayoutEffect, useRef, useState, } from '../../../lib/teact/teact'; import { requestMutation } from '../../../lib/fasterdom/fasterdom'; import { getActions, withGlobal } from '../../../global'; @@ -12,6 +12,7 @@ import { IS_TOUCH_ENV } from '../../../util/windowEnvironment'; import buildClassName from '../../../util/buildClassName'; import { selectTabState, selectIsCurrentUserPremium, selectIsContextMenuTranslucent } from '../../../global/selectors'; +import useLastCallback from '../../../hooks/useLastCallback'; import useShowTransition from '../../../hooks/useShowTransition'; import useMouseInside from '../../../hooks/useMouseInside'; import useLang from '../../../hooks/useLang'; @@ -164,11 +165,11 @@ const SymbolMenu: FC = ({ setRecentEmojis([]); }, [isOpen, addRecentEmoji]); - const handleEmojiSelect = useCallback((emoji: string, name: string) => { + const handleEmojiSelect = useLastCallback((emoji: string, name: string) => { setRecentEmojis((emojis) => [...emojis, name]); onEmojiSelect(emoji); - }, [onEmojiSelect]); + }); const recentCustomEmojisRef = useRef(recentCustomEmojis); recentCustomEmojisRef.current = recentCustomEmojis; @@ -186,22 +187,22 @@ const SymbolMenu: FC = ({ setRecentEmojis([]); }, [isOpen, addRecentCustomEmoji]); - const handleCustomEmojiSelect = useCallback((emoji: ApiSticker) => { + const handleCustomEmojiSelect = useLastCallback((emoji: ApiSticker) => { setRecentCustomEmojis((ids) => [...ids, emoji.id]); onCustomEmojiSelect(emoji); - }, [onCustomEmojiSelect]); + }); - const handleSearch = useCallback((type: 'stickers' | 'gifs') => { + const handleSearch = useLastCallback((type: 'stickers' | 'gifs') => { onClose(); onSearchOpen(type); - }, [onClose, onSearchOpen]); + }); - const handleStickerSelect = useCallback(( + const handleStickerSelect = useLastCallback(( sticker: ApiSticker, isSilent?: boolean, shouldSchedule?: boolean, canUpdateStickerSetsOrder?: boolean, ) => { onStickerSelect?.(sticker, isSilent, shouldSchedule, true, canUpdateStickerSetsOrder); - }, [onStickerSelect]); + }); const lang = useLang(); diff --git a/src/components/middle/composer/SymbolMenuButton.tsx b/src/components/middle/composer/SymbolMenuButton.tsx index dbea0180a..45010f261 100644 --- a/src/components/middle/composer/SymbolMenuButton.tsx +++ b/src/components/middle/composer/SymbolMenuButton.tsx @@ -1,6 +1,4 @@ -import React, { - memo, useCallback, useRef, useState, -} from '../../../lib/teact/teact'; +import React, { memo, useRef, useState } from '../../../lib/teact/teact'; import { getActions } from '../../../global'; import type { FC } from '../../../lib/teact/teact'; @@ -9,6 +7,8 @@ import type { ApiVideo, ApiSticker } from '../../../api/types'; import { EDITABLE_INPUT_CSS_SELECTOR, EDITABLE_INPUT_MODAL_CSS_SELECTOR } from '../../../config'; import buildClassName from '../../../util/buildClassName'; + +import useLastCallback from '../../../hooks/useLastCallback'; import useFlag from '../../../hooks/useFlag'; import useMenuPosition from '../../../hooks/useMenuPosition'; @@ -35,7 +35,7 @@ type OwnProps = { isSilent?: boolean, shouldSchedule?: boolean, shouldPreserveInput?: boolean, - canUpdateStickerSetsOrder?: boolean + canUpdateStickerSetsOrder?: boolean, ) => void; onGifSelect?: (gif: ApiVideo, isSilent?: boolean, shouldSchedule?: boolean) => void; onRemoveSymbol: VoidFunction; @@ -91,7 +91,7 @@ const SymbolMenuButton: FC = ({ : (isSymbolMenuOpen && 'is-loading'), ); - const handleActivateSymbolMenu = useCallback(() => { + const handleActivateSymbolMenu = useLastCallback(() => { closeBotCommandMenu?.(); closeSendAsMenu?.(); openSymbolMenu(); @@ -99,9 +99,9 @@ const SymbolMenuButton: FC = ({ if (!triggerEl) return; const { x, y } = triggerEl.getBoundingClientRect(); setContextMenuPosition({ x, y }); - }, [closeBotCommandMenu, closeSendAsMenu, openSymbolMenu]); + }); - const handleSearchOpen = useCallback((type: 'stickers' | 'gifs') => { + const handleSearchOpen = useLastCallback((type: 'stickers' | 'gifs') => { if (type === 'stickers') { setStickerSearchQuery({ query: '' }); setGifSearchQuery({ query: undefined }); @@ -109,9 +109,9 @@ const SymbolMenuButton: FC = ({ setGifSearchQuery({ query: '' }); setStickerSearchQuery({ query: undefined }); } - }, [setStickerSearchQuery, setGifSearchQuery]); + }); - const handleSymbolMenuOpen = useCallback(() => { + const handleSymbolMenuOpen = useLastCallback(() => { const messageInput = document.querySelector( isAttachmentModal ? EDITABLE_INPUT_MODAL_CSS_SELECTOR : EDITABLE_INPUT_CSS_SELECTOR, ); @@ -126,23 +126,12 @@ const SymbolMenuButton: FC = ({ closeBotCommandMenu?.(); openSymbolMenu(); }, MOBILE_KEYBOARD_HIDE_DELAY_MS); - }, [isAttachmentModal, isMobile, openSymbolMenu, closeBotCommandMenu]); + }); - const getTriggerElement = useCallback(() => triggerRef.current, []); - - const getRootElement = useCallback( - () => triggerRef.current?.closest('.custom-scroll, .no-scrollbar'), - [], - ); - - const getMenuElement = useCallback( - () => document.querySelector('#portals .SymbolMenu .bubble'), - [], - ); - - const getLayout = useCallback(() => ({ - withPortal: true, - }), []); + const getTriggerElement = useLastCallback(() => triggerRef.current); + const getRootElement = useLastCallback(() => triggerRef.current?.closest('.custom-scroll, .no-scrollbar')); + const getMenuElement = useLastCallback(() => document.querySelector('#portals .SymbolMenu .bubble')); + const getLayout = useLastCallback(() => ({ withPortal: true })); const { positionX, positionY, transformOriginX, transformOriginY, style: menuStyle, diff --git a/src/components/middle/composer/SymbolMenuFooter.tsx b/src/components/middle/composer/SymbolMenuFooter.tsx index dae1a5eb3..11fb2a7b7 100644 --- a/src/components/middle/composer/SymbolMenuFooter.tsx +++ b/src/components/middle/composer/SymbolMenuFooter.tsx @@ -1,7 +1,9 @@ import type { FC } from '../../../lib/teact/teact'; -import React, { memo, useCallback } from '../../../lib/teact/teact'; +import React, { memo } from '../../../lib/teact/teact'; import buildClassName from '../../../util/buildClassName'; + +import useLastCallback from '../../../hooks/useLastCallback'; import useLang from '../../../hooks/useLang'; import Button from '../../ui/Button'; @@ -58,9 +60,9 @@ const SymbolMenuFooter: FC = ({ ); } - const handleSearchOpen = useCallback(() => { + const handleSearchOpen = useLastCallback(() => { onSearchOpen(activeTab === SymbolMenuTabs.Stickers ? 'stickers' : 'gifs'); - }, [activeTab, onSearchOpen]); + }); function stopPropagation(event: any) { event.stopPropagation(); diff --git a/src/components/middle/composer/TextFormatter.tsx b/src/components/middle/composer/TextFormatter.tsx index f80c69dba..6acc175ad 100644 --- a/src/components/middle/composer/TextFormatter.tsx +++ b/src/components/middle/composer/TextFormatter.tsx @@ -1,6 +1,6 @@ import type { FC } from '../../../lib/teact/teact'; import React, { - memo, useCallback, useEffect, useRef, useState, + memo, useEffect, useRef, useState, } from '../../../lib/teact/teact'; import type { IAnchorPosition } from '../../../types'; @@ -14,6 +14,7 @@ import getKeyFromEvent from '../../../util/getKeyFromEvent'; import { INPUT_CUSTOM_EMOJI_SELECTOR } from './helpers/customEmoji'; import stopEvent from '../../../util/stopEvent'; +import useLastCallback from '../../../hooks/useLastCallback'; import useShowTransition from '../../../hooks/useShowTransition'; import useVirtualBackdrop from '../../../hooks/useVirtualBackdrop'; import useFlag from '../../../hooks/useFlag'; @@ -114,7 +115,7 @@ const TextFormatter: FC = ({ setSelectedTextFormats(selectedFormats); }, [isOpen, selectedRange, openLinkControl]); - const restoreSelection = useCallback(() => { + const restoreSelection = useLastCallback(() => { if (!selectedRange) { return; } @@ -124,16 +125,16 @@ const TextFormatter: FC = ({ selection.removeAllRanges(); selection.addRange(selectedRange); } - }, [selectedRange]); + }); - const updateSelectedRange = useCallback(() => { + const updateSelectedRange = useLastCallback(() => { const selection = window.getSelection(); if (selection) { setSelectedRange(selection.getRangeAt(0)); } - }, [setSelectedRange]); + }); - const getSelectedText = useCallback((shouldDropCustomEmoji?: boolean) => { + const getSelectedText = useLastCallback((shouldDropCustomEmoji?: boolean) => { if (!selectedRange) { return undefined; } @@ -144,15 +145,15 @@ const TextFormatter: FC = ({ }); } return fragmentEl.innerHTML; - }, [selectedRange]); + }); - const getSelectedElement = useCallback(() => { + const getSelectedElement = useLastCallback(() => { if (!selectedRange) { return undefined; } return selectedRange.commonAncestorContainer.parentElement; - }, [selectedRange]); + }); function updateInputStyles() { const input = linkUrlInputRef.current; @@ -200,7 +201,7 @@ const TextFormatter: FC = ({ return undefined; } - const handleSpoilerText = useCallback(() => { + const handleSpoilerText = useLastCallback(() => { if (selectedTextFormats.spoiler) { const element = getSelectedElement(); if ( @@ -226,9 +227,9 @@ const TextFormatter: FC = ({ 'insertHTML', false, `${text}`, ); onClose(); - }, [getSelectedElement, getSelectedText, onClose, selectedRange, selectedTextFormats.spoiler]); + }); - const handleBoldText = useCallback(() => { + const handleBoldText = useLastCallback(() => { setSelectedTextFormats((selectedFormats) => { // Somehow re-applying 'bold' command to already bold text doesn't work document.execCommand(selectedFormats.bold ? 'removeFormat' : 'bold'); @@ -244,27 +245,27 @@ const TextFormatter: FC = ({ bold: !selectedFormats.bold, }; }); - }, [updateSelectedRange]); + }); - const handleItalicText = useCallback(() => { + const handleItalicText = useLastCallback(() => { document.execCommand('italic'); updateSelectedRange(); setSelectedTextFormats((selectedFormats) => ({ ...selectedFormats, italic: !selectedFormats.italic, })); - }, [updateSelectedRange]); + }); - const handleUnderlineText = useCallback(() => { + const handleUnderlineText = useLastCallback(() => { document.execCommand('underline'); updateSelectedRange(); setSelectedTextFormats((selectedFormats) => ({ ...selectedFormats, underline: !selectedFormats.underline, })); - }, [updateSelectedRange]); + }); - const handleStrikethroughText = useCallback(() => { + const handleStrikethroughText = useLastCallback(() => { if (selectedTextFormats.strikethrough) { const element = getSelectedElement(); if ( @@ -288,11 +289,9 @@ const TextFormatter: FC = ({ const text = getSelectedText(); document.execCommand('insertHTML', false, `${text}`); onClose(); - }, [ - getSelectedElement, getSelectedText, onClose, selectedRange, selectedTextFormats.strikethrough, - ]); + }); - const handleMonospaceText = useCallback(() => { + const handleMonospaceText = useLastCallback(() => { if (selectedTextFormats.monospace) { const element = getSelectedElement(); if ( @@ -316,11 +315,9 @@ const TextFormatter: FC = ({ const text = getSelectedText(true); document.execCommand('insertHTML', false, `${text}`); onClose(); - }, [ - getSelectedElement, getSelectedText, onClose, selectedRange, selectedTextFormats.monospace, - ]); + }); - const handleLinkUrlConfirm = useCallback(() => { + const handleLinkUrlConfirm = useLastCallback(() => { const formattedLinkUrl = (ensureProtocol(linkUrl) || '').split('%').map(encodeURI).join('%'); if (isEditingLink) { @@ -344,9 +341,9 @@ const TextFormatter: FC = ({ `${text}`, ); onClose(); - }, [getSelectedElement, getSelectedText, isEditingLink, linkUrl, onClose, restoreSelection]); + }); - const handleKeyDown = useCallback((e: KeyboardEvent) => { + const handleKeyDown = useLastCallback((e: KeyboardEvent) => { const HANDLERS_BY_KEY: Record = { k: openLinkControl, b: handleBoldText, @@ -370,10 +367,7 @@ const TextFormatter: FC = ({ e.preventDefault(); e.stopPropagation(); handler(); - }, [ - openLinkControl, handleBoldText, handleUnderlineText, handleItalicText, handleMonospaceText, - handleStrikethroughText, handleSpoilerText, - ]); + }); useEffect(() => { if (isOpen) { diff --git a/src/components/middle/composer/WebPagePreview.tsx b/src/components/middle/composer/WebPagePreview.tsx index bf8018a76..073b12888 100644 --- a/src/components/middle/composer/WebPagePreview.tsx +++ b/src/components/middle/composer/WebPagePreview.tsx @@ -1,6 +1,4 @@ -import React, { - memo, useCallback, useEffect, useRef, -} from '../../../lib/teact/teact'; +import React, { memo, useEffect, useRef } from '../../../lib/teact/teact'; import { getActions, withGlobal } from '../../../global'; import type { FC } from '../../../lib/teact/teact'; @@ -15,6 +13,8 @@ import { RE_LINK_TEMPLATE } from '../../../config'; import { selectTabState, selectNoWebPage, selectTheme } from '../../../global/selectors'; import buildClassName from '../../../util/buildClassName'; import parseMessageInput from '../../../util/parseMessageInput'; + +import useLastCallback from '../../../hooks/useLastCallback'; import useSyncEffect from '../../../hooks/useSyncEffect'; import useShowTransition from '../../../hooks/useShowTransition'; import useCurrentOrPrev from '../../../hooks/useCurrentOrPrev'; @@ -97,9 +97,9 @@ const WebPagePreview: FC = ({ const renderingWebPage = useCurrentOrPrev(webPagePreview, true); - const handleClearWebpagePreview = useCallback(() => { + const handleClearWebpagePreview = useLastCallback(() => { toggleMessageWebPage({ chatId, threadId, noWebPage: true }); - }, [chatId, threadId, toggleMessageWebPage]); + }); if (!shouldRender || !renderingWebPage) { return undefined; diff --git a/src/components/middle/composer/hooks/useAttachmentModal.ts b/src/components/middle/composer/hooks/useAttachmentModal.ts index 2152c8e86..3b94ba067 100644 --- a/src/components/middle/composer/hooks/useAttachmentModal.ts +++ b/src/components/middle/composer/hooks/useAttachmentModal.ts @@ -1,4 +1,4 @@ -import { useCallback, useState } from '../../../../lib/teact/teact'; +import { useState } from '../../../../lib/teact/teact'; import { getActions } from '../../../../global'; import type { ApiAttachment } from '../../../../api/types'; @@ -11,6 +11,8 @@ import { SUPPORTED_VIDEO_CONTENT_TYPES, } from '../../../../config'; +import useLastCallback from '../../../../hooks/useLastCallback'; + export default function useAttachmentModal({ attachments, fileSizeLimit, @@ -37,11 +39,11 @@ export default function useAttachmentModal({ const [shouldForceCompression, setShouldForceCompression] = useState(false); const [shouldSuggestCompression, setShouldSuggestCompression] = useState(undefined); - const handleClearAttachments = useCallback(() => { + const handleClearAttachments = useLastCallback(() => { setAttachments(MEMO_EMPTY_ARRAY); - }, [setAttachments]); + }); - const handleSetAttachments = useCallback( + const handleSetAttachments = useLastCallback( (newValue: ApiAttachment[] | ((current: ApiAttachment[]) => ApiAttachment[])) => { const newAttachments = typeof newValue === 'function' ? newValue(attachments) : newValue; if (!newAttachments.length) { @@ -75,25 +77,22 @@ export default function useAttachmentModal({ setShouldForceAsFile(Boolean(shouldForce && canSendDocuments)); setShouldForceCompression(!canSendDocuments); } - }, [ - attachments, canSendAudios, canSendDocuments, canSendPhotos, canSendVideos, chatId, fileSizeLimit, - handleClearAttachments, openLimitReachedModal, setAttachments, showAllowedMessageTypesNotification, - ], + }, ); - const handleAppendFiles = useCallback(async (files: File[], isSpoiler?: boolean) => { + const handleAppendFiles = useLastCallback(async (files: File[], isSpoiler?: boolean) => { handleSetAttachments([ ...attachments, ...await Promise.all(files.map((file) => ( buildAttachment(file.name, file, { shouldSendAsSpoiler: isSpoiler || undefined }) ))), ]); - }, [attachments, handleSetAttachments]); + }); - const handleFileSelect = useCallback(async (files: File[], suggestCompression?: boolean) => { + const handleFileSelect = useLastCallback(async (files: File[], suggestCompression?: boolean) => { handleSetAttachments(await Promise.all(files.map((file) => buildAttachment(file.name, file)))); setShouldSuggestCompression(suggestCompression); - }, [handleSetAttachments]); + }); return { shouldSuggestCompression, diff --git a/src/components/middle/composer/hooks/useCustomEmojiTooltip.ts b/src/components/middle/composer/hooks/useCustomEmojiTooltip.ts index 492264058..6c9407daf 100644 --- a/src/components/middle/composer/hooks/useCustomEmojiTooltip.ts +++ b/src/components/middle/composer/hooks/useCustomEmojiTooltip.ts @@ -1,5 +1,5 @@ import type { RefObject } from 'react'; -import { useCallback, useEffect } from '../../../../lib/teact/teact'; +import { useEffect } from '../../../../lib/teact/teact'; import twemojiRegex from '../../../../lib/twemojiRegex'; import { requestNextMutation } from '../../../../lib/fasterdom/fasterdom'; @@ -13,6 +13,7 @@ import { getHtmlBeforeSelection } from '../../../../util/selection'; import focusEditableElement from '../../../../util/focusEditableElement'; import { buildCustomEmojiHtml } from '../helpers/customEmoji'; +import useLastCallback from '../../../../hooks/useLastCallback'; import useDerivedState from '../../../../hooks/useDerivedState'; import useFlag from '../../../../hooks/useFlag'; import useDerivedSignal from '../../../../hooks/useDerivedSignal'; @@ -68,7 +69,7 @@ export default function useCustomEmojiTooltip( } }, [isEnabled, getLastEmoji, hasCustomEmojis, clearCustomEmojiForEmoji, loadCustomEmojiForEmoji]); - const insertCustomEmoji = useCallback((emoji: ApiSticker) => { + const insertCustomEmoji = useLastCallback((emoji: ApiSticker) => { const lastEmoji = getLastEmoji(); if (!isEnabled || !lastEmoji) return; @@ -89,7 +90,7 @@ export default function useCustomEmojiTooltip( requestNextMutation(() => { focusEditableElement(inputEl, true, true); }); - }, [getLastEmoji, isEnabled, inputRef, setHtml]); + }); useEffect(unmarkManuallyClosed, [unmarkManuallyClosed, getHtml]); diff --git a/src/components/middle/composer/hooks/useDraft.ts b/src/components/middle/composer/hooks/useDraft.ts index f258a33d3..6a35afa39 100644 --- a/src/components/middle/composer/hooks/useDraft.ts +++ b/src/components/middle/composer/hooks/useDraft.ts @@ -1,4 +1,4 @@ -import { useCallback, useEffect } from '../../../../lib/teact/teact'; +import { useEffect } from '../../../../lib/teact/teact'; import { requestMeasure, requestNextMutation } from '../../../../lib/fasterdom/fasterdom'; import { getActions } from '../../../../global'; @@ -12,6 +12,8 @@ import { IS_TOUCH_ENV } from '../../../../util/windowEnvironment'; import focusEditableElement from '../../../../util/focusEditableElement'; import parseMessageInput from '../../../../util/parseMessageInput'; import { getTextWithEntitiesAsHtml } from '../../../common/helpers/renderTextWithEntities'; + +import useLastCallback from '../../../../hooks/useLastCallback'; import useBackgroundMode from '../../../../hooks/useBackgroundMode'; import useBeforeUnload from '../../../../hooks/useBeforeUnload'; import { useStateRef } from '../../../../hooks/useStateRef'; @@ -41,7 +43,7 @@ const useDraft = ( const isEditing = Boolean(editedMessage); - const updateDraft = useCallback((prevState: { chatId?: string; threadId?: number } = {}, shouldForce = false) => { + const updateDraft = useLastCallback((prevState: { chatId?: string; threadId?: number } = {}, shouldForce = false) => { if (isEditing || !lastSyncTime) return; const html = getHtml(); @@ -60,7 +62,7 @@ const useDraft = ( shouldForce, }); } - }, [chatId, threadId, isEditing, lastSyncTime, getHtml, saveDraft, clearDraft]); + }); const updateDraftRef = useStateRef(updateDraft); const runDebouncedForSaveDraft = useRunDebounced(DRAFT_DEBOUNCE, true, undefined, [chatId, threadId]); diff --git a/src/components/middle/composer/hooks/useEditing.ts b/src/components/middle/composer/hooks/useEditing.ts index 0524e00a7..aef186a50 100644 --- a/src/components/middle/composer/hooks/useEditing.ts +++ b/src/components/middle/composer/hooks/useEditing.ts @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useState } from '../../../../lib/teact/teact'; +import { useEffect, useState } from '../../../../lib/teact/teact'; import { getActions } from '../../../../global'; import { requestMeasure, requestNextMutation } from '../../../../lib/fasterdom/fasterdom'; @@ -13,6 +13,8 @@ import parseMessageInput from '../../../../util/parseMessageInput'; import focusEditableElement from '../../../../util/focusEditableElement'; import { hasMessageMedia } from '../../../../global/helpers'; import { getTextWithEntitiesAsHtml } from '../../../common/helpers/renderTextWithEntities'; + +import useLastCallback from '../../../../hooks/useLastCallback'; import useBackgroundMode from '../../../../hooks/useBackgroundMode'; import useBeforeUnload from '../../../../hooks/useBeforeUnload'; import { useDebouncedResolver } from '../../../../hooks/useAsyncResolvers'; @@ -118,7 +120,7 @@ const useEditing = ( } }, [editedMessage, chatId, getHtml, threadId, getShouldResetNoWebPageDebounced]); - const restoreNewDraftAfterEditing = useCallback(() => { + const restoreNewDraftAfterEditing = useLastCallback(() => { if (!draft) return; // Run one frame after editing draft reset @@ -133,14 +135,14 @@ const useEditing = ( } }); }); - }, [draft, setHtml]); + }); - const handleEditCancel = useCallback(() => { + const handleEditCancel = useLastCallback(() => { resetComposer(); restoreNewDraftAfterEditing(); - }, [resetComposer, restoreNewDraftAfterEditing]); + }); - const handleEditComplete = useCallback(() => { + const handleEditComplete = useLastCallback(() => { const { text, entities } = parseMessageInput(getHtml()); if (!editedMessage) { @@ -159,9 +161,9 @@ const useEditing = ( resetComposer(); restoreNewDraftAfterEditing(); - }, [editMessage, editedMessage, getHtml, openDeleteModal, resetComposer, restoreNewDraftAfterEditing]); + }); - const handleBlur = useCallback(() => { + const handleBlur = useLastCallback(() => { if (!editedMessage) return; const edited = parseMessageInput(getHtml()); const update = edited.text.length ? edited : undefined; @@ -169,7 +171,7 @@ const useEditing = ( setEditingDraft({ chatId, threadId, type, text: update, }); - }, [chatId, editedMessage, getHtml, setEditingDraft, threadId, type]); + }); useBackgroundMode(handleBlur); useBeforeUnload(handleBlur); diff --git a/src/components/middle/composer/hooks/useEmojiTooltip.ts b/src/components/middle/composer/hooks/useEmojiTooltip.ts index 1fd6802f6..f2917ef6e 100644 --- a/src/components/middle/composer/hooks/useEmojiTooltip.ts +++ b/src/components/middle/composer/hooks/useEmojiTooltip.ts @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useState } from '../../../../lib/teact/teact'; +import { useEffect, useState } from '../../../../lib/teact/teact'; import { requestNextMutation } from '../../../../lib/fasterdom/fasterdom'; import { getGlobal } from '../../../../global'; @@ -19,6 +19,7 @@ import renderText from '../../../common/helpers/renderText'; import { selectCustomEmojiForEmojis } from '../../../../global/selectors'; import { buildCustomEmojiHtml } from '../helpers/customEmoji'; +import useLastCallback from '../../../../hooks/useLastCallback'; import useFlag from '../../../../hooks/useFlag'; import useDerivedSignal from '../../../../hooks/useDerivedSignal'; import { useThrottledResolver } from '../../../../hooks/useAsyncResolvers'; @@ -94,7 +95,7 @@ export default function useEmojiTooltip( detectEmojiCodeThrottled, [detectEmojiCodeThrottled, getHtml], true, ); - const updateFiltered = useCallback((emojis: Emoji[]) => { + const updateFiltered = useLastCallback((emojis: Emoji[]) => { setFilteredEmojis(emojis); if (emojis === MEMO_EMPTY_ARRAY) { @@ -108,9 +109,9 @@ export default function useEmojiTooltip( 'id', ); setFilteredCustomEmojis(customEmojis); - }, []); + }); - const insertEmoji = useCallback((emoji: string | ApiSticker, isForce = false) => { + const insertEmoji = useLastCallback((emoji: string | ApiSticker, isForce = false) => { const html = getHtml(); if (!html) return; @@ -130,7 +131,7 @@ export default function useEmojiTooltip( } updateFiltered(MEMO_EMPTY_ARRAY); - }, [getHtml, setHtml, inputId, updateFiltered]); + }); useEffect(() => { const emojiCode = getEmojiCode(); diff --git a/src/components/middle/composer/hooks/useInlineBotTooltip.ts b/src/components/middle/composer/hooks/useInlineBotTooltip.ts index e62f62992..c4a748537 100644 --- a/src/components/middle/composer/hooks/useInlineBotTooltip.ts +++ b/src/components/middle/composer/hooks/useInlineBotTooltip.ts @@ -1,4 +1,4 @@ -import { useCallback, useEffect } from '../../../../lib/teact/teact'; +import { useEffect } from '../../../../lib/teact/teact'; import type { InlineBotSettings } from '../../../../types'; import type { Signal } from '../../../../util/signals'; @@ -6,6 +6,7 @@ import type { Signal } from '../../../../util/signals'; import { getActions } from '../../../../global'; import memoized from '../../../../util/memoized'; +import useLastCallback from '../../../../hooks/useLastCallback'; import useFlag from '../../../../hooks/useFlag'; import useDerivedState from '../../../../hooks/useDerivedState'; import useSyncEffect from '../../../../hooks/useSyncEffect'; @@ -75,13 +76,13 @@ export default function useInlineBotTooltip( } }, [isOpen, resetAllInlineBots, username]); - const loadMore = useCallback(() => { + const loadMore = useLastCallback(() => { if (!usernameLowered) return; queryInlineBot({ chatId, username: usernameLowered, query, offset, }); - }, [chatId, offset, query, queryInlineBot, usernameLowered]); + }); return { isOpen, diff --git a/src/components/middle/composer/hooks/useInputCustomEmojis.ts b/src/components/middle/composer/hooks/useInputCustomEmojis.ts index 25a0be7e4..e539a5121 100644 --- a/src/components/middle/composer/hooks/useInputCustomEmojis.ts +++ b/src/components/middle/composer/hooks/useInputCustomEmojis.ts @@ -1,5 +1,5 @@ import { - useCallback, useEffect, useLayoutEffect, useRef, + useEffect, useLayoutEffect, useRef, } from '../../../../lib/teact/teact'; import { requestMeasure } from '../../../../lib/fasterdom/fasterdom'; import { ensureRLottie } from '../../../../lib/rlottie/RLottie.async'; @@ -19,12 +19,12 @@ import { REM } from '../../../common/helpers/mediaDimensions'; import { hexToRgb } from '../../../../util/switchTheme'; import useColorFilter from '../../../../hooks/stickers/useColorFilter'; +import useLastCallback from '../../../../hooks/useLastCallback'; import useResizeObserver from '../../../../hooks/useResizeObserver'; import useBackgroundMode from '../../../../hooks/useBackgroundMode'; import useThrottledCallback from '../../../../hooks/useThrottledCallback'; import useDynamicColorListener from '../../../../hooks/stickers/useDynamicColorListener'; import useEffectWithPrevDeps from '../../../../hooks/useEffectWithPrevDeps'; -import { useLastCallback } from '../../../../hooks/useLastCallback'; const SIZE = 1.25 * REM; const THROTTLE_MS = 300; @@ -51,7 +51,7 @@ export default function useInputCustomEmojis( const colorFilter = useColorFilter(customColor, true); const playersById = useRef>(new Map()); - const clearPlayers = useCallback((ids: string[]) => { + const clearPlayers = useLastCallback((ids: string[]) => { ids.forEach((id) => { const player = playersById.current.get(id); if (player) { @@ -59,7 +59,7 @@ export default function useInputCustomEmojis( playersById.current.delete(id); } }); - }, []); + }); const synchronizeElements = useLastCallback(() => { if (!isReady || !inputRef.current || !sharedCanvasRef.current || !sharedCanvasHqRef.current) return; @@ -160,13 +160,13 @@ export default function useInputCustomEmojis( ); useResizeObserver(sharedCanvasRef, throttledSynchronizeElements); - const freezeAnimation = useCallback(() => { + const freezeAnimation = useLastCallback(() => { playersById.current.forEach((player) => { player.pause(); }); - }, []); + }); - const unfreezeAnimation = useCallback(() => { + const unfreezeAnimation = useLastCallback(() => { if (!canPlayAnimatedEmojis) { return; } @@ -174,11 +174,11 @@ export default function useInputCustomEmojis( playersById.current?.forEach((player) => { player.play(); }); - }, [canPlayAnimatedEmojis]); + }); - const unfreezeAnimationOnRaf = useCallback(() => { + const unfreezeAnimationOnRaf = useLastCallback(() => { requestMeasure(unfreezeAnimation); - }, [unfreezeAnimation]); + }); // Pausing frame may not happen in background, // so we need to make sure it happens right after focusing, diff --git a/src/components/middle/composer/hooks/useKeyboardNavigation.ts b/src/components/middle/composer/hooks/useKeyboardNavigation.ts index aac9db9d4..4c7ce0aef 100644 --- a/src/components/middle/composer/hooks/useKeyboardNavigation.ts +++ b/src/components/middle/composer/hooks/useKeyboardNavigation.ts @@ -1,7 +1,10 @@ -import { useCallback, useEffect, useState } from '../../../../lib/teact/teact'; +import { useEffect, useState } from '../../../../lib/teact/teact'; + import captureKeyboardListeners from '../../../../util/captureKeyboardListeners'; import cycleRestrict from '../../../../util/cycleRestrict'; +import useLastCallback from '../../../../hooks/useLastCallback'; + export function useKeyboardNavigation({ isActive, isHorizontal, @@ -25,20 +28,20 @@ export function useKeyboardNavigation({ }) { const [selectedItemIndex, setSelectedItemIndex] = useState(-1); - const getSelectedIndex = useCallback((newIndex: number) => { + const getSelectedIndex = useLastCallback((newIndex: number) => { if (!items) { return -1; } return cycleRestrict(items.length, newIndex); - }, [items]); + }); - const handleArrowKey = useCallback((value: number, e: KeyboardEvent) => { + const handleArrowKey = useLastCallback((value: number, e: KeyboardEvent) => { e.preventDefault(); setSelectedItemIndex((index) => (getSelectedIndex(index + value))); - }, [setSelectedItemIndex, getSelectedIndex]); + }); - const handleItemSelect = useCallback((e: KeyboardEvent) => { + const handleItemSelect = useLastCallback((e: KeyboardEvent) => { // Prevent action on key combinations if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) return false; if (items && items.length && selectedItemIndex > -1) { @@ -53,7 +56,7 @@ export function useKeyboardNavigation({ } return true; - }, [items, onSelect, selectedItemIndex]); + }); const isSelectionOutOfRange = !items || selectedItemIndex > items.length - 1; useEffect(() => { diff --git a/src/components/middle/composer/hooks/useMentionTooltip.ts b/src/components/middle/composer/hooks/useMentionTooltip.ts index 5202b4fff..671bdc988 100644 --- a/src/components/middle/composer/hooks/useMentionTooltip.ts +++ b/src/components/middle/composer/hooks/useMentionTooltip.ts @@ -1,7 +1,5 @@ import type { RefObject } from 'react'; -import { - useCallback, useEffect, useState, -} from '../../../../lib/teact/teact'; +import { useEffect, useState } from '../../../../lib/teact/teact'; import { getGlobal } from '../../../../global'; import { requestNextMutation } from '../../../../lib/fasterdom/fasterdom'; @@ -15,6 +13,7 @@ import focusEditableElement from '../../../../util/focusEditableElement'; import { pickTruthy, unique } from '../../../../util/iteratees'; import { getCaretPosition, getHtmlBeforeSelection, setCaretPosition } from '../../../../util/selection'; +import useLastCallback from '../../../../hooks/useLastCallback'; import useFlag from '../../../../hooks/useFlag'; import useDerivedSignal from '../../../../hooks/useDerivedSignal'; import { useThrottledResolver } from '../../../../hooks/useAsyncResolvers'; @@ -91,7 +90,7 @@ export default function useMentionTooltip( setFilteredUsers(Object.values(pickTruthy(usersById, filteredIds))); }, [currentUserId, groupChatMembers, topInlineBotIds, getUsernameTag, getWithInlineBots]); - const insertMention = useCallback((user: ApiUser, forceFocus = false) => { + const insertMention = useLastCallback((user: ApiUser, forceFocus = false) => { if (!user.usernames && !getUserFirstOrLastName(user)) { return; } @@ -131,7 +130,7 @@ export default function useMentionTooltip( } setFilteredUsers(undefined); - }, [inputRef, setHtml]); + }); useEffect(unmarkManuallyClosed, [unmarkManuallyClosed, getHtml]); diff --git a/src/components/middle/composer/hooks/useVoiceRecording.ts b/src/components/middle/composer/hooks/useVoiceRecording.ts index d97a59e7d..2e7975b62 100644 --- a/src/components/middle/composer/hooks/useVoiceRecording.ts +++ b/src/components/middle/composer/hooks/useVoiceRecording.ts @@ -1,12 +1,12 @@ -import { - useCallback, useEffect, useRef, useState, -} from '../../../../lib/teact/teact'; +import { useEffect, useRef, useState } from '../../../../lib/teact/teact'; import { requestMutation } from '../../../../lib/fasterdom/fasterdom'; import { IS_SAFARI, IS_VOICE_RECORDING_SUPPORTED } from '../../../../util/windowEnvironment'; import * as voiceRecording from '../../../../util/voiceRecording'; import captureEscKeyListener from '../../../../util/captureEscKeyListener'; +import useLastCallback from '../../../../hooks/useLastCallback'; + type ActiveVoiceRecording = { stop: () => Promise; pause: NoneToVoidFunction } | undefined; @@ -25,7 +25,7 @@ const useVoiceRecording = () => { } }, []); - const startRecordingVoice = useCallback(async () => { + const startRecordingVoice = useLastCallback(async () => { try { const { stop, pause } = await voiceRecording.start((tickVolume: number) => { if (recordButtonRef.current) { @@ -45,9 +45,9 @@ const useVoiceRecording = () => { // eslint-disable-next-line no-console console.error(err); } - }, []); + }); - const pauseRecordingVoice = useCallback(() => { + const pauseRecordingVoice = useLastCallback(() => { if (!activeVoiceRecording) { return undefined; } @@ -65,9 +65,9 @@ const useVoiceRecording = () => { console.error(err); return undefined; } - }, [activeVoiceRecording]); + }); - const stopRecordingVoice = useCallback(() => { + const stopRecordingVoice = useLastCallback(() => { if (!activeVoiceRecording) { return undefined; } @@ -89,7 +89,7 @@ const useVoiceRecording = () => { console.error(err); return undefined; } - }, [activeVoiceRecording]); + }); useEffect(() => { return activeVoiceRecording ? captureEscKeyListener(stopRecordingVoice) : undefined; diff --git a/src/components/middle/composer/inlineResults/ArticleResult.tsx b/src/components/middle/composer/inlineResults/ArticleResult.tsx index e41009481..b72ce3cea 100644 --- a/src/components/middle/composer/inlineResults/ArticleResult.tsx +++ b/src/components/middle/composer/inlineResults/ArticleResult.tsx @@ -1,8 +1,10 @@ import type { FC } from '../../../../lib/teact/teact'; -import React, { memo, useCallback } from '../../../../lib/teact/teact'; +import React, { memo } from '../../../../lib/teact/teact'; import type { ApiBotInlineResult } from '../../../../api/types'; +import useLastCallback from '../../../../hooks/useLastCallback'; + import BaseResult from './BaseResult'; export type OwnProps = { @@ -16,9 +18,9 @@ const ArticleResult: FC = ({ focus, inlineResult, onClick }) => { title, url, description, webThumbnail, } = inlineResult; - const handleClick = useCallback(() => { + const handleClick = useLastCallback(() => { onClick(inlineResult); - }, [inlineResult, onClick]); + }); return ( = ({ }) => { const { gif } = inlineResult; - const handleClick = useCallback((_gif: ApiVideo, isSilent?: boolean, shouldSchedule?: boolean) => { + const handleClick = useLastCallback((_gif: ApiVideo, isSilent?: boolean, shouldSchedule?: boolean) => { onClick(inlineResult, isSilent, shouldSchedule); - }, [inlineResult, onClick]); + }); if (!gif) { return undefined; diff --git a/src/components/middle/composer/inlineResults/MediaResult.tsx b/src/components/middle/composer/inlineResults/MediaResult.tsx index 9e80b313a..c03375e1d 100644 --- a/src/components/middle/composer/inlineResults/MediaResult.tsx +++ b/src/components/middle/composer/inlineResults/MediaResult.tsx @@ -1,11 +1,13 @@ import type { FC } from '../../../../lib/teact/teact'; -import React, { memo, useCallback } from '../../../../lib/teact/teact'; +import React, { memo } from '../../../../lib/teact/teact'; import type { ApiBotInlineMediaResult, ApiBotInlineResult, ApiPhoto, ApiThumbnail, ApiWebDocument, } from '../../../../api/types'; import buildClassName from '../../../../util/buildClassName'; + +import useLastCallback from '../../../../hooks/useLastCallback'; import useMedia from '../../../../hooks/useMedia'; import useMediaTransition from '../../../../hooks/useMediaTransition'; @@ -42,9 +44,9 @@ const MediaResult: FC = ({ const mediaBlobUrl = useMedia(photo && `photo${photo.id}?size=m`); const transitionClassNames = useMediaTransition(mediaBlobUrl); - const handleClick = useCallback(() => { + const handleClick = useLastCallback(() => { onClick(inlineResult); - }, [inlineResult, onClick]); + }); if (isForGallery) { return ( diff --git a/src/components/middle/hooks/useContainerHeight.ts b/src/components/middle/hooks/useContainerHeight.ts index 4edb45518..f57a9d50a 100644 --- a/src/components/middle/hooks/useContainerHeight.ts +++ b/src/components/middle/hooks/useContainerHeight.ts @@ -1,6 +1,7 @@ import type { RefObject } from 'react'; -import { useCallback, useEffect, useRef } from '../../../lib/teact/teact'; +import { useEffect, useRef } from '../../../lib/teact/teact'; +import useLastCallback from '../../../hooks/useLastCallback'; import useSignal from '../../../hooks/useSignal'; import useResizeObserver from '../../../hooks/useResizeObserver'; @@ -8,9 +9,10 @@ export default function useContainerHeight(containerRef: RefObject(); // Container resize observer (caused by Composer reply/webpage panels) - const handleResize = useCallback((entry: ResizeObserverEntry) => { + const handleResize = useLastCallback((entry: ResizeObserverEntry) => { setContainerHeight(entry.contentRect.height); - }, [setContainerHeight]); + }); + useResizeObserver(containerRef, handleResize); useEffect(() => { diff --git a/src/components/middle/hooks/usePinnedMessage.ts b/src/components/middle/hooks/usePinnedMessage.ts index bb1976960..1c2402581 100644 --- a/src/components/middle/hooks/usePinnedMessage.ts +++ b/src/components/middle/hooks/usePinnedMessage.ts @@ -1,15 +1,16 @@ import { getGlobal } from '../../../global'; -import { useCallback, useEffect, useRef } from '../../../lib/teact/teact'; +import { useEffect, useRef } from '../../../lib/teact/teact'; import { selectFocusedMessageId, selectListedIds, selectOutlyingListByMessageId, } from '../../../global/selectors'; - import { unique } from '../../../util/iteratees'; import { clamp } from '../../../util/math'; import cycleRestrict from '../../../util/cycleRestrict'; + +import useLastCallback from '../../../hooks/useLastCallback'; import useSignal from '../../../hooks/useSignal'; type PinnedIntersectionChangedParams = { @@ -51,7 +52,7 @@ export default function usePinnedMessage(chatId?: string, threadId?: number, pin } }, [getCurrentPinnedIndexes, key, pinnedIds?.length, setCurrentPinnedIndexes]); - const onIntersectionChanged = useCallback(({ + const onIntersectionChanged = useLastCallback(({ viewportPinnedIdsToAdd = [], viewportPinnedIdsToRemove = [], isReversed, hasScrolled, isUnmount, }: PinnedIntersectionChangedParams) => { if (!chatId || !threadId || !key) return; @@ -126,12 +127,9 @@ export default function usePinnedMessage(chatId?: string, threadId?: number, pin ...getCurrentPinnedIndexes(), [key]: newIndex, }); - }, [ - chatId, threadId, key, pinnedIds, getLoadingPinnedId, getForceNextPinnedInHeader, setCurrentPinnedIndexes, - getCurrentPinnedIndexes, setLoadingPinnedId, setForceNextPinnedInHeader, - ]); + }); - const onFocusPinnedMessage = useCallback((messageId: number): boolean => { + const onFocusPinnedMessage = useLastCallback((messageId: number): boolean => { if (!chatId || !threadId || !key || getLoadingPinnedId()) return false; const global = getGlobal(); @@ -155,10 +153,7 @@ export default function usePinnedMessage(chatId?: string, threadId?: number, pin setLoadingPinnedId(pinnedIds[newPinnedIndex]); return true; } - }, [ - chatId, getCurrentPinnedIndexes, getLoadingPinnedId, key, pinnedIds, setCurrentPinnedIndexes, - setForceNextPinnedInHeader, setLoadingPinnedId, threadId, - ]); + }); return { onIntersectionChanged, diff --git a/src/components/middle/hooks/useScrollHooks.ts b/src/components/middle/hooks/useScrollHooks.ts index 11e89c2dd..a9bc50f25 100644 --- a/src/components/middle/hooks/useScrollHooks.ts +++ b/src/components/middle/hooks/useScrollHooks.ts @@ -1,7 +1,5 @@ import type { RefObject } from 'react'; -import { - useCallback, useEffect, useMemo, useRef, -} from '../../../lib/teact/teact'; +import { useEffect, useMemo, useRef } from '../../../lib/teact/teact'; import { requestMeasure } from '../../../lib/fasterdom/fasterdom'; import { getActions } from '../../../global'; @@ -13,6 +11,7 @@ import { MESSAGE_LIST_SENSITIVE_AREA } from '../../../util/windowEnvironment'; import { debounce } from '../../../util/schedulers'; import { isLocalMessageId } from '../../../global/helpers'; +import useLastCallback from '../../../hooks/useLastCallback'; import { useIntersectionObserver, useOnIntersect } from '../../../hooks/useIntersectionObserver'; import useSyncEffect from '../../../hooks/useSyncEffect'; import { useStateRef } from '../../../hooks/useStateRef'; @@ -148,7 +147,7 @@ export default function useScrollHooks( } }, [isReady, toggleScrollToolsRef]); - const freezeShortly = useCallback(() => { + const freezeShortly = useLastCallback(() => { freezeForFab(); freezeForNotch(); @@ -156,7 +155,7 @@ export default function useScrollHooks( unfreezeForNotch(); unfreezeForFab(); }, TOOLS_FREEZE_TIMEOUT); - }, [freezeForFab, freezeForNotch, unfreezeForFab, unfreezeForNotch]); + }); // Workaround for FAB and notch flickering with tall incoming message useSyncEffect(freezeShortly, [freezeShortly, messageIds]); diff --git a/src/components/middle/hooks/useStickyDates.ts b/src/components/middle/hooks/useStickyDates.ts index 3549d2176..d968725a1 100644 --- a/src/components/middle/hooks/useStickyDates.ts +++ b/src/components/middle/hooks/useStickyDates.ts @@ -1,6 +1,6 @@ -import { useCallback } from '../../../lib/teact/teact'; import { requestMutation } from '../../../lib/fasterdom/fasterdom'; +import useLastCallback from '../../../hooks/useLastCallback'; import useRunDebounced from '../../../hooks/useRunDebounced'; import useFlag from '../../../hooks/useFlag'; @@ -15,7 +15,7 @@ export default function useStickyDates() { const runDebounced = useRunDebounced(DEBOUNCE, true); - const updateStickyDates = useCallback((container: HTMLDivElement, hasTools?: boolean) => { + const updateStickyDates = useLastCallback((container: HTMLDivElement, hasTools?: boolean) => { markIsScrolled(); if (!document.body.classList.contains('is-scrolling-messages')) { @@ -41,7 +41,7 @@ export default function useStickyDates() { document.body.classList.remove('is-scrolling-messages'); }); }); - }, [markIsScrolled, runDebounced]); + }); return { isScrolled, diff --git a/src/components/middle/message/Album.tsx b/src/components/middle/message/Album.tsx index ad37118f0..9e18e8210 100644 --- a/src/components/middle/message/Album.tsx +++ b/src/components/middle/message/Album.tsx @@ -1,5 +1,5 @@ import type { FC } from '../../../lib/teact/teact'; -import React, { useCallback } from '../../../lib/teact/teact'; +import React from '../../../lib/teact/teact'; import type { GlobalState } from '../../../global/types'; import type { ApiMessage } from '../../../api/types'; @@ -18,6 +18,8 @@ import { selectTheme, } from '../../../global/selectors'; +import useLastCallback from '../../../hooks/useLastCallback'; + import Photo from './Photo'; import Video from './Video'; @@ -60,9 +62,9 @@ const Album: FC = ({ const mediaCount = album.messages.length; - const handleCancelUpload = useCallback((message: ApiMessage) => { + const handleCancelUpload = useLastCallback((message: ApiMessage) => { cancelSendingMessage({ chatId: message.chatId, messageId: message.id }); - }, [cancelSendingMessage]); + }); function renderAlbumMessage(message: ApiMessage, index: number) { const { photo, video } = getMessageContent(message); diff --git a/src/components/middle/message/CommentButton.tsx b/src/components/middle/message/CommentButton.tsx index ffc855e75..efa3822e4 100644 --- a/src/components/middle/message/CommentButton.tsx +++ b/src/components/middle/message/CommentButton.tsx @@ -1,5 +1,5 @@ import type { FC } from '../../../lib/teact/teact'; -import React, { memo, useCallback, useMemo } from '../../../lib/teact/teact'; +import React, { memo, useMemo } from '../../../lib/teact/teact'; import { getActions, getGlobal } from '../../../global'; import type { @@ -9,6 +9,8 @@ import type { import { isUserId } from '../../../global/helpers'; import { formatIntegerCompact } from '../../../util/textFormat'; import buildClassName from '../../../util/buildClassName'; + +import useLastCallback from '../../../hooks/useLastCallback'; import useLang from '../../../hooks/useLang'; import Avatar from '../../common/Avatar'; @@ -32,9 +34,9 @@ const CommentButton: FC = ({ threadId, chatId, messagesCount, lastMessageId, lastReadInboxMessageId, recentReplierIds, originChannelId, } = threadInfo; - const handleClick = useCallback(() => { + const handleClick = useLastCallback(() => { openComments({ id: chatId, threadId, originChannelId }); - }, [openComments, chatId, threadId, originChannelId]); + }); const recentRepliers = useMemo(() => { if (!recentReplierIds?.length) { diff --git a/src/components/middle/message/Contact.tsx b/src/components/middle/message/Contact.tsx index 7ffa71655..f937214e4 100644 --- a/src/components/middle/message/Contact.tsx +++ b/src/components/middle/message/Contact.tsx @@ -1,5 +1,5 @@ import type { FC } from '../../../lib/teact/teact'; -import React, { useCallback } from '../../../lib/teact/teact'; +import React from '../../../lib/teact/teact'; import { getActions, withGlobal } from '../../../global'; import type { ApiUser, ApiContact, ApiCountryCode } from '../../../api/types'; @@ -8,6 +8,8 @@ import { selectUser } from '../../../global/selectors'; import { formatPhoneNumberWithCode } from '../../../util/phoneNumber'; import buildClassName from '../../../util/buildClassName'; +import useLastCallback from '../../../hooks/useLastCallback'; + import Avatar from '../../common/Avatar'; import './Contact.scss'; @@ -36,9 +38,9 @@ const Contact: FC = ({ } = contact; const isRegistered = userId !== UNREGISTERED_CONTACT_ID; - const handleClick = useCallback(() => { + const handleClick = useLastCallback(() => { openChat({ id: userId }); - }, [openChat, userId]); + }); return (
= ({ return activeDownloads?.[message.isScheduled ? 'scheduledIds' : 'ids']?.includes(message.id); }, [activeDownloads, album, message]); - const handleDelete = useCallback(() => { + const handleDelete = useLastCallback(() => { setIsMenuOpen(false); setIsDeleteModalOpen(true); - }, []); + }); - const handleReport = useCallback(() => { + const handleReport = useLastCallback(() => { setIsMenuOpen(false); setIsReportModalOpen(true); - }, []); + }); - const closeMenu = useCallback(() => { + const closeMenu = useLastCallback(() => { setIsMenuOpen(false); onClose(); - }, [onClose]); + }); - const closeDeleteModal = useCallback(() => { + const closeDeleteModal = useLastCallback(() => { setIsDeleteModalOpen(false); onClose(); - }, [onClose]); + }); - const closeReportModal = useCallback(() => { + const closeReportModal = useLastCallback(() => { setIsReportModalOpen(false); onClose(); - }, [onClose]); + }); - const closePinModal = useCallback(() => { + const closePinModal = useLastCallback(() => { setIsPinModalOpen(false); onClose(); - }, [onClose]); + }); - const handleReply = useCallback(() => { + const handleReply = useLastCallback(() => { setReplyingToId({ messageId: message.id }); closeMenu(); - }, [setReplyingToId, message.id, closeMenu]); + }); - const handleOpenThread = useCallback(() => { + const handleOpenThread = useLastCallback(() => { openChat({ id: message.chatId, threadId: message.id, }); closeMenu(); - }, [closeMenu, message.chatId, message.id, openChat]); + }); - const handleEdit = useCallback(() => { + const handleEdit = useLastCallback(() => { setEditingId({ messageId: message.id }); closeMenu(); - }, [setEditingId, message.id, closeMenu]); + }); - const handlePin = useCallback(() => { + const handlePin = useLastCallback(() => { setIsMenuOpen(false); setIsPinModalOpen(true); - }, []); + }); - const handleUnpin = useCallback(() => { + const handleUnpin = useLastCallback(() => { pinMessage({ messageId: message.id, isUnpin: true }); closeMenu(); - }, [pinMessage, message.id, closeMenu]); + }); - const handleForward = useCallback(() => { + const handleForward = useLastCallback(() => { closeMenu(); if (album?.messages) { const messageIds = album.messages.map(({ id }) => id); @@ -327,29 +328,29 @@ const ContextMenuContainer: FC = ({ } else { openForwardMenu({ fromChatId: message.chatId, messageIds: [message.id] }); } - }, [openForwardMenu, message, closeMenu, album]); + }); - const handleFaveSticker = useCallback(() => { + const handleFaveSticker = useLastCallback(() => { closeMenu(); faveSticker({ sticker: message.content.sticker! }); - }, [closeMenu, message.content.sticker, faveSticker]); + }); - const handleUnfaveSticker = useCallback(() => { + const handleUnfaveSticker = useLastCallback(() => { closeMenu(); unfaveSticker({ sticker: message.content.sticker! }); - }, [closeMenu, message.content.sticker, unfaveSticker]); + }); - const handleCancelVote = useCallback(() => { + const handleCancelVote = useLastCallback(() => { closeMenu(); cancelPollVote({ chatId: message.chatId, messageId: message.id }); - }, [closeMenu, message, cancelPollVote]); + }); - const handlePollClose = useCallback(() => { + const handlePollClose = useLastCallback(() => { closeMenu(); closePoll({ chatId: message.chatId, messageId: message.id }); - }, [closeMenu, message, closePoll]); + }); - const handleSelectMessage = useCallback(() => { + const handleSelectMessage = useLastCallback(() => { const params = album?.messages ? { messageId: message.id, @@ -360,53 +361,53 @@ const ContextMenuContainer: FC = ({ toggleMessageSelection(params); closeMenu(); - }, [closeMenu, message.id, toggleMessageSelection, album]); + }); - const handleScheduledMessageSend = useCallback(() => { + const handleScheduledMessageSend = useLastCallback(() => { sendScheduledMessages({ chatId: message.chatId, id: message.id }); closeMenu(); - }, [closeMenu, message.chatId, message.id, sendScheduledMessages]); + }); - const handleRescheduleMessage = useCallback((scheduledAt: number) => { + const handleRescheduleMessage = useLastCallback((scheduledAt: number) => { rescheduleMessage({ chatId: message.chatId, messageId: message.id, scheduledAt, }); onClose(); - }, [message.chatId, message.id, onClose, rescheduleMessage]); + }); - const handleOpenCalendar = useCallback(() => { + const handleOpenCalendar = useLastCallback(() => { setIsMenuOpen(false); requestCalendar(handleRescheduleMessage); - }, [handleRescheduleMessage, requestCalendar]); + }); - const handleOpenSeenByModal = useCallback(() => { + const handleOpenSeenByModal = useLastCallback(() => { closeMenu(); openSeenByModal({ chatId: message.chatId, messageId: message.id }); - }, [closeMenu, message.chatId, message.id, openSeenByModal]); + }); - const handleOpenReactorListModal = useCallback(() => { + const handleOpenReactorListModal = useLastCallback(() => { closeMenu(); openReactorListModal({ chatId: message.chatId, messageId: message.id }); - }, [closeMenu, openReactorListModal, message.chatId, message.id]); + }); - const handleCopyMessages = useCallback((messageIds: number[]) => { + const handleCopyMessages = useLastCallback((messageIds: number[]) => { copyMessagesByIds({ messageIds }); closeMenu(); - }, [closeMenu, copyMessagesByIds]); + }); - const handleCopyLink = useCallback(() => { + const handleCopyLink = useLastCallback(() => { copyTextToClipboard(getChatMessageLink(message.chatId, chatUsername, threadId, message.id)); closeMenu(); - }, [chatUsername, closeMenu, message, threadId]); + }); - const handleCopyNumber = useCallback(() => { + const handleCopyNumber = useLastCallback(() => { copyTextToClipboard(message.content.contact!.phoneNumber); closeMenu(); - }, [closeMenu, message]); + }); - const handleDownloadClick = useCallback(() => { + const handleDownloadClick = useLastCallback(() => { (album?.messages || [message]).forEach((msg) => { if (isDownloading) { cancelMessageMediaDownload({ message: msg }); @@ -415,48 +416,48 @@ const ContextMenuContainer: FC = ({ } }); closeMenu(); - }, [album, message, closeMenu, isDownloading, cancelMessageMediaDownload, downloadMessageMedia]); + }); - const handleSaveGif = useCallback(() => { + const handleSaveGif = useLastCallback(() => { const video = getMessageVideo(message); saveGif({ gif: video! }); closeMenu(); - }, [closeMenu, message, saveGif]); + }); - const handleToggleReaction = useCallback((reaction: ApiReaction) => { + const handleToggleReaction = useLastCallback((reaction: ApiReaction) => { toggleReaction({ chatId: message.chatId, messageId: message.id, reaction, shouldAddToRecent: true, }); closeMenu(); - }, [closeMenu, message, toggleReaction]); + }); - const handleReactionPickerOpen = useCallback((position: IAnchorPosition) => { + const handleReactionPickerOpen = useLastCallback((position: IAnchorPosition) => { openReactionPicker({ chatId: message.chatId, messageId: message.id, position }); - }, [message.chatId, message.id]); + }); - const handleTranslate = useCallback(() => { + const handleTranslate = useLastCallback(() => { requestMessageTranslation({ chatId: message.chatId, id: message.id, }); closeMenu(); - }, [closeMenu, message, requestMessageTranslation]); + }); - const handleShowOriginal = useCallback(() => { + const handleShowOriginal = useLastCallback(() => { showOriginalMessage({ chatId: message.chatId, id: message.id, }); closeMenu(); - }, [closeMenu, message, showOriginalMessage]); + }); - const handleSelectLanguage = useCallback(() => { + const handleSelectLanguage = useLastCallback(() => { openMessageLanguageModal({ chatId: message.chatId, id: message.id, }); closeMenu(); - }, [closeMenu, message.chatId, message.id, openMessageLanguageModal]); + }); const reportMessageIds = useMemo(() => (album ? album.messages : [message]).map(({ id }) => id), [album, message]); diff --git a/src/components/middle/message/InvoiceMediaPreview.tsx b/src/components/middle/message/InvoiceMediaPreview.tsx index 4950c86a0..6cdabd25d 100644 --- a/src/components/middle/message/InvoiceMediaPreview.tsx +++ b/src/components/middle/message/InvoiceMediaPreview.tsx @@ -1,4 +1,4 @@ -import React, { memo, useCallback } from '../../../lib/teact/teact'; +import React, { memo } from '../../../lib/teact/teact'; import { getActions } from '../../../global'; import type { FC } from '../../../lib/teact/teact'; @@ -9,6 +9,7 @@ import { formatCurrency } from '../../../util/formatCurrency'; import { formatMediaDuration } from '../../../util/dateFormat'; import buildClassName from '../../../util/buildClassName'; +import useLastCallback from '../../../hooks/useLastCallback'; import useLang from '../../../hooks/useLang'; import useInterval from '../../../hooks/useInterval'; @@ -33,9 +34,9 @@ const InvoiceMediaPreview: FC = ({ const { chatId, id } = message; - const refreshExtendedMedia = useCallback(() => { + const refreshExtendedMedia = useLastCallback(() => { loadExtendedMedia({ chatId, ids: [id] }); - }, [chatId, id, loadExtendedMedia]); + }); useInterval(refreshExtendedMedia, lastSyncTime ? POLLING_INTERVAL : undefined); @@ -49,13 +50,13 @@ const InvoiceMediaPreview: FC = ({ width, height, thumbnail, duration, } = extendedMedia!; - const handleClick = useCallback(() => { + const handleClick = useLastCallback(() => { openInvoice({ chatId, messageId: id, isExtendedMedia: true, }); - }, [chatId, id, openInvoice]); + }); return (
= ({ openUrl({ url }); }; - const updateCountdown = useCallback((countdownEl: HTMLDivElement) => { + const updateCountdown = useLastCallback((countdownEl: HTMLDivElement) => { if (type !== 'geoLive') return; const radius = 12; const circumference = radius * 2 * Math.PI; @@ -139,7 +140,7 @@ const Location: FC = ({ timerEl.textContent = text; svgEl.firstElementChild!.setAttribute('stroke-dashoffset', `-${strokeDashOffset}`); } - }, [type, message.date, location, lang]); + }); useLayoutEffect(() => { if (countdownRef.current) { diff --git a/src/components/middle/message/Message.tsx b/src/components/middle/message/Message.tsx index f4b74571f..08d5e8f3e 100644 --- a/src/components/middle/message/Message.tsx +++ b/src/components/middle/message/Message.tsx @@ -1,7 +1,6 @@ import type { FC } from '../../../lib/teact/teact'; import React, { memo, - useCallback, useEffect, useMemo, useRef, @@ -111,6 +110,7 @@ import { isElementInViewport } from '../../../util/isElementInViewport'; import { getCustomEmojiSize } from '../composer/helpers/customEmoji'; import { isAnimatingScroll } from '../../../util/animateScroll'; +import useLastCallback from '../../../hooks/useLastCallback'; import useEnsureMessage from '../../../hooks/useEnsureMessage'; import useContextMenuHandlers from '../../../hooks/useContextMenuHandlers'; import { useOnIntersect } from '../../../hooks/useIntersectionObserver'; @@ -470,14 +470,14 @@ const Message: FC = ({ const hasSubheader = hasTopicChip || hasReply; - const selectMessage = useCallback((e?: React.MouseEvent, groupedId?: string) => { + const selectMessage = useLastCallback((e?: React.MouseEvent, groupedId?: string) => { toggleMessageSelection({ messageId, groupedId, ...(e?.shiftKey && { withShift: true }), ...(isAlbum && { childMessageIds: album!.messages.map(({ id }) => id) }), }); - }, [toggleMessageSelection, messageId, isAlbum, album]); + }); const messageSender = canShowSender ? sender : undefined; const withVoiceTranscription = Boolean(!isTranscriptionHidden && (isTranscriptionError || transcribedText)); @@ -680,7 +680,7 @@ const Message: FC = ({ const shouldFocusOnResize = isLastInList; - const handleResize = useCallback((entry: ResizeObserverEntry) => { + const handleResize = useLastCallback((entry: ResizeObserverEntry) => { const lastHeight = messageHeightRef.current; const newHeight = entry.contentRect.height; @@ -701,7 +701,7 @@ const Message: FC = ({ if (previousScrollBottom <= BOTTOM_FOCUS_SCROLL_THRESHOLD) { focusLastMessage(); } - }, [focusLastMessage]); + }); const throttledResize = useThrottledCallback(handleResize, [handleResize], THROTTLE_MS, false); diff --git a/src/components/middle/message/MessageContextMenu.tsx b/src/components/middle/message/MessageContextMenu.tsx index eb8790a27..d596286b7 100644 --- a/src/components/middle/message/MessageContextMenu.tsx +++ b/src/components/middle/message/MessageContextMenu.tsx @@ -1,5 +1,5 @@ import React, { - memo, useCallback, useEffect, useMemo, useRef, + memo, useEffect, useMemo, useRef, } from '../../../lib/teact/teact'; import { getActions } from '../../../global'; @@ -23,6 +23,7 @@ import { getUserFullName } from '../../../global/helpers'; import buildClassName from '../../../util/buildClassName'; import renderText from '../../common/helpers/renderText'; +import useLastCallback from '../../../hooks/useLastCallback'; import useFlag from '../../../hooks/useFlag'; import useMenuPosition from '../../../hooks/useMenuPosition'; import useLang from '../../../hooks/useLang'; @@ -214,14 +215,14 @@ const MessageContextMenu: FC = ({ const { isMobile, isDesktop } = useAppLayout(); const seenByDatesCount = useMemo(() => (seenByDates ? Object.keys(seenByDates).length : 0), [seenByDates]); - const handleAfterCopy = useCallback(() => { + const handleAfterCopy = useLastCallback(() => { showNotification({ message: lang('Share.Link.Copied'), }); onClose(); - }, [lang, onClose, showNotification]); + }); - const handleOpenCustomEmojiSets = useCallback(() => { + const handleOpenCustomEmojiSets = useLastCallback(() => { if (!customEmojiSets) return; if (customEmojiSets.length === 1) { openStickerSet({ @@ -235,7 +236,7 @@ const MessageContextMenu: FC = ({ }); } onClose(); - }, [customEmojiSets, onClose, openCustomEmojiSets, openStickerSet]); + }); const copyOptions = isSponsoredMessage ? [] @@ -243,23 +244,17 @@ const MessageContextMenu: FC = ({ message, targetHref, handleAfterCopy, canCopyLink ? onCopyLink : undefined, onCopyMessages, onCopyNumber, ); - const getTriggerElement = useCallback(() => { + const getTriggerElement = useLastCallback(() => { return isSponsoredMessage ? document.querySelector('.Transition_slide-active > .MessageList .SponsoredMessage') : document.querySelector(`.Transition_slide-active > .MessageList div[data-message-id="${messageId}"]`); - }, [isSponsoredMessage, messageId]); + }); - const getRootElement = useCallback( - () => document.querySelector('.Transition_slide-active > .MessageList'), - [], - ); + const getRootElement = useLastCallback(() => document.querySelector('.Transition_slide-active > .MessageList')); - const getMenuElement = useCallback( - () => document.querySelector('.MessageContextMenu .bubble'), - [], - ); + const getMenuElement = useLastCallback(() => document.querySelector('.MessageContextMenu .bubble')); - const getLayout = useCallback(() => { + const getLayout = useLastCallback(() => { const extraHeightAudioPlayer = (isMobile && (document.querySelector('.AudioPlayer-content'))?.offsetHeight) || 0; const pinnedElement = document.querySelector('.HeaderPinnedMessageWrapper'); @@ -275,7 +270,7 @@ const MessageContextMenu: FC = ({ shouldAvoidNegativePosition: !isDesktop, menuElMinWidth: withReactions && isMobile ? REACTION_SELECTOR_WIDTH_REM * REM : undefined, }; - }, [isDesktop, isMobile, withReactions]); + }); useEffect(() => { if (!isOpen) { @@ -298,10 +293,10 @@ const MessageContextMenu: FC = ({ return enableScrolling; }, [withScroll]); - const handleOpenReactionPicker = useCallback((position: IAnchorPosition) => { + const handleOpenReactionPicker = useLastCallback((position: IAnchorPosition) => { onReactionPickerOpen!(position); hideItems(); - }, [onReactionPickerOpen]); + }); return ( = ({ const isMissed = reason === 'missed'; const isCancelled = reason === 'busy' && !isOutgoing; - const handleCall = useCallback(() => { + const handleCall = useLastCallback(() => { requestMasterAndRequestCall({ isVideo, userId: chatId }); - }, [chatId, isVideo, requestMasterAndRequestCall]); + }); const reasonText = useMemo(() => { if (isVideo) { diff --git a/src/components/middle/message/Photo.tsx b/src/components/middle/message/Photo.tsx index eb990b16d..2569ad1dd 100644 --- a/src/components/middle/message/Photo.tsx +++ b/src/components/middle/message/Photo.tsx @@ -1,6 +1,4 @@ -import React, { - useCallback, useRef, useState, -} from '../../../lib/teact/teact'; +import React, { useRef, useState } from '../../../lib/teact/teact'; import { requestMutation } from '../../../lib/fasterdom/fasterdom'; import type { FC } from '../../../lib/teact/teact'; @@ -23,6 +21,7 @@ import buildClassName from '../../../util/buildClassName'; import getCustomAppendixBg from './helpers/getCustomAppendixBg'; import { calculateMediaDimensions, MIN_MEDIA_HEIGHT } from './helpers/mediaDimensions'; +import useLastCallback from '../../../hooks/useLastCallback'; import { useIsIntersecting } from '../../../hooks/useIntersectionObserver'; import useMediaWithLoadProgress from '../../../hooks/useMediaWithLoadProgress'; import useShowTransition from '../../../hooks/useShowTransition'; @@ -129,7 +128,7 @@ const Photo: FC = ({ transitionClassNames: downloadButtonClassNames, } = useShowTransition(!fullMediaData && !isLoadAllowed); - const handleClick = useCallback(() => { + const handleClick = useLastCallback(() => { if (isUploading) { onCancelUpload?.(message); return; @@ -146,7 +145,7 @@ const Photo: FC = ({ } onClick?.(message.id); - }, [fullMediaData, hideSpoiler, isSpoilerShown, isUploading, message, onCancelUpload, onClick]); + }); const isOwn = isOwnMessage(message); useLayoutEffectWithPrevDeps(([prevShouldAffectAppendix]) => { diff --git a/src/components/middle/message/Poll.tsx b/src/components/middle/message/Poll.tsx index 03a11b24c..bcfb1b190 100644 --- a/src/components/middle/message/Poll.tsx +++ b/src/components/middle/message/Poll.tsx @@ -1,6 +1,5 @@ import type { FC } from '../../../lib/teact/teact'; import React, { - useCallback, useEffect, useState, memo, @@ -20,6 +19,8 @@ import type { LangFn } from '../../../hooks/useLang'; import useLang from '../../../hooks/useLang'; import { getServerTimeOffset } from '../../../util/serverTime'; +import useLastCallback from '../../../hooks/useLastCallback'; + import CheckboxGroup from '../../ui/CheckboxGroup'; import RadioGroup from '../../ui/RadioGroup'; import Avatar from '../../common/Avatar'; @@ -161,43 +162,35 @@ const Poll: FC = ({ }, []) : []; }, [usersById, recentVoterIds]); - const handleRadioChange = useCallback( - (option: string) => { - setChosenOptions([option]); - setIsSubmitting(true); - setWasSubmitted(true); - onSendVote([option]); - }, [onSendVote], - ); + const handleRadioChange = useLastCallback((option: string) => { + setChosenOptions([option]); + setIsSubmitting(true); + setWasSubmitted(true); + onSendVote([option]); + }); - const handleCheckboxChange = useCallback( - (options: string[]) => { - setChosenOptions(options); - }, [], - ); + const handleCheckboxChange = useLastCallback((options: string[]) => { + setChosenOptions(options); + }); - const handleVoteClick = useCallback( - () => { - setIsSubmitting(true); - setWasSubmitted(true); - onSendVote(chosenOptions); - }, [onSendVote, chosenOptions], - ); + const handleVoteClick = useLastCallback(() => { + setIsSubmitting(true); + setWasSubmitted(true); + onSendVote(chosenOptions); + }); - const handleViewResultsClick = useCallback( - () => { - openPollResults({ chatId, messageId }); - }, [chatId, messageId, openPollResults], - ); + const handleViewResultsClick = useLastCallback(() => { + openPollResults({ chatId, messageId }); + }); - const handleSolutionShow = useCallback(() => { + const handleSolutionShow = useLastCallback(() => { setIsSolutionShown(true); - }, []); + }); - const handleSolutionHide = useCallback(() => { + const handleSolutionHide = useLastCallback(() => { setIsSolutionShown(false); setWasSubmitted(false); - }, []); + }); // Show the solution to quiz if the answer was incorrect useEffect(() => { diff --git a/src/components/middle/message/ReactionAnimatedEmoji.tsx b/src/components/middle/message/ReactionAnimatedEmoji.tsx index 915297eeb..7ec0bd543 100644 --- a/src/components/middle/message/ReactionAnimatedEmoji.tsx +++ b/src/components/middle/message/ReactionAnimatedEmoji.tsx @@ -1,5 +1,5 @@ import React, { - memo, useCallback, useMemo, useRef, + memo, useMemo, useRef, } from '../../../lib/teact/teact'; import { getActions } from '../../../global'; @@ -24,6 +24,7 @@ import AnimatedSticker from '../../common/AnimatedSticker'; import CustomEmojiEffect from './CustomEmojiEffect'; import styles from './ReactionAnimatedEmoji.module.scss'; +import useLastCallback from '../../../hooks/useLastCallback'; type OwnProps = { reaction: ApiReaction; @@ -101,10 +102,10 @@ const ReactionAnimatedEmoji: FC = ({ transitionClassNames: animationClassNames, } = useShowTransition(shouldPlay, undefined, true, 'slow'); - const handleEnded = useCallback(() => { + const handleEnded = useLastCallback(() => { if (!activeReaction?.messageId) return; stopActiveReaction({ messageId: activeReaction.messageId, reaction }); - }, [activeReaction?.messageId, reaction, stopActiveReaction]); + }); const [isAnimationLoaded, markAnimationLoaded, unmarkAnimationLoaded] = useFlag(); const shouldRenderStatic = !isCustom && (!shouldPlay || !isAnimationLoaded); diff --git a/src/components/middle/message/ReactionButton.tsx b/src/components/middle/message/ReactionButton.tsx index 4e7935b6f..341c362ed 100644 --- a/src/components/middle/message/ReactionButton.tsx +++ b/src/components/middle/message/ReactionButton.tsx @@ -1,4 +1,4 @@ -import React, { memo, useCallback, useMemo } from '../../../lib/teact/teact'; +import React, { memo, useMemo } from '../../../lib/teact/teact'; import { getActions, getGlobal } from '../../../global'; import type { FC } from '../../../lib/teact/teact'; @@ -12,6 +12,8 @@ import buildClassName from '../../../util/buildClassName'; import { formatIntegerCompact } from '../../../util/textFormat'; import { isSameReaction, isReactionChosen } from '../../../global/helpers'; +import useLastCallback from '../../../hooks/useLastCallback'; + import Button from '../../ui/Button'; import Avatar from '../../common/Avatar'; import ReactionAnimatedEmoji from './ReactionAnimatedEmoji'; @@ -55,13 +57,13 @@ const ReactionButton: FC<{ .filter(Boolean) as ApiUser[]; }, [reaction.reaction, recentReactions, withRecentReactors]); - const handleClick = useCallback(() => { + const handleClick = useLastCallback(() => { toggleReaction({ reaction: reaction.reaction, chatId: message.chatId, messageId: message.id, }); - }, [message, reaction, toggleReaction]); + }); return (