[Refactoring] ESLint: Add addCallback where possible (#1780)

This commit is contained in:
Alexander Zinchuk 2022-03-25 13:14:45 +01:00
parent f198cd96b6
commit 41a67c9baf
68 changed files with 306 additions and 152 deletions

View File

@ -73,8 +73,8 @@
"react/style-prop-object": "off",
"react/jsx-no-bind": ["error", {
"ignoreRefs": true,
"allowArrowFunctions": true,
"allowFunctions": true,
"allowArrowFunctions": false,
"allowFunctions": false,
"allowBind": false,
"ignoreDOMComponents": true
}],

View File

@ -1,5 +1,7 @@
import { ChangeEvent } from 'react';
import React, { FC, useState, memo } from '../../lib/teact/teact';
import React, {
FC, useState, memo, useCallback,
} from '../../lib/teact/teact';
import { getActions, withGlobal } from '../../global';
import { GlobalState } from '../../global/types';
@ -24,7 +26,7 @@ const AuthRegister: FC<StateProps> = ({
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
function handleFirstNameChange(event: ChangeEvent<HTMLInputElement>) {
const handleFirstNameChange = useCallback((event: ChangeEvent<HTMLInputElement>) => {
if (authError) {
clearAuthError();
}
@ -33,13 +35,13 @@ const AuthRegister: FC<StateProps> = ({
setFirstName(target.value);
setIsButtonShown(target.value.length > 0);
}
}, [authError, clearAuthError]);
function handleLastNameChange(event: ChangeEvent<HTMLInputElement>) {
const handleLastNameChange = useCallback((event: ChangeEvent<HTMLInputElement>) => {
const { target } = event;
setLastName(target.value);
}
}, []);
function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault();

View File

@ -138,6 +138,7 @@ const CountryCodeInput: FC<OwnProps & StateProps> = ({
<MenuItem
key={`${country.iso2}-${country.countryCode}`}
className={value && country.iso2 === value.iso2 ? 'selected' : ''}
// eslint-disable-next-line react/jsx-no-bind
onClick={() => handleChange(country)}
>
<span className="country-flag">{renderText(isoToEmoji(country.iso2), ['hq_emoji'])}</span>

View File

@ -1,4 +1,6 @@
import React, { FC, memo, useState } from '../../lib/teact/teact';
import React, {
FC, memo, useCallback, useState,
} from '../../lib/teact/teact';
import { getActions, withGlobal } from '../../global';
import ConfirmDialog from '../ui/ConfirmDialog';
@ -30,13 +32,15 @@ const CallFallbackConfirm: FC<OwnProps & StateProps> = ({
const [shouldRemove, setShouldRemove] = useState(true);
const renderingUserFullName = useCurrentOrPrev(userFullName, true);
const handleConfirm = useCallback(() => {
inviteToCallFallback({ shouldRemove });
}, [inviteToCallFallback, shouldRemove]);
return (
<ConfirmDialog
title="Start Call"
isOpen={isOpen}
confirmHandler={() => {
inviteToCallFallback({ shouldRemove });
}}
confirmHandler={handleConfirm}
onClose={closeCallFallbackConfirm}
>
<p>The call will be started in a private channel <b>{channelTitle}</b>.</p>

View File

@ -123,10 +123,10 @@ const GroupCall: FC<OwnProps & StateProps> = ({
}
}, [connectionState, playGroupCallSound]);
const handleCloseConfirmLeaveModal = () => {
const handleCloseConfirmLeaveModal = useCallback(() => {
closeConfirmLeaveModal();
setIsEndGroupCallModal(false);
};
}, [closeConfirmLeaveModal]);
const MainButton: FC<{ onTrigger: () => void; isOpen?: boolean }> = useMemo(() => {
return ({ onTrigger, isOpen }) => (
@ -153,13 +153,13 @@ const GroupCall: FC<OwnProps & StateProps> = ({
}
}, [closeFullscreen, isFullscreen, openFullscreen]);
const handleToggleSidebar = () => {
const handleToggleSidebar = useCallback(() => {
if (isSidebarOpen) {
closeSidebar();
} else {
openSidebar();
}
};
}, [closeSidebar, isSidebarOpen, openSidebar]);
const handleStreamsDoubleClick = useCallback(() => {
if (!IS_REQUEST_FULLSCREEN_SUPPORTED) return;
@ -180,12 +180,12 @@ const GroupCall: FC<OwnProps & StateProps> = ({
}
}, [closeFullscreen, isFullscreen, openFullscreen]);
const handleClose = () => {
const handleClose = useCallback(() => {
toggleGroupCallPanel();
if (isFullscreen) {
closeFullscreen();
}
};
}, [closeFullscreen, isFullscreen, toggleGroupCallPanel]);
useEffect(() => {
if (!IS_REQUEST_FULLSCREEN_SUPPORTED) return undefined;
@ -211,16 +211,16 @@ const GroupCall: FC<OwnProps & StateProps> = ({
connectToActiveGroupCall();
}, [connectToActiveGroupCall, groupCallId]);
const endGroupCall = () => {
const endGroupCall = useCallback(() => {
setIsEndGroupCallModal(true);
setShouldEndGroupCall(true);
openConfirmLeaveModal();
if (isFullscreen) {
handleToggleFullscreen();
}
};
}, [handleToggleFullscreen, isFullscreen, openConfirmLeaveModal]);
const handleLeaveGroupCall = () => {
const handleLeaveGroupCall = useCallback(() => {
if (isAdmin && !isConfirmLeaveModalOpen) {
openConfirmLeaveModal();
if (isFullscreen) {
@ -231,15 +231,18 @@ const GroupCall: FC<OwnProps & StateProps> = ({
playGroupCallSound({ sound: 'leave' });
setIsLeaving(true);
closeConfirmLeaveModal();
};
}, [
closeConfirmLeaveModal, handleToggleFullscreen, isAdmin, isConfirmLeaveModalOpen, isFullscreen,
openConfirmLeaveModal, playGroupCallSound,
]);
const handleCloseAnimationEnd = () => {
const handleCloseAnimationEnd = useCallback(() => {
if (isLeaving) {
leaveGroupCall({
shouldDiscard: shouldEndGroupCall,
});
}
};
}, [isLeaving, leaveGroupCall, shouldEndGroupCall]);
return (
<Modal

View File

@ -163,9 +163,9 @@ const CalendarModal: FC<OwnProps> = ({
});
}
function handleSubmit() {
const handleSubmit = useCallback(() => {
onSubmit(selectedDate);
}
}, [onSubmit, selectedDate]);
const handleChangeHours = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value.replace(/[^\d]+/g, '');

View File

@ -86,6 +86,7 @@ const ChatExtra: FC<OwnProps & StateProps> = ({
return (
<div className="ChatExtra">
{formattedNumber && Boolean(formattedNumber.length) && (
// eslint-disable-next-line react/jsx-no-bind
<ListItem icon="phone" multiline narrow ripple onClick={() => copy(formattedNumber, lang('Phone'))}>
<span className="title" dir="auto">{formattedNumber}</span>
<span className="subtitle">{lang('Phone')}</span>
@ -97,6 +98,7 @@ const ChatExtra: FC<OwnProps & StateProps> = ({
multiline
narrow
ripple
// eslint-disable-next-line react/jsx-no-bind
onClick={() => copy(`@${username}`, lang('Username'))}
>
<span className="title" dir="auto">{renderText(username)}</span>
@ -122,6 +124,7 @@ const ChatExtra: FC<OwnProps & StateProps> = ({
multiline
narrow
ripple
// eslint-disable-next-line react/jsx-no-bind
onClick={() => copy(link, lang('SetUrlPlaceholder'))}
>
<div className="title">{link}</div>

View File

@ -113,6 +113,7 @@ const ChatOrUserPicker: FC<OwnProps> = ({
key={id}
className="chat-item-clickable force-rounded-corners"
style={`top: ${(viewportOffset + i) * CHAT_HEIGHT_PX}px;`}
// eslint-disable-next-line react/jsx-no-bind
onClick={() => onSelectChatOrUser(id)}
>
{isUserId(id) ? (

View File

@ -115,6 +115,7 @@ const Picker: FC<OwnProps> = ({
<ListItem
key={id}
className="chat-item-clickable picker-list-item"
// eslint-disable-next-line react/jsx-no-bind
onClick={() => handleItemClick(id)}
ripple
>

View File

@ -33,11 +33,11 @@ const ReportMessageModal: FC<OwnProps> = ({
const [selectedReason, setSelectedReason] = useState<ApiReportReason>('spam');
const [description, setDescription] = useState('');
const handleReport = () => {
const handleReport = useCallback(() => {
reportMessages({ messageIds, reason: selectedReason, description });
exitMessageSelectMode();
onClose();
};
}, [description, exitMessageSelectMode, messageIds, onClose, reportMessages, selectedReason]);
const handleSelectReason = useCallback((value: string) => {
setSelectedReason(value as ApiReportReason);

View File

@ -53,6 +53,7 @@ const SeenByModal: FC<OwnProps & StateProps> = ({
<ListItem
key={userId}
className="chat-item-clickable scroll-item small-icon"
// eslint-disable-next-line react/jsx-no-bind
onClick={() => handleClick(userId)}
>
<PrivateChatInfo userId={userId} noStatusOrTyping />

View File

@ -140,28 +140,28 @@ const StickerButton = <T extends number | ApiSticker | ApiBotInlineMediaResult |
handleBeforeContextMenu(e);
};
const handleUnfaveClick = (e: ReactMouseEvent<HTMLButtonElement, MouseEvent>) => {
const handleUnfaveClick = useCallback((e: ReactMouseEvent<HTMLButtonElement, MouseEvent>) => {
e.stopPropagation();
e.preventDefault();
onUnfaveClick!(sticker);
};
}, [onUnfaveClick, sticker]);
const handleContextUnfave = () => {
const handleContextUnfave = useCallback(() => {
onUnfaveClick!(sticker);
};
}, [onUnfaveClick, sticker]);
const handleContextFave = () => {
const handleContextFave = useCallback(() => {
onFaveClick!(sticker);
};
}, [onFaveClick, sticker]);
const handleSendQuiet = () => {
const handleSendQuiet = useCallback(() => {
onClick?.(clickArg, true);
};
}, [clickArg, onClick]);
const handleSendScheduled = () => {
const handleSendScheduled = useCallback(() => {
onClick?.(clickArg, undefined, true);
};
}, [clickArg, onClick]);
const fullClassName = buildClassName(
'StickerButton',

View File

@ -286,10 +286,10 @@ const LeftColumn: FC<StateProps> = ({
initResize, resetResize, handleMouseUp,
} = useResize(resizeRef, setLeftColumnWidth, resetLeftColumnWidth, leftColumnWidth);
const handleSettingsScreenSelect = (screen: SettingsScreens) => {
const handleSettingsScreenSelect = useCallback((screen: SettingsScreens) => {
setContent(LeftColumnContent.Settings);
setSettingsScreen(screen);
};
}, []);
return (
<div

View File

@ -1,5 +1,5 @@
import React, {
FC, useState, useEffect, memo,
FC, useState, useEffect, memo, useCallback,
} from '../../lib/teact/teact';
import buildClassName from '../../util/buildClassName';
@ -40,13 +40,13 @@ const NewChatButton: FC<OwnProps> = ({
isMenuOpen && 'menu-is-open',
);
const toggleIsMenuOpen = () => {
const toggleIsMenuOpen = useCallback(() => {
setIsMenuOpen(!isMenuOpen);
};
}, [isMenuOpen]);
const handleClose = () => {
const handleClose = useCallback(() => {
setIsMenuOpen(false);
};
}, []);
return (
<div className={fabClassName}>

View File

@ -83,6 +83,7 @@ const ContactList: FC<OwnProps & StateProps> = ({
<ListItem
key={id}
className="chat-item-clickable"
// eslint-disable-next-line react/jsx-no-bind
onClick={() => handleClick(id)}
ripple={!IS_SINGLE_COLUMN_LAYOUT}
>

View File

@ -134,6 +134,7 @@ const LeftMainHeader: FC<OwnProps & StateProps> = ({
size="smaller"
color="translucent"
className={isOpen ? 'active' : ''}
// eslint-disable-next-line react/jsx-no-bind
onClick={hasMenu ? onTrigger : () => onReset()}
ariaLabel={hasMenu ? lang('AccDescrOpenMenu2') : 'Return to chat list'}
>
@ -181,15 +182,15 @@ const LeftMainHeader: FC<OwnProps & StateProps> = ({
setSettingOption({ animationLevel: newLevel });
}, [animationLevel, setSettingOption]);
const handleSwitchToWebK = () => {
const handleSwitchToWebK = useCallback(() => {
setPermanentWebVersion('K');
clearWebsync();
disableHistoryBack();
};
}, []);
const handleOpenTipsChat = () => {
const handleOpenTipsChat = useCallback(() => {
openTipsChat({ langCode: lang.code });
};
}, [lang.code, openTipsChat]);
const isSearchFocused = (
Boolean(globalSearchChatId)

View File

@ -125,6 +125,7 @@ const NewChatStep2: FC<OwnProps & StateProps > = ({
round
size="smaller"
color="translucent"
// eslint-disable-next-line react/jsx-no-bind
onClick={() => onReset()}
ariaLabel="Return to member selection"
>

View File

@ -1,5 +1,5 @@
import React, {
FC, memo,
FC, memo, useCallback,
} from '../../../lib/teact/teact';
import { withGlobal } from '../../../global';
@ -56,9 +56,9 @@ const LeftSearchResultChat: FC<OwnProps & StateProps> = ({
handleChatFolderChange: openChatFolderModal,
}, true);
const handleClick = () => {
const handleClick = useCallback(() => {
onClick(chatId);
};
}, [chatId, onClick]);
const buttonRef = useSelectWithEnter(handleClick);

View File

@ -69,6 +69,14 @@ const SettingsDataStorage: FC<OwnProps & StateProps> = ({
setSettingOption({ autoLoadFileMaxSizeMb: AUTODOWNLOAD_FILESIZE_MB_LIMITS[value] });
}, [setSettingOption]);
const handleCanAutoPlayGifsChange = useCallback((value: boolean) => {
setSettingOption({ canAutoPlayGifs: value });
}, [setSettingOption]);
const handleCanAutoPlayVideosChange = useCallback((value: boolean) => {
setSettingOption({ canAutoPlayVideos: value });
}, [setSettingOption]);
function renderContentSizeSlider() {
const value = AUTODOWNLOAD_FILESIZE_MB_LIMITS.indexOf(autoLoadFileMaxSizeMb);
@ -101,21 +109,26 @@ const SettingsDataStorage: FC<OwnProps & StateProps> = ({
<Checkbox
label={lang('AutoDownloadSettings.Contacts')}
checked={canAutoLoadFromContacts}
// TODO rewrite to support `useCallback`
// eslint-disable-next-line react/jsx-no-bind
onCheck={(isChecked) => setSettingOption({ [`canAutoLoad${key}FromContacts`]: isChecked })}
/>
<Checkbox
label={lang('AutoDownloadSettings.PrivateChats')}
checked={canAutoLoadInPrivateChats}
// eslint-disable-next-line react/jsx-no-bind
onCheck={(isChecked) => setSettingOption({ [`canAutoLoad${key}InPrivateChats`]: isChecked })}
/>
<Checkbox
label={lang('AutoDownloadSettings.GroupChats')}
checked={canAutoLoadInGroups}
// eslint-disable-next-line react/jsx-no-bind
onCheck={(isChecked) => setSettingOption({ [`canAutoLoad${key}InGroups`]: isChecked })}
/>
<Checkbox
label={lang('AutoDownloadSettings.Channels')}
checked={canAutoLoadInChannels}
// eslint-disable-next-line react/jsx-no-bind
onCheck={(isChecked) => setSettingOption({ [`canAutoLoad${key}InChannels`]: isChecked })}
/>
@ -157,12 +170,12 @@ const SettingsDataStorage: FC<OwnProps & StateProps> = ({
<Checkbox
label={lang('GifsTab2')}
checked={canAutoPlayGifs}
onCheck={(isChecked) => setSettingOption({ canAutoPlayGifs: isChecked })}
onCheck={handleCanAutoPlayGifsChange}
/>
<Checkbox
label={lang('DataAndStorage.Autoplay.Videos')}
checked={canAutoPlayVideos}
onCheck={(isChecked) => setSettingOption({ canAutoPlayVideos: isChecked })}
onCheck={handleCanAutoPlayVideosChange}
/>
</div>
</div>

View File

@ -122,6 +122,18 @@ const SettingsGeneral: FC<OwnProps & StateProps> = ({
openModal();
}, [openModal]);
const handleMessageSendComboChange = useCallback((newCombo: string) => {
setSettingOption({ messageSendKeyCombo: newCombo });
}, [setSettingOption]);
const handleSuggestStickersChange = useCallback((newValue: boolean) => {
setSettingOption({ shouldSuggestStickers: newValue });
}, [setSettingOption]);
const handleShouldLoopStickersChange = useCallback((newValue: boolean) => {
setSettingOption({ shouldLoopStickers: newValue });
}, [setSettingOption]);
const stickerSets = stickerSetIds && stickerSetIds.map((id: string) => {
return stickerSetsById?.[id]?.installedDate ? stickerSetsById[id] : false;
}).filter<ApiStickerSet>(Boolean as any);
@ -143,6 +155,7 @@ const SettingsGeneral: FC<OwnProps & StateProps> = ({
<ListItem
icon="photo"
// eslint-disable-next-line react/jsx-no-bind
onClick={() => onScreenSelect(SettingsScreens.GeneralChatBackground)}
>
{lang('ChatBackground')}
@ -183,7 +196,7 @@ const SettingsGeneral: FC<OwnProps & StateProps> = ({
<RadioGroup
name="keyboard-send-settings"
options={KEYBOARD_SEND_OPTIONS}
onChange={(value) => setSettingOption({ messageSendKeyCombo: value })}
onChange={handleMessageSendComboChange}
selected={messageSendKeyCombo}
/>
</div>
@ -195,6 +208,7 @@ const SettingsGeneral: FC<OwnProps & StateProps> = ({
{defaultReaction && (
<ListItem
className="SettingsDefaultReaction"
// eslint-disable-next-line react/jsx-no-bind
onClick={() => onScreenSelect(SettingsScreens.QuickReaction)}
>
<ReactionStaticEmoji reaction={defaultReaction} />
@ -205,12 +219,12 @@ const SettingsGeneral: FC<OwnProps & StateProps> = ({
<Checkbox
label={lang('SuggestStickers')}
checked={shouldSuggestStickers}
onCheck={(isChecked) => setSettingOption({ shouldSuggestStickers: isChecked })}
onCheck={handleSuggestStickersChange}
/>
<Checkbox
label={lang('LoopAnimatedStickers')}
checked={shouldLoopStickers}
onCheck={(isChecked) => setSettingOption({ shouldLoopStickers: isChecked })}
onCheck={handleShouldLoopStickersChange}
/>
<div className="mt-4" ref={stickerSettingsRef}>

View File

@ -215,6 +215,7 @@ const SettingsHeader: FC<OwnProps> = ({
ripple={!IS_SINGLE_COLUMN_LAYOUT}
size="smaller"
color="translucent"
// eslint-disable-next-line react/jsx-no-bind
onClick={() => onScreenSelect(SettingsScreens.EditProfile)}
ariaLabel={lang('lng_settings_information')}
>

View File

@ -60,36 +60,42 @@ const SettingsMain: FC<OwnProps & StateProps> = ({
)}
<ListItem
icon="settings"
// eslint-disable-next-line react/jsx-no-bind
onClick={() => onScreenSelect(SettingsScreens.General)}
>
{lang('Telegram.GeneralSettingsViewController')}
</ListItem>
<ListItem
icon="unmute"
// eslint-disable-next-line react/jsx-no-bind
onClick={() => onScreenSelect(SettingsScreens.Notifications)}
>
{lang('Notifications')}
</ListItem>
<ListItem
icon="lock"
// eslint-disable-next-line react/jsx-no-bind
onClick={() => onScreenSelect(SettingsScreens.Privacy)}
>
{lang('PrivacySettings')}
</ListItem>
<ListItem
icon="data"
// eslint-disable-next-line react/jsx-no-bind
onClick={() => onScreenSelect(SettingsScreens.DataStorage)}
>
{lang('DataSettings')}
</ListItem>
<ListItem
icon="folder"
// eslint-disable-next-line react/jsx-no-bind
onClick={() => onScreenSelect(SettingsScreens.Folders)}
>
{lang('Filters')}
</ListItem>
<ListItem
icon="language"
// eslint-disable-next-line react/jsx-no-bind
onClick={() => onScreenSelect(SettingsScreens.Language)}
>
{lang('Language')}

View File

@ -85,12 +85,55 @@ const SettingsNotifications: FC<OwnProps & StateProps> = ({
updateNotificationSettings,
]);
const handleWebNotificationsChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
updateWebNotificationSettings({
hasWebNotifications: e.target.checked,
});
}, [updateWebNotificationSettings]);
const handlePushNotificationsChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
updateWebNotificationSettings({
hasPushNotifications: e.target.checked,
});
}, [updateWebNotificationSettings]);
const handlePrivateChatsNotificationsChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
handleSettingsChange(e, 'contact', 'silent');
}, [handleSettingsChange]);
const handlePrivateChatsPreviewChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
handleSettingsChange(e, 'contact', 'showPreviews');
}, [handleSettingsChange]);
const handleGroupsNotificationsChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
handleSettingsChange(e, 'group', 'silent');
}, [handleSettingsChange]);
const handleGroupsPreviewChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
handleSettingsChange(e, 'group', 'showPreviews');
}, [handleSettingsChange]);
const handleChannelsNotificationsChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
handleSettingsChange(e, 'broadcast', 'silent');
}, [handleSettingsChange]);
const handleChannelsPreviewChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
handleSettingsChange(e, 'broadcast', 'showPreviews');
}, [handleSettingsChange]);
const handleContactNotificationChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
updateContactSignUpNotification({
isSilent: !e.target.checked,
});
}, [updateContactSignUpNotification]);
const handleVolumeChange = useCallback((volume: number) => {
updateWebNotificationSettings({
notificationSoundVolume: volume,
});
runDebounced(() => playNotifySound(undefined, volume));
}, [runDebounced, updateWebNotificationSettings]);
const lang = useLang();
useHistoryBack(isActive, onReset, onScreenSelect, SettingsScreens.Notifications);
@ -106,9 +149,7 @@ const SettingsNotifications: FC<OwnProps & StateProps> = ({
// eslint-disable-next-line max-len
subLabel={lang(hasWebNotifications ? 'UserInfo.NotificationsEnabled' : 'UserInfo.NotificationsDisabled')}
checked={hasWebNotifications}
onChange={(e) => {
updateWebNotificationSettings({ hasWebNotifications: e.target.checked });
}}
onChange={handleWebNotificationsChange}
/>
<Checkbox
label="Offline notifications"
@ -116,9 +157,7 @@ const SettingsNotifications: FC<OwnProps & StateProps> = ({
// eslint-disable-next-line max-len
subLabel={lang(hasPushNotifications ? 'UserInfo.NotificationsEnabled' : 'UserInfo.NotificationsDisabled')}
checked={hasPushNotifications}
onChange={(e) => {
updateWebNotificationSettings({ hasPushNotifications: e.target.checked });
}}
onChange={handlePushNotificationsChange}
/>
<div className="settings-item-slider">
<RangeSlider
@ -126,10 +165,7 @@ const SettingsNotifications: FC<OwnProps & StateProps> = ({
min={0}
max={10}
value={notificationSoundVolume}
onChange={(volume) => {
updateWebNotificationSettings({ notificationSoundVolume: volume });
runDebounced(() => playNotifySound(undefined, volume));
}}
onChange={handleVolumeChange}
/>
</div>
</div>
@ -143,9 +179,7 @@ const SettingsNotifications: FC<OwnProps & StateProps> = ({
// eslint-disable-next-line max-len
subLabel={lang(hasPrivateChatsNotifications ? 'UserInfo.NotificationsEnabled' : 'UserInfo.NotificationsDisabled')}
checked={hasPrivateChatsNotifications}
onChange={(e) => {
handleSettingsChange(e, 'contact', 'silent');
}}
onChange={handlePrivateChatsNotificationsChange}
/>
<Checkbox
label={lang('MessagePreview')}
@ -153,9 +187,7 @@ const SettingsNotifications: FC<OwnProps & StateProps> = ({
// eslint-disable-next-line max-len
subLabel={lang(hasPrivateChatsMessagePreview ? 'UserInfo.NotificationsEnabled' : 'UserInfo.NotificationsDisabled')}
checked={hasPrivateChatsMessagePreview}
onChange={(e) => {
handleSettingsChange(e, 'contact', 'showPreviews');
}}
onChange={handlePrivateChatsPreviewChange}
/>
</div>
@ -166,18 +198,14 @@ const SettingsNotifications: FC<OwnProps & StateProps> = ({
label={lang('NotificationsForGroups')}
subLabel={lang(hasGroupNotifications ? 'UserInfo.NotificationsEnabled' : 'UserInfo.NotificationsDisabled')}
checked={hasGroupNotifications}
onChange={(e) => {
handleSettingsChange(e, 'group', 'silent');
}}
onChange={handleGroupsNotificationsChange}
/>
<Checkbox
label={lang('MessagePreview')}
disabled={!hasGroupNotifications}
subLabel={lang(hasGroupMessagePreview ? 'UserInfo.NotificationsEnabled' : 'UserInfo.NotificationsDisabled')}
checked={hasGroupMessagePreview}
onChange={(e) => {
handleSettingsChange(e, 'group', 'showPreviews');
}}
onChange={handleGroupsPreviewChange}
/>
</div>
@ -189,9 +217,7 @@ const SettingsNotifications: FC<OwnProps & StateProps> = ({
// eslint-disable-next-line max-len
subLabel={lang(hasBroadcastNotifications ? 'UserInfo.NotificationsEnabled' : 'UserInfo.NotificationsDisabled')}
checked={hasBroadcastNotifications}
onChange={(e) => {
handleSettingsChange(e, 'broadcast', 'silent');
}}
onChange={handleChannelsNotificationsChange}
/>
<Checkbox
label={lang('MessagePreview')}
@ -199,9 +225,7 @@ const SettingsNotifications: FC<OwnProps & StateProps> = ({
// eslint-disable-next-line max-len
subLabel={lang(hasBroadcastMessagePreview ? 'UserInfo.NotificationsEnabled' : 'UserInfo.NotificationsDisabled')}
checked={hasBroadcastMessagePreview}
onChange={(e) => {
handleSettingsChange(e, 'broadcast', 'showPreviews');
}}
onChange={handleChannelsPreviewChange}
/>
</div>

View File

@ -84,6 +84,7 @@ const SettingsPrivacy: FC<OwnProps & StateProps> = ({
<ListItem
icon="delete-user"
narrow
// eslint-disable-next-line react/jsx-no-bind
onClick={() => onScreenSelect(SettingsScreens.PrivacyBlockedUsers)}
>
<div className="multiline-menu-item">
@ -98,6 +99,7 @@ const SettingsPrivacy: FC<OwnProps & StateProps> = ({
<ListItem
icon="lock"
narrow
// eslint-disable-next-line react/jsx-no-bind
onClick={() => onScreenSelect(
hasPassword ? SettingsScreens.TwoFaEnabled : SettingsScreens.TwoFaDisabled,
)}
@ -112,6 +114,7 @@ const SettingsPrivacy: FC<OwnProps & StateProps> = ({
<ListItem
icon="active-sessions"
narrow
// eslint-disable-next-line react/jsx-no-bind
onClick={() => onScreenSelect(SettingsScreens.PrivacyActiveSessions)}
>
<div className="multiline-menu-item">
@ -131,6 +134,7 @@ const SettingsPrivacy: FC<OwnProps & StateProps> = ({
<ListItem
narrow
className="no-icon"
// eslint-disable-next-line react/jsx-no-bind
onClick={() => onScreenSelect(SettingsScreens.PrivacyPhoneNumber)}
>
<div className="multiline-menu-item">
@ -143,6 +147,7 @@ const SettingsPrivacy: FC<OwnProps & StateProps> = ({
<ListItem
narrow
className="no-icon"
// eslint-disable-next-line react/jsx-no-bind
onClick={() => onScreenSelect(SettingsScreens.PrivacyLastSeen)}
>
<div className="multiline-menu-item">
@ -155,6 +160,7 @@ const SettingsPrivacy: FC<OwnProps & StateProps> = ({
<ListItem
narrow
className="no-icon"
// eslint-disable-next-line react/jsx-no-bind
onClick={() => onScreenSelect(SettingsScreens.PrivacyProfilePhoto)}
>
<div className="multiline-menu-item">
@ -167,6 +173,7 @@ const SettingsPrivacy: FC<OwnProps & StateProps> = ({
<ListItem
narrow
className="no-icon"
// eslint-disable-next-line react/jsx-no-bind
onClick={() => onScreenSelect(SettingsScreens.PrivacyForwarding)}
>
<div className="multiline-menu-item">
@ -179,6 +186,7 @@ const SettingsPrivacy: FC<OwnProps & StateProps> = ({
<ListItem
narrow
className="no-icon"
// eslint-disable-next-line react/jsx-no-bind
onClick={() => onScreenSelect(SettingsScreens.PrivacyGroupChats)}
>
<div className="multiline-menu-item">

View File

@ -176,6 +176,7 @@ const SettingsPrivacyVisibility: FC<OwnProps & StateProps> = ({
<ListItem
narrow
icon="add-user"
// eslint-disable-next-line react/jsx-no-bind
onClick={() => {
onScreenSelect(allowedContactsScreen);
}}
@ -191,6 +192,7 @@ const SettingsPrivacyVisibility: FC<OwnProps & StateProps> = ({
<ListItem
narrow
icon="delete-user"
// eslint-disable-next-line react/jsx-no-bind
onClick={() => {
onScreenSelect(deniedContactsScreen);
}}

View File

@ -40,6 +40,7 @@ const SettingsStickerSet: FC<OwnProps> = ({
narrow
className="SettingsStickerSet"
inactive={!firstSticker}
// eslint-disable-next-line react/jsx-no-bind
onClick={() => firstSticker && onClick(firstSticker)}
>
<Button
@ -71,6 +72,7 @@ const SettingsStickerSet: FC<OwnProps> = ({
<ListItem
narrow
className="SettingsStickerSet"
// eslint-disable-next-line react/jsx-no-bind
onClick={() => onClick(firstSticker)}
>
<StickerButton

View File

@ -115,6 +115,7 @@ const SettingsFoldersChatsPicker: FC<OwnProps> = ({
<ListItem
key={type.key}
className="chat-item-clickable picker-list-item chat-type-item"
// eslint-disable-next-line react/jsx-no-bind
onClick={() => handleChatTypeClick(type.key)}
ripple
>
@ -136,6 +137,7 @@ const SettingsFoldersChatsPicker: FC<OwnProps> = ({
<ListItem
key={id}
className="chat-item-clickable picker-list-item chat-item"
// eslint-disable-next-line react/jsx-no-bind
onClick={() => handleItemClick(id)}
ripple
disabled={!isSelected && hasMaxChats}

View File

@ -124,12 +124,12 @@ const SettingsFoldersEdit: FC<OwnProps & StateProps> = ({
? SettingsScreens.FoldersEditFolder
: SettingsScreens.FoldersCreateFolder);
function handleChange(event: React.ChangeEvent<HTMLInputElement>) {
const handleChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
const { currentTarget } = event;
dispatch({ type: 'setTitle', payload: currentTarget.value.trim() });
}
}, [dispatch]);
function handleSubmit() {
const handleSubmit = useCallback(() => {
const { title } = state.folder;
if (!title) {
@ -152,7 +152,7 @@ const SettingsFoldersEdit: FC<OwnProps & StateProps> = ({
setTimeout(() => {
onReset();
}, SUBMIT_TIMEOUT);
}
}, [addChatFolder, dispatch, editChatFolder, includedChatIds.length, includedChatTypes, onReset, state]);
function renderChatType(key: string, mode: 'included' | 'excluded') {
const chatType = mode === 'included'
@ -207,6 +207,7 @@ const SettingsFoldersEdit: FC<OwnProps & StateProps> = ({
<ShowMoreButton
count={leftChatsCount}
itemName="chat"
// eslint-disable-next-line react/jsx-no-bind
onClick={clickHandler}
/>
)}

View File

@ -165,6 +165,7 @@ const SettingsFoldersMain: FC<OwnProps & StateProps> = ({
className="mb-2 no-icon"
narrow
multiline
// eslint-disable-next-line react/jsx-no-bind
onClick={() => onEditFolder(foldersById[folder.id])}
>
<span className="title">{folder.title}</span>
@ -187,6 +188,7 @@ const SettingsFoldersMain: FC<OwnProps & StateProps> = ({
<ListItem
className="mb-2"
narrow
// eslint-disable-next-line react/jsx-no-bind
onClick={() => handleCreateFolderFromRecommended(folder)}
>
<div className="settings-folders-recommended-item">

View File

@ -1,4 +1,4 @@
import React, { FC, memo } from '../../../../lib/teact/teact';
import React, { FC, memo, useCallback } from '../../../../lib/teact/teact';
import { withGlobal } from '../../../../global';
import { ApiSticker } from '../../../../api/types';
@ -26,9 +26,9 @@ const SettingsTwoFaCongratulations: FC<OwnProps & StateProps> = ({
}) => {
const lang = useLang();
const handleClick = () => {
const handleClick = useCallback(() => {
onScreenSelect(SettingsScreens.Privacy);
};
}, [onScreenSelect]);
useHistoryBack(isActive, onReset, onScreenSelect, SettingsScreens.TwoFaCongratulations);

View File

@ -1,5 +1,5 @@
import React, {
FC, memo, useEffect, useRef, useState,
FC, memo, useCallback, useEffect, useRef, useState,
} from '../../../../lib/teact/teact';
import { withGlobal } from '../../../../global';
@ -62,7 +62,7 @@ const SettingsTwoFaEmailCode: FC<OwnProps & StateProps> = ({
useHistoryBack(isActive, onReset, onScreenSelect, screen);
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const handleInputChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
if (error && clearError) {
clearError();
}
@ -75,7 +75,7 @@ const SettingsTwoFaEmailCode: FC<OwnProps & StateProps> = ({
setValue(newValue);
e.target.value = newValue;
};
}, [clearError, codeLength, error, onSubmit]);
return (
<div className="settings-content two-fa custom-scroll">

View File

@ -42,18 +42,21 @@ const SettingsTwoFaEnabled: FC<OwnProps & StateProps> = ({
<div className="settings-item pt-0 no-border">
<ListItem
icon="edit"
// eslint-disable-next-line react/jsx-no-bind
onClick={() => onScreenSelect(SettingsScreens.TwoFaChangePasswordCurrent)}
>
{lang('ChangePassword')}
</ListItem>
<ListItem
icon="password-off"
// eslint-disable-next-line react/jsx-no-bind
onClick={() => onScreenSelect(SettingsScreens.TwoFaTurnOff)}
>
{lang('TurnPasswordOff')}
</ListItem>
<ListItem
icon="email"
// eslint-disable-next-line react/jsx-no-bind
onClick={() => onScreenSelect(SettingsScreens.TwoFaRecoveryEmailCurrentPassword)}
>
{lang('SetRecoveryEmail')}

View File

@ -1,5 +1,5 @@
import React, {
FC, memo, useEffect, useRef, useState,
FC, memo, useCallback, useEffect, useRef, useState,
} from '../../../../lib/teact/teact';
import { withGlobal } from '../../../../global';
@ -67,13 +67,13 @@ const SettingsTwoFaSkippableForm: FC<OwnProps & StateProps> = ({
}
}, []);
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const handleInputChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
if (error && clearError) {
clearError();
}
setValue(e.target.value);
};
}, [clearError, error]);
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
@ -85,14 +85,14 @@ const SettingsTwoFaSkippableForm: FC<OwnProps & StateProps> = ({
onSubmit(value);
};
const handleSkip = () => {
const handleSkip = useCallback(() => {
onSubmit();
};
}, [onSubmit]);
const handleSkipConfirm = () => {
const handleSkipConfirm = useCallback(() => {
unmarkIsConfirmShown();
onSubmit();
};
}, [onSubmit, unmarkIsConfirmShown]);
const lang = useLang();

View File

@ -1,4 +1,4 @@
import React, { FC } from '../../lib/teact/teact';
import React, { FC, useCallback } from '../../lib/teact/teact';
import Button from '../ui/Button';
@ -6,9 +6,9 @@ import appInactivePath from '../../assets/app-inactive.png';
import './AppInactive.scss';
const AppInactive: FC = () => {
const handleReload = () => {
const handleReload = useCallback(() => {
window.location.reload();
};
}, []);
return (
<div id="AppInactive">
@ -21,7 +21,9 @@ const AppInactive: FC = () => {
Please reload this page to continue using this tab or close it.
</div>
<div className="actions">
<Button isText ripple onClick={handleReload}>Reload app</Button>
<Button isText ripple onClick={handleReload}>
Reload app
</Button>
</div>
</div>
</div>

View File

@ -96,7 +96,12 @@ const Dialogs: FC<StateProps> = ({ dialogs }) => {
: lang('MemberRequests.RequestToJoinDescriptionGroup')}
</p>
)}
<Button isText className="confirm-dialog-button" onClick={handleJoinClick}>
<Button
isText
className="confirm-dialog-button"
// eslint-disable-next-line react/jsx-no-bind
onClick={handleJoinClick}
>
{isRequestNeeded ? requestToJoinText : joinText}
</Button>
<Button isText className="confirm-dialog-button" onClick={closeModal}>{lang('Cancel')}</Button>
@ -122,7 +127,14 @@ const Dialogs: FC<StateProps> = ({ dialogs }) => {
>
{lang('AreYouSureShareMyContactInfoBot')}
<div>
<Button className="confirm-dialog-button" isText onClick={handleConfirm}>{lang('OK')}</Button>
<Button
className="confirm-dialog-button"
isText
// eslint-disable-next-line react/jsx-no-bind
onClick={handleConfirm}
>
{lang('OK')}
</Button>
<Button className="confirm-dialog-button" isText onClick={closeModal}>{lang('Cancel')}</Button>
</div>
</Modal>

View File

@ -24,6 +24,7 @@ const Notifications: FC<StateProps> = ({ notifications }) => {
{notifications.map(({ message, localId }) => (
<Notification
message={renderText(message, ['emoji', 'br', 'links', 'simple_markdown'])}
// eslint-disable-next-line react/jsx-no-bind
onDismiss={() => dismissNotification({ localId })}
/>
))}

View File

@ -220,6 +220,7 @@ const VideoPlayerControls: FC<OwnProps> = ({
onClose={closePlaybackMenu}
>
{PLAYBACK_RATES.map((rate) => (
// eslint-disable-next-line react/jsx-no-bind
<MenuItem disabled={playbackRate === rate} onClick={() => onPlaybackRateChange(rate)}>
{`${rate}x`}
</MenuItem>

View File

@ -36,17 +36,17 @@ const ZoomControls: FC<OwnProps> = ({ isShown, onChangeZoom }) => {
}
}, [isShown, prevIsShown]);
const handleZoomOut = () => {
const handleZoomOut = useCallback(() => {
if (inputRef.current) {
setZoomLevel(Math.max(MIN_ZOOM_LEVEL, zoomLevel - 0.5));
}
};
}, [zoomLevel]);
const handleZoomIn = () => {
const handleZoomIn = useCallback(() => {
if (inputRef.current) {
setZoomLevel(Math.min(MAX_ZOOM_LEVEL, zoomLevel + 0.5));
}
};
}, [zoomLevel]);
const handleStartSeek = useCallback(() => {
isSeeking.current = true;

View File

@ -166,6 +166,7 @@ const MobileSearchFooter: FC<StateProps> = ({
round
size="smaller"
color="translucent"
// eslint-disable-next-line react/jsx-no-bind
onClick={() => openHistoryCalendar({ selectedAt: getDayStartAt(Date.now()) })}
ariaLabel="Search messages by date"
>

View File

@ -124,6 +124,7 @@ const ReactorListModal: FC<OwnProps & StateProps> = ({
className={buildClassName(!chosenTab && 'chosen')}
size="tiny"
ripple
// eslint-disable-next-line react/jsx-no-bind
onClick={() => setChosenTab(undefined)}
>
<i className="icon-reaction-filled" />
@ -136,6 +137,7 @@ const ReactorListModal: FC<OwnProps & StateProps> = ({
className={buildClassName(chosenTab === reaction && 'chosen')}
size="tiny"
ripple
// eslint-disable-next-line react/jsx-no-bind
onClick={() => setChosenTab(reaction)}
>
<ReactionStaticEmoji reaction={reaction} className="reaction-filter-emoji" />
@ -162,6 +164,7 @@ const ReactorListModal: FC<OwnProps & StateProps> = ({
<ListItem
key={userId}
className="chat-item-clickable reactors-list-item"
// eslint-disable-next-line react/jsx-no-bind
onClick={() => handleClick(userId)}
>
<Avatar user={user} size="medium" />

View File

@ -30,6 +30,7 @@ const BotCommand: FC<OwnProps> = ({
key={botCommand.command}
className={buildClassName('BotCommand chat-item-clickable scroll-item', withAvatar && 'with-avatar')}
multiline
// eslint-disable-next-line react/jsx-no-bind
onClick={() => onClick(botCommand)}
focus={focus}
>

View File

@ -1,4 +1,6 @@
import React, { FC, memo, useEffect } from '../../../lib/teact/teact';
import React, {
FC, memo, useCallback, useEffect,
} from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global';
import { ApiMessage } from '../../../api/types';
@ -32,10 +34,10 @@ const BotKeyboardMenu: FC<OwnProps & StateProps> = ({
const { isKeyboardSingleUse } = message || {};
const [forceOpen, markForceOpen, unmarkForceOpen] = useFlag(true);
const handleClose = () => {
const handleClose = useCallback(() => {
unmarkForceOpen();
onClose();
};
}, [onClose, unmarkForceOpen]);
useEffect(() => {
markForceOpen();
@ -65,6 +67,7 @@ const BotKeyboardMenu: FC<OwnProps & StateProps> = ({
<Button
ripple
disabled={button.type === 'NOT_SUPPORTED'}
// eslint-disable-next-line react/jsx-no-bind
onClick={() => clickInlineButton({ button })}
>
{button.text}

View File

@ -174,6 +174,7 @@ const EmojiPicker: FC<OwnProps & StateProps> = ({
round
faded
color="translucent"
// eslint-disable-next-line react/jsx-no-bind
onClick={() => selectCategory(index)}
ariaLabel={category.name}
>

View File

@ -92,6 +92,7 @@ const MentionTooltip: FC<OwnProps> = ({
<ListItem
key={id}
className="chat-item-clickable scroll-item"
// eslint-disable-next-line react/jsx-no-bind
onClick={() => handleUserSelect(id)}
focus={selectedMentionIndex === index}
>

View File

@ -209,6 +209,10 @@ const PollModal: FC<OwnProps> = ({
}
}, [handleCreate]);
const handleQuestionChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
setQuestion(e.target.value);
}, []);
const getQuestionError = useCallback(() => {
if (hasErrors && !question.trim().length) {
return lang('lng_polls_choose_question');
@ -253,6 +257,7 @@ const PollModal: FC<OwnProps> = ({
: lang('CreatePoll.AddOption')}
error={getOptionsError(index)}
value={option}
// eslint-disable-next-line react/jsx-no-bind
onChange={(e) => updateOption(index, e.currentTarget.value)}
onKeyPress={handleKeyPress}
/>
@ -263,6 +268,7 @@ const PollModal: FC<OwnProps> = ({
color="translucent"
size="smaller"
ariaLabel={lang('Delete')}
// eslint-disable-next-line react/jsx-no-bind
onClick={() => removeOption(index)}
>
<i className="icon-close" />
@ -292,7 +298,7 @@ const PollModal: FC<OwnProps> = ({
label={lang('AskAQuestion')}
value={question}
error={getQuestionError()}
onChange={(e) => setQuestion(e.currentTarget.value)}
onChange={handleQuestionChange}
onKeyPress={handleKeyPress}
/>
<div className="options-divider" />

View File

@ -98,6 +98,7 @@ const SendAsMenu: FC<OwnProps> = ({
<ListItem
key={id}
className="SendAsItem chat-item-clickable scroll-item with-avatar"
// eslint-disable-next-line react/jsx-no-bind
onClick={() => handleUserSelect(id)}
focus={selectedSendAsIndex === index}
>

View File

@ -204,6 +204,7 @@ const StickerPicker: FC<OwnProps & StateProps> = ({
round
faded={stickerSet.id === 'recent' || stickerSet.id === 'favorite'}
color="translucent"
// eslint-disable-next-line react/jsx-no-bind
onClick={() => selectStickerSet(index)}
>
{stickerSet.id === 'recent' ? (

View File

@ -37,6 +37,7 @@ const SymbolMenuFooter: FC<OwnProps> = ({
return (
<Button
className={`symbol-tab-button ${activeTab === tab ? 'activated' : ''}`}
// eslint-disable-next-line react/jsx-no-bind
onClick={() => onSwitchTab(tab)}
ariaLabel={SYMBOL_MENU_TAB_TITLES[tab]}
round

View File

@ -109,7 +109,7 @@ const TextFormatter: FC<OwnProps> = ({
setSelectedTextFormats(selectedFormats);
}, [isOpen, selectedRange, openLinkControl]);
function restoreSelection() {
const restoreSelection = useCallback(() => {
if (!selectedRange) {
return;
}
@ -119,7 +119,7 @@ const TextFormatter: FC<OwnProps> = ({
selection.removeAllRanges();
selection.addRange(selectedRange);
}
}
}, [selectedRange]);
const updateSelectedRange = useCallback(() => {
const selection = window.getSelection();
@ -311,7 +311,7 @@ const TextFormatter: FC<OwnProps> = ({
getSelectedElement, getSelectedText, onClose, selectedRange, selectedTextFormats.monospace,
]);
function handleLinkUrlConfirm() {
const handleLinkUrlConfirm = useCallback(() => {
const formattedLinkUrl = encodeURI(ensureProtocol(linkUrl) || '');
if (isEditingLink) {
@ -335,7 +335,7 @@ const TextFormatter: FC<OwnProps> = ({
`<a href=${formattedLinkUrl} class="text-entity-link" dir="auto">${text}</a>`,
);
onClose();
}
}, [getSelectedElement, getSelectedText, isEditingLink, linkUrl, onClose, restoreSelection]);
const handleKeyDown = useCallback((e: KeyboardEvent) => {
const HANDLERS_BY_KEY: Record<string, AnyToVoidFunction> = {

View File

@ -1,4 +1,6 @@
import React, { FC, memo, useEffect } from '../../../lib/teact/teact';
import React, {
FC, memo, useCallback, useEffect,
} from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global';
import { ApiMessage, ApiMessageEntityTypes, ApiWebPage } from '../../../api/types';
@ -84,14 +86,14 @@ const WebPagePreview: FC<OwnProps & StateProps> = ({
const renderingWebPage = useCurrentOrPrev(webPagePreview, true);
const handleClearWebpagePreview = useCallback(() => {
toggleMessageWebPage({ chatId, threadId, noWebPage: true });
}, [chatId, threadId, toggleMessageWebPage]);
if (!shouldRender || !renderingWebPage) {
return undefined;
}
const handleClearWebpagePreview = () => {
toggleMessageWebPage({ chatId, threadId, noWebPage: true });
};
// TODO Refactor so `WebPage` can be used without message
const { photo, ...webPageWithoutPhoto } = renderingWebPage;
const messageStub = {

View File

@ -27,6 +27,7 @@ const InlineButtons: FC<OwnProps> = ({ message, onClick }) => {
size="tiny"
ripple
disabled={button.type === 'NOT_SUPPORTED'}
// eslint-disable-next-line react/jsx-no-bind
onClick={() => onClick({ button })}
>
{renderText(lang(button.text))}

View File

@ -1,6 +1,6 @@
import { RefObject } from 'react';
import React, {
FC, memo, useEffect, useRef,
FC, memo, useCallback, useEffect, useRef,
} from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global';
@ -64,11 +64,8 @@ const SponsoredMessage: FC<OwnProps & StateProps> = ({
}) : undefined;
}, [chatId, shouldObserve, observeIntersection, viewSponsoredMessage]);
if (!message) {
return undefined;
}
const handleClick = () => {
const handleClick = useCallback(() => {
if (!message) return;
if (message.chatInviteHash) {
openChatByInvite({ hash: message.chatInviteHash });
} else if (message.channelPostId) {
@ -83,7 +80,11 @@ const SponsoredMessage: FC<OwnProps & StateProps> = ({
});
}
}
};
}, [focusMessage, message, openChat, openChatByInvite, startBot]);
if (!message) {
return undefined;
}
return (
<div className="SponsoredMessage Message open" key="sponsored-message">

View File

@ -103,6 +103,7 @@ const PollAnswerResults: FC<OwnProps & StateProps> = ({
<ListItem
key={id}
className="chat-item-clickable"
// eslint-disable-next-line react/jsx-no-bind
onClick={() => handleMemberClick(id)}
>
<PrivateChatInfo

View File

@ -406,6 +406,7 @@ const Profile: FC<OwnProps & StateProps> = ({
key={id}
teactOrderKey={i}
className="chat-item-clickable scroll-item small-icon"
// eslint-disable-next-line react/jsx-no-bind
onClick={() => handleMemberClick(id)}
contextActions={getMemberContextAction(id)}
>
@ -418,6 +419,7 @@ const Profile: FC<OwnProps & StateProps> = ({
key={id}
teactOrderKey={i}
className="chat-item-clickable scroll-item small-icon"
// eslint-disable-next-line react/jsx-no-bind
onClick={() => openChat({ id })}
>
<GroupChatInfo chatId={id} />

View File

@ -269,6 +269,7 @@ const RightHeader: FC<OwnProps & StateProps> = ({
round
size="smaller"
color="translucent"
// eslint-disable-next-line react/jsx-no-bind
onClick={() => openHistoryCalendar({ selectedAt: getDayStartAt(Date.now()) })}
ariaLabel="Search messages by date"
>

View File

@ -120,13 +120,13 @@ const ManageChannel: FC<OwnProps & StateProps> = ({
onScreenSelect(ManagementScreens.ChatAdministrators);
}, [onScreenSelect]);
const handleClickInvites = () => {
const handleClickInvites = useCallback(() => {
onScreenSelect(ManagementScreens.Invites);
};
}, [onScreenSelect]);
const handleClickRequests = () => {
const handleClickRequests = useCallback(() => {
onScreenSelect(ManagementScreens.JoinRequests);
};
}, [onScreenSelect]);
const handleSetPhoto = useCallback((file: File) => {
setPhoto(file);

View File

@ -42,9 +42,9 @@ const ManageChatAdministrators: FC<OwnProps & StateProps> = ({
useHistoryBack(isActive, onClose);
function handleRecentActionsClick() {
const handleRecentActionsClick = useCallback(() => {
onScreenSelect(ManagementScreens.GroupRecentActions);
}
}, [onScreenSelect]);
const adminMembers = useMemo(() => {
if (!chat.fullInfo || !chat.fullInfo.adminMembers) {
@ -112,6 +112,7 @@ const ManageChatAdministrators: FC<OwnProps & StateProps> = ({
<ListItem
key={member.userId}
className="chat-item-clickable"
// eslint-disable-next-line react/jsx-no-bind
onClick={() => handleAdminMemberClick(member)}
>
<PrivateChatInfo

View File

@ -210,6 +210,7 @@ const ManageDiscussion: FC<OwnProps & StateProps> = ({
key={id}
teactOrderKey={i + 1}
className="chat-item-clickable scroll-item"
// eslint-disable-next-line react/jsx-no-bind
onClick={() => {
onDiscussionClick(id);
}}

View File

@ -133,13 +133,13 @@ const ManageGroup: FC<OwnProps & StateProps> = ({
onScreenSelect(ManagementScreens.ChatAdministrators);
}, [onScreenSelect]);
const handleClickInvites = () => {
const handleClickInvites = useCallback(() => {
onScreenSelect(ManagementScreens.Invites);
};
}, [onScreenSelect]);
const handleClickRequests = () => {
const handleClickRequests = useCallback(() => {
onScreenSelect(ManagementScreens.JoinRequests);
};
}, [onScreenSelect]);
const handleSetPhoto = useCallback((file: File) => {
setPhoto(file);

View File

@ -172,6 +172,7 @@ const ManageGroupMembers: FC<OwnProps & StateProps> = ({
<ListItem
key={id}
className="chat-item-clickable scroll-item"
// eslint-disable-next-line react/jsx-no-bind
onClick={() => handleMemberClick(id)}
>
<PrivateChatInfo userId={id} forceShowSelf />

View File

@ -269,6 +269,7 @@ const ManageGroupPermissions: FC<OwnProps & StateProps> = ({
<ListItem
key={member.userId}
className="chat-item-clickable exceptions-member"
// eslint-disable-next-line react/jsx-no-bind
onClick={() => handleExceptionMemberClick(member)}
>
<PrivateChatInfo

View File

@ -72,6 +72,7 @@ const ManageGroupUserPermissionsCreate: FC<OwnProps & StateProps> = ({
key={id}
teactOrderKey={i}
className="chat-item-clickable scroll-item"
// eslint-disable-next-line react/jsx-no-bind
onClick={() => handleExceptionMemberClick(id)}
>
<PrivateChatInfo userId={id} forceShowSelf />

View File

@ -86,6 +86,7 @@ const ManageInviteInfo: FC<OwnProps & StateProps> = ({
{importers.map((importer) => (
<ListItem
className="chat-item-clickable scroll-item small-icon"
// eslint-disable-next-line react/jsx-no-bind
onClick={() => openChat({ id: importer.userId })}
>
<PrivateChatInfo
@ -111,6 +112,7 @@ const ManageInviteInfo: FC<OwnProps & StateProps> = ({
{requesters.map((requester) => (
<ListItem
className="chat-item-clickable scroll-item small-icon"
// eslint-disable-next-line react/jsx-no-bind
onClick={() => openChat({ id: requester.userId })}
>
<PrivateChatInfo
@ -155,6 +157,7 @@ const ManageInviteInfo: FC<OwnProps & StateProps> = ({
<p>{lang('LinkCreatedeBy')}</p>
<ListItem
className="chat-item-clickable scroll-item small-icon"
// eslint-disable-next-line react/jsx-no-bind
onClick={() => openChat({ id: adminId })}
>
<PrivateChatInfo

View File

@ -333,6 +333,7 @@ const ManageInvites: FC<OwnProps & StateProps> = ({
icon="link"
secondaryIcon="more"
multiline
// eslint-disable-next-line react/jsx-no-bind
onClick={() => showInviteInfo(invite)}
contextActions={prepareContextActions(invite)}
key={invite.link}
@ -361,6 +362,7 @@ const ManageInvites: FC<OwnProps & StateProps> = ({
icon="link"
secondaryIcon="more"
multiline
// eslint-disable-next-line react/jsx-no-bind
onClick={() => showInviteInfo(invite)}
contextActions={prepareContextActions(invite)}
key={invite.link}

View File

@ -71,7 +71,9 @@ const TestOrdered: FC<{}> = () => {
teactOrderKey={itemValue}
key={key}
value={itemValue}
// eslint-disable-next-line react/jsx-no-bind
onChange={(newValue) => updateData(key, Number(newValue))}
// eslint-disable-next-line react/jsx-no-bind
onDelete={() => deleteData(key)}
/>
))}

View File

@ -1,6 +1,6 @@
import { ChangeEvent } from 'react';
import React, {
FC, useState, useEffect, memo,
FC, useState, useEffect, memo, useCallback,
} from '../../lib/teact/teact';
import buildClassName from '../../util/buildClassName';
@ -40,7 +40,7 @@ const AvatarEditable: FC<OwnProps> = ({
target.value = '';
}
function handleAvatarCrop(croppedImg: File) {
const handleAvatarCrop = useCallback((croppedImg: File) => {
setSelectedFile(undefined);
onChange(croppedImg);
@ -48,11 +48,11 @@ const AvatarEditable: FC<OwnProps> = ({
URL.revokeObjectURL(croppedBlobUrl);
}
setCroppedBlobUrl(URL.createObjectURL(croppedImg));
}
}, [croppedBlobUrl, onChange]);
function handleModalClose() {
const handleModalClose = useCallback(() => {
setSelectedFile(undefined);
}
}, []);
const labelClassName = buildClassName(
croppedBlobUrl && 'filled',

View File

@ -1,5 +1,5 @@
import React, {
FC, useEffect, useState, memo,
FC, useEffect, useState, memo, useCallback,
} from '../../lib/teact/teact';
import { DEBUG } from '../../config';
@ -93,7 +93,7 @@ const CropModal: FC<OwnProps> = ({ file, onChange, onClose }: OwnProps) => {
const lang = useLang();
async function handleCropClick() {
const handleCropClick = useCallback(async () => {
if (!cropper) {
return;
}
@ -102,7 +102,7 @@ const CropModal: FC<OwnProps> = ({ file, onChange, onClose }: OwnProps) => {
const croppedImg = typeof result === 'string' ? result : blobToFile(result, 'avatar.jpg');
onChange(croppedImg);
}
}, [onChange]);
return (
<Modal

View File

@ -1,4 +1,6 @@
import React, { FC, useState, useRef } from '../../lib/teact/teact';
import React, {
FC, useState, useRef, useCallback,
} from '../../lib/teact/teact';
import Menu from './Menu';
@ -55,10 +57,10 @@ const DropdownMenu: FC<OwnProps> = ({
}
};
const handleClose = () => {
const handleClose = useCallback(() => {
setIsOpen(false);
if (onClose) onClose();
};
}, [onClose]);
return (
<div