[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/style-prop-object": "off",
"react/jsx-no-bind": ["error", { "react/jsx-no-bind": ["error", {
"ignoreRefs": true, "ignoreRefs": true,
"allowArrowFunctions": true, "allowArrowFunctions": false,
"allowFunctions": true, "allowFunctions": false,
"allowBind": false, "allowBind": false,
"ignoreDOMComponents": true "ignoreDOMComponents": true
}], }],

View File

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

View File

@ -138,6 +138,7 @@ const CountryCodeInput: FC<OwnProps & StateProps> = ({
<MenuItem <MenuItem
key={`${country.iso2}-${country.countryCode}`} key={`${country.iso2}-${country.countryCode}`}
className={value && country.iso2 === value.iso2 ? 'selected' : ''} className={value && country.iso2 === value.iso2 ? 'selected' : ''}
// eslint-disable-next-line react/jsx-no-bind
onClick={() => handleChange(country)} onClick={() => handleChange(country)}
> >
<span className="country-flag">{renderText(isoToEmoji(country.iso2), ['hq_emoji'])}</span> <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 { getActions, withGlobal } from '../../global';
import ConfirmDialog from '../ui/ConfirmDialog'; import ConfirmDialog from '../ui/ConfirmDialog';
@ -30,13 +32,15 @@ const CallFallbackConfirm: FC<OwnProps & StateProps> = ({
const [shouldRemove, setShouldRemove] = useState(true); const [shouldRemove, setShouldRemove] = useState(true);
const renderingUserFullName = useCurrentOrPrev(userFullName, true); const renderingUserFullName = useCurrentOrPrev(userFullName, true);
const handleConfirm = useCallback(() => {
inviteToCallFallback({ shouldRemove });
}, [inviteToCallFallback, shouldRemove]);
return ( return (
<ConfirmDialog <ConfirmDialog
title="Start Call" title="Start Call"
isOpen={isOpen} isOpen={isOpen}
confirmHandler={() => { confirmHandler={handleConfirm}
inviteToCallFallback({ shouldRemove });
}}
onClose={closeCallFallbackConfirm} onClose={closeCallFallbackConfirm}
> >
<p>The call will be started in a private channel <b>{channelTitle}</b>.</p> <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]); }, [connectionState, playGroupCallSound]);
const handleCloseConfirmLeaveModal = () => { const handleCloseConfirmLeaveModal = useCallback(() => {
closeConfirmLeaveModal(); closeConfirmLeaveModal();
setIsEndGroupCallModal(false); setIsEndGroupCallModal(false);
}; }, [closeConfirmLeaveModal]);
const MainButton: FC<{ onTrigger: () => void; isOpen?: boolean }> = useMemo(() => { const MainButton: FC<{ onTrigger: () => void; isOpen?: boolean }> = useMemo(() => {
return ({ onTrigger, isOpen }) => ( return ({ onTrigger, isOpen }) => (
@ -153,13 +153,13 @@ const GroupCall: FC<OwnProps & StateProps> = ({
} }
}, [closeFullscreen, isFullscreen, openFullscreen]); }, [closeFullscreen, isFullscreen, openFullscreen]);
const handleToggleSidebar = () => { const handleToggleSidebar = useCallback(() => {
if (isSidebarOpen) { if (isSidebarOpen) {
closeSidebar(); closeSidebar();
} else { } else {
openSidebar(); openSidebar();
} }
}; }, [closeSidebar, isSidebarOpen, openSidebar]);
const handleStreamsDoubleClick = useCallback(() => { const handleStreamsDoubleClick = useCallback(() => {
if (!IS_REQUEST_FULLSCREEN_SUPPORTED) return; if (!IS_REQUEST_FULLSCREEN_SUPPORTED) return;
@ -180,12 +180,12 @@ const GroupCall: FC<OwnProps & StateProps> = ({
} }
}, [closeFullscreen, isFullscreen, openFullscreen]); }, [closeFullscreen, isFullscreen, openFullscreen]);
const handleClose = () => { const handleClose = useCallback(() => {
toggleGroupCallPanel(); toggleGroupCallPanel();
if (isFullscreen) { if (isFullscreen) {
closeFullscreen(); closeFullscreen();
} }
}; }, [closeFullscreen, isFullscreen, toggleGroupCallPanel]);
useEffect(() => { useEffect(() => {
if (!IS_REQUEST_FULLSCREEN_SUPPORTED) return undefined; if (!IS_REQUEST_FULLSCREEN_SUPPORTED) return undefined;
@ -211,16 +211,16 @@ const GroupCall: FC<OwnProps & StateProps> = ({
connectToActiveGroupCall(); connectToActiveGroupCall();
}, [connectToActiveGroupCall, groupCallId]); }, [connectToActiveGroupCall, groupCallId]);
const endGroupCall = () => { const endGroupCall = useCallback(() => {
setIsEndGroupCallModal(true); setIsEndGroupCallModal(true);
setShouldEndGroupCall(true); setShouldEndGroupCall(true);
openConfirmLeaveModal(); openConfirmLeaveModal();
if (isFullscreen) { if (isFullscreen) {
handleToggleFullscreen(); handleToggleFullscreen();
} }
}; }, [handleToggleFullscreen, isFullscreen, openConfirmLeaveModal]);
const handleLeaveGroupCall = () => { const handleLeaveGroupCall = useCallback(() => {
if (isAdmin && !isConfirmLeaveModalOpen) { if (isAdmin && !isConfirmLeaveModalOpen) {
openConfirmLeaveModal(); openConfirmLeaveModal();
if (isFullscreen) { if (isFullscreen) {
@ -231,15 +231,18 @@ const GroupCall: FC<OwnProps & StateProps> = ({
playGroupCallSound({ sound: 'leave' }); playGroupCallSound({ sound: 'leave' });
setIsLeaving(true); setIsLeaving(true);
closeConfirmLeaveModal(); closeConfirmLeaveModal();
}; }, [
closeConfirmLeaveModal, handleToggleFullscreen, isAdmin, isConfirmLeaveModalOpen, isFullscreen,
openConfirmLeaveModal, playGroupCallSound,
]);
const handleCloseAnimationEnd = () => { const handleCloseAnimationEnd = useCallback(() => {
if (isLeaving) { if (isLeaving) {
leaveGroupCall({ leaveGroupCall({
shouldDiscard: shouldEndGroupCall, shouldDiscard: shouldEndGroupCall,
}); });
} }
}; }, [isLeaving, leaveGroupCall, shouldEndGroupCall]);
return ( return (
<Modal <Modal

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -165,6 +165,7 @@ const SettingsFoldersMain: FC<OwnProps & StateProps> = ({
className="mb-2 no-icon" className="mb-2 no-icon"
narrow narrow
multiline multiline
// eslint-disable-next-line react/jsx-no-bind
onClick={() => onEditFolder(foldersById[folder.id])} onClick={() => onEditFolder(foldersById[folder.id])}
> >
<span className="title">{folder.title}</span> <span className="title">{folder.title}</span>
@ -187,6 +188,7 @@ const SettingsFoldersMain: FC<OwnProps & StateProps> = ({
<ListItem <ListItem
className="mb-2" className="mb-2"
narrow narrow
// eslint-disable-next-line react/jsx-no-bind
onClick={() => handleCreateFolderFromRecommended(folder)} onClick={() => handleCreateFolderFromRecommended(folder)}
> >
<div className="settings-folders-recommended-item"> <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 { withGlobal } from '../../../../global';
import { ApiSticker } from '../../../../api/types'; import { ApiSticker } from '../../../../api/types';
@ -26,9 +26,9 @@ const SettingsTwoFaCongratulations: FC<OwnProps & StateProps> = ({
}) => { }) => {
const lang = useLang(); const lang = useLang();
const handleClick = () => { const handleClick = useCallback(() => {
onScreenSelect(SettingsScreens.Privacy); onScreenSelect(SettingsScreens.Privacy);
}; }, [onScreenSelect]);
useHistoryBack(isActive, onReset, onScreenSelect, SettingsScreens.TwoFaCongratulations); useHistoryBack(isActive, onReset, onScreenSelect, SettingsScreens.TwoFaCongratulations);

View File

@ -1,5 +1,5 @@
import React, { import React, {
FC, memo, useEffect, useRef, useState, FC, memo, useCallback, useEffect, useRef, useState,
} from '../../../../lib/teact/teact'; } from '../../../../lib/teact/teact';
import { withGlobal } from '../../../../global'; import { withGlobal } from '../../../../global';
@ -62,7 +62,7 @@ const SettingsTwoFaEmailCode: FC<OwnProps & StateProps> = ({
useHistoryBack(isActive, onReset, onScreenSelect, screen); useHistoryBack(isActive, onReset, onScreenSelect, screen);
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => { const handleInputChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
if (error && clearError) { if (error && clearError) {
clearError(); clearError();
} }
@ -75,7 +75,7 @@ const SettingsTwoFaEmailCode: FC<OwnProps & StateProps> = ({
setValue(newValue); setValue(newValue);
e.target.value = newValue; e.target.value = newValue;
}; }, [clearError, codeLength, error, onSubmit]);
return ( return (
<div className="settings-content two-fa custom-scroll"> <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"> <div className="settings-item pt-0 no-border">
<ListItem <ListItem
icon="edit" icon="edit"
// eslint-disable-next-line react/jsx-no-bind
onClick={() => onScreenSelect(SettingsScreens.TwoFaChangePasswordCurrent)} onClick={() => onScreenSelect(SettingsScreens.TwoFaChangePasswordCurrent)}
> >
{lang('ChangePassword')} {lang('ChangePassword')}
</ListItem> </ListItem>
<ListItem <ListItem
icon="password-off" icon="password-off"
// eslint-disable-next-line react/jsx-no-bind
onClick={() => onScreenSelect(SettingsScreens.TwoFaTurnOff)} onClick={() => onScreenSelect(SettingsScreens.TwoFaTurnOff)}
> >
{lang('TurnPasswordOff')} {lang('TurnPasswordOff')}
</ListItem> </ListItem>
<ListItem <ListItem
icon="email" icon="email"
// eslint-disable-next-line react/jsx-no-bind
onClick={() => onScreenSelect(SettingsScreens.TwoFaRecoveryEmailCurrentPassword)} onClick={() => onScreenSelect(SettingsScreens.TwoFaRecoveryEmailCurrentPassword)}
> >
{lang('SetRecoveryEmail')} {lang('SetRecoveryEmail')}

View File

@ -1,5 +1,5 @@
import React, { import React, {
FC, memo, useEffect, useRef, useState, FC, memo, useCallback, useEffect, useRef, useState,
} from '../../../../lib/teact/teact'; } from '../../../../lib/teact/teact';
import { withGlobal } from '../../../../global'; 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) { if (error && clearError) {
clearError(); clearError();
} }
setValue(e.target.value); setValue(e.target.value);
}; }, [clearError, error]);
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => { const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault(); e.preventDefault();
@ -85,14 +85,14 @@ const SettingsTwoFaSkippableForm: FC<OwnProps & StateProps> = ({
onSubmit(value); onSubmit(value);
}; };
const handleSkip = () => { const handleSkip = useCallback(() => {
onSubmit(); onSubmit();
}; }, [onSubmit]);
const handleSkipConfirm = () => { const handleSkipConfirm = useCallback(() => {
unmarkIsConfirmShown(); unmarkIsConfirmShown();
onSubmit(); onSubmit();
}; }, [onSubmit, unmarkIsConfirmShown]);
const lang = useLang(); 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'; import Button from '../ui/Button';
@ -6,9 +6,9 @@ import appInactivePath from '../../assets/app-inactive.png';
import './AppInactive.scss'; import './AppInactive.scss';
const AppInactive: FC = () => { const AppInactive: FC = () => {
const handleReload = () => { const handleReload = useCallback(() => {
window.location.reload(); window.location.reload();
}; }, []);
return ( return (
<div id="AppInactive"> <div id="AppInactive">
@ -21,7 +21,9 @@ const AppInactive: FC = () => {
Please reload this page to continue using this tab or close it. Please reload this page to continue using this tab or close it.
</div> </div>
<div className="actions"> <div className="actions">
<Button isText ripple onClick={handleReload}>Reload app</Button> <Button isText ripple onClick={handleReload}>
Reload app
</Button>
</div> </div>
</div> </div>
</div> </div>

View File

@ -96,7 +96,12 @@ const Dialogs: FC<StateProps> = ({ dialogs }) => {
: lang('MemberRequests.RequestToJoinDescriptionGroup')} : lang('MemberRequests.RequestToJoinDescriptionGroup')}
</p> </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} {isRequestNeeded ? requestToJoinText : joinText}
</Button> </Button>
<Button isText className="confirm-dialog-button" onClick={closeModal}>{lang('Cancel')}</Button> <Button isText className="confirm-dialog-button" onClick={closeModal}>{lang('Cancel')}</Button>
@ -122,7 +127,14 @@ const Dialogs: FC<StateProps> = ({ dialogs }) => {
> >
{lang('AreYouSureShareMyContactInfoBot')} {lang('AreYouSureShareMyContactInfoBot')}
<div> <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> <Button className="confirm-dialog-button" isText onClick={closeModal}>{lang('Cancel')}</Button>
</div> </div>
</Modal> </Modal>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -30,6 +30,7 @@ const BotCommand: FC<OwnProps> = ({
key={botCommand.command} key={botCommand.command}
className={buildClassName('BotCommand chat-item-clickable scroll-item', withAvatar && 'with-avatar')} className={buildClassName('BotCommand chat-item-clickable scroll-item', withAvatar && 'with-avatar')}
multiline multiline
// eslint-disable-next-line react/jsx-no-bind
onClick={() => onClick(botCommand)} onClick={() => onClick(botCommand)}
focus={focus} 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 { getActions, withGlobal } from '../../../global';
import { ApiMessage } from '../../../api/types'; import { ApiMessage } from '../../../api/types';
@ -32,10 +34,10 @@ const BotKeyboardMenu: FC<OwnProps & StateProps> = ({
const { isKeyboardSingleUse } = message || {}; const { isKeyboardSingleUse } = message || {};
const [forceOpen, markForceOpen, unmarkForceOpen] = useFlag(true); const [forceOpen, markForceOpen, unmarkForceOpen] = useFlag(true);
const handleClose = () => { const handleClose = useCallback(() => {
unmarkForceOpen(); unmarkForceOpen();
onClose(); onClose();
}; }, [onClose, unmarkForceOpen]);
useEffect(() => { useEffect(() => {
markForceOpen(); markForceOpen();
@ -65,6 +67,7 @@ const BotKeyboardMenu: FC<OwnProps & StateProps> = ({
<Button <Button
ripple ripple
disabled={button.type === 'NOT_SUPPORTED'} disabled={button.type === 'NOT_SUPPORTED'}
// eslint-disable-next-line react/jsx-no-bind
onClick={() => clickInlineButton({ button })} onClick={() => clickInlineButton({ button })}
> >
{button.text} {button.text}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -109,7 +109,7 @@ const TextFormatter: FC<OwnProps> = ({
setSelectedTextFormats(selectedFormats); setSelectedTextFormats(selectedFormats);
}, [isOpen, selectedRange, openLinkControl]); }, [isOpen, selectedRange, openLinkControl]);
function restoreSelection() { const restoreSelection = useCallback(() => {
if (!selectedRange) { if (!selectedRange) {
return; return;
} }
@ -119,7 +119,7 @@ const TextFormatter: FC<OwnProps> = ({
selection.removeAllRanges(); selection.removeAllRanges();
selection.addRange(selectedRange); selection.addRange(selectedRange);
} }
} }, [selectedRange]);
const updateSelectedRange = useCallback(() => { const updateSelectedRange = useCallback(() => {
const selection = window.getSelection(); const selection = window.getSelection();
@ -311,7 +311,7 @@ const TextFormatter: FC<OwnProps> = ({
getSelectedElement, getSelectedText, onClose, selectedRange, selectedTextFormats.monospace, getSelectedElement, getSelectedText, onClose, selectedRange, selectedTextFormats.monospace,
]); ]);
function handleLinkUrlConfirm() { const handleLinkUrlConfirm = useCallback(() => {
const formattedLinkUrl = encodeURI(ensureProtocol(linkUrl) || ''); const formattedLinkUrl = encodeURI(ensureProtocol(linkUrl) || '');
if (isEditingLink) { if (isEditingLink) {
@ -335,7 +335,7 @@ const TextFormatter: FC<OwnProps> = ({
`<a href=${formattedLinkUrl} class="text-entity-link" dir="auto">${text}</a>`, `<a href=${formattedLinkUrl} class="text-entity-link" dir="auto">${text}</a>`,
); );
onClose(); onClose();
} }, [getSelectedElement, getSelectedText, isEditingLink, linkUrl, onClose, restoreSelection]);
const handleKeyDown = useCallback((e: KeyboardEvent) => { const handleKeyDown = useCallback((e: KeyboardEvent) => {
const HANDLERS_BY_KEY: Record<string, AnyToVoidFunction> = { 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 { getActions, withGlobal } from '../../../global';
import { ApiMessage, ApiMessageEntityTypes, ApiWebPage } from '../../../api/types'; import { ApiMessage, ApiMessageEntityTypes, ApiWebPage } from '../../../api/types';
@ -84,14 +86,14 @@ const WebPagePreview: FC<OwnProps & StateProps> = ({
const renderingWebPage = useCurrentOrPrev(webPagePreview, true); const renderingWebPage = useCurrentOrPrev(webPagePreview, true);
const handleClearWebpagePreview = useCallback(() => {
toggleMessageWebPage({ chatId, threadId, noWebPage: true });
}, [chatId, threadId, toggleMessageWebPage]);
if (!shouldRender || !renderingWebPage) { if (!shouldRender || !renderingWebPage) {
return undefined; return undefined;
} }
const handleClearWebpagePreview = () => {
toggleMessageWebPage({ chatId, threadId, noWebPage: true });
};
// TODO Refactor so `WebPage` can be used without message // TODO Refactor so `WebPage` can be used without message
const { photo, ...webPageWithoutPhoto } = renderingWebPage; const { photo, ...webPageWithoutPhoto } = renderingWebPage;
const messageStub = { const messageStub = {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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