[Refactoring] Revise hook dependencies (#2424)

This commit is contained in:
Alexander Zinchuk 2023-02-08 00:43:25 +01:00
parent cf4c199eba
commit 9c25abbd9a
39 changed files with 145 additions and 120 deletions

View File

@ -40,7 +40,12 @@
"no-console": "error",
"semi": "error",
"no-implicit-coercion": "error",
"react-hooks/exhaustive-deps": "error",
"react-hooks/exhaustive-deps": [
"error",
{
"additionalHooks": "(useSyncEffect|useAsync|useDebouncedCallback|useThrottledCallback|useEffectWithPrevDeps|useLayoutEffectWithPrevDeps)$"
}
],
"arrow-body-style": "off",
"no-else-return": "off",
"no-plusplus": "off",

View File

@ -12,7 +12,7 @@ import buildClassName from '../../util/buildClassName';
import renderText from '../common/helpers/renderText';
import useLang from '../../hooks/useLang';
import { isoToEmoji } from '../../util/emoji';
import useOnChange from '../../hooks/useOnChange';
import useSyncEffect from '../../hooks/useSyncEffect';
import DropdownMenu from '../ui/DropdownMenu';
import MenuItem from '../ui/MenuItem';
@ -53,11 +53,11 @@ const CountryCodeInput: FC<OwnProps & StateProps> = ({
setFilteredList(getFilteredList(phoneCodeList, filterValue));
}, [phoneCodeList]);
useOnChange(([prevPhoneCodeList]) => {
if (prevPhoneCodeList?.length === 0 && phoneCodeList.length > 0) {
updateFilter(filter);
useSyncEffect(([prevPhoneCodeList]) => {
if (!prevPhoneCodeList?.length && phoneCodeList.length) {
setFilteredList(getFilteredList(phoneCodeList, filter));
}
}, [phoneCodeList, updateFilter]);
}, [phoneCodeList, filter]);
const handleChange = useCallback((country: ApiCountryCode) => {
onChange(country);

View File

@ -12,7 +12,7 @@ import generateIdFor from '../../util/generateIdFor';
import useHeavyAnimationCheck from '../../hooks/useHeavyAnimationCheck';
import useBackgroundMode from '../../hooks/useBackgroundMode';
import useOnChange from '../../hooks/useOnChange';
import useSyncEffect from '../../hooks/useSyncEffect';
import useAppLayout from '../../hooks/useAppLayout';
export type OwnProps = {
@ -229,13 +229,13 @@ const AnimatedSticker: FC<OwnProps> = ({
fastRaf(unfreezeAnimation);
}, [unfreezeAnimation]);
useOnChange(([prevNoLoop]) => {
useSyncEffect(([prevNoLoop]) => {
if (prevNoLoop !== undefined && noLoop !== prevNoLoop) {
animation?.setNoLoop(noLoop);
}
}, [noLoop, animation]);
useOnChange(([prevSharedCanvasCoords, prevIsMobile]) => {
useSyncEffect(([prevSharedCanvasCoords, prevIsMobile]) => {
if (
(prevSharedCanvasCoords !== undefined && sharedCanvasCoords !== prevSharedCanvasCoords)
|| (prevIsMobile !== undefined && isMobile !== prevIsMobile)

View File

@ -1,4 +1,4 @@
import React, { useEffect } from '../../lib/teact/teact';
import React from '../../lib/teact/teact';
import { getActions, getGlobal, withGlobal } from '../../global';
import { ApiMediaFormat } from '../../api/types';
@ -13,8 +13,6 @@ import {
selectTabState,
} from '../../global/selectors';
import { DARK_THEME_BG_COLOR, LIGHT_THEME_BG_COLOR } from '../../config';
import useFlag from '../../hooks/useFlag';
import useShowTransition from '../../hooks/useShowTransition';
import { pause } from '../../util/schedulers';
import { preloadImage } from '../../util/files';
import preloadFonts from '../../util/fonts';
@ -22,6 +20,10 @@ import * as mediaLoader from '../../util/mediaLoader';
import { Bundles, loadModule } from '../../util/moduleLoader';
import buildClassName from '../../util/buildClassName';
import useFlag from '../../hooks/useFlag';
import useShowTransition from '../../hooks/useShowTransition';
import useEffectOnce from '../../hooks/useEffectOnce';
import styles from './UiLoader.module.scss';
import telegramLogoPath from '../../assets/telegram-logo.svg';
@ -114,7 +116,7 @@ const UiLoader: FC<OwnProps & StateProps> = ({
shouldRender: shouldRenderMask, transitionClassNames,
} = useShowTransition(!isReady, undefined, true);
useEffect(() => {
useEffectOnce(() => {
let timeout: number | undefined;
const safePreload = async () => {
@ -145,8 +147,7 @@ const UiLoader: FC<OwnProps & StateProps> = ({
setIsUiReady({ uiReadyState: 0 });
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
});
return (
<div

View File

@ -12,7 +12,7 @@ import { selectTabState, selectCurrentChat, selectIsForumPanelOpen } from '../..
import useFoldersReducer from '../../hooks/reducers/useFoldersReducer';
import { useResize } from '../../hooks/useResize';
import { useHotkeys } from '../../hooks/useHotkeys';
import useOnChange from '../../hooks/useOnChange';
import useSyncEffect from '../../hooks/useSyncEffect';
import Transition from '../ui/Transition';
import LeftMain from './main/LeftMain';
@ -372,7 +372,7 @@ const LeftColumn: FC<StateProps> = ({
}
}, [clearTwoFaError, loadPasswordInfo, settingsScreen]);
useOnChange(() => {
useSyncEffect(() => {
if (nextSettingsScreen !== undefined) {
setContent(LeftColumnContent.Settings);
setSettingsScreen(nextSettingsScreen);

View File

@ -117,7 +117,7 @@ const ChatList: FC<OwnProps> = ({
return;
}
openChat({ id: chatId, shouldReplaceHistory: true });
}, [], DRAG_ENTER_DEBOUNCE, true);
}, [openChat], DRAG_ENTER_DEBOUNCE, true);
const handleDragLeave = useCallback((e: React.DragEvent<HTMLDivElement>) => {
const rect = e.currentTarget.getBoundingClientRect();

View File

@ -1,4 +1,4 @@
import React, { memo, useRef } from '../../lib/teact/teact';
import React, { memo, useCallback, useRef } from '../../lib/teact/teact';
import { withGlobal } from '../../global';
import type { TabState } from '../../global/types';
@ -9,7 +9,7 @@ import buildStyle from '../../util/buildStyle';
import { selectTabState } from '../../global/selectors';
import useWindowSize from '../../hooks/useWindowSize';
import useOnChange from '../../hooks/useOnChange';
import useSyncEffect from '../../hooks/useSyncEffect';
import useForceUpdate from '../../hooks/useForceUpdate';
import useAppLayout from '../../hooks/useAppLayout';
@ -55,7 +55,7 @@ const ConfettiContainer: FC<StateProps> = ({ confetti }) => {
lastConfettiTime, top, width, left, height,
} = confetti || {};
function generateConfetti(w: number, h: number, amount = defaultConfettiAmount) {
const generateConfetti = useCallback((w: number, h: number, amount = defaultConfettiAmount) => {
for (let i = 0; i < amount; i++) {
const leftSide = i % 2;
const pos = {
@ -83,9 +83,9 @@ const ConfettiContainer: FC<StateProps> = ({ confetti }) => {
frameCount: 0,
});
}
}
}, [defaultConfettiAmount]);
const updateCanvas = () => {
const updateCanvas = useCallback(() => {
if (!canvasRef.current || !isRafStartedRef.current) {
return;
}
@ -166,9 +166,9 @@ const ConfettiContainer: FC<StateProps> = ({ confetti }) => {
} else {
isRafStartedRef.current = false;
}
};
}, []);
useOnChange(([prevConfettiTime]) => {
useSyncEffect(([prevConfettiTime]) => {
let hideTimeout: ReturnType<typeof setTimeout>;
if (prevConfettiTime !== lastConfettiTime) {
generateConfetti(width || windowSize.width, height || windowSize.height);
@ -179,11 +179,10 @@ const ConfettiContainer: FC<StateProps> = ({ confetti }) => {
}
}
return () => {
if (hideTimeout) {
clearTimeout(hideTimeout);
}
clearTimeout(hideTimeout);
};
}, [lastConfettiTime, updateCanvas]);
// eslint-disable-next-line react-hooks/exhaustive-deps -- Old timeout should be cleared only if new confetti is generated
}, [lastConfettiTime, forceUpdate, updateCanvas]);
if (!lastConfettiTime || Date.now() - lastConfettiTime > CONFETTI_FADEOUT_TIMEOUT) {
return undefined;

View File

@ -35,7 +35,7 @@ import { fastRaf } from '../../util/schedulers';
import useEffectWithPrevDeps from '../../hooks/useEffectWithPrevDeps';
import useBackgroundMode from '../../hooks/useBackgroundMode';
import useBeforeUnload from '../../hooks/useBeforeUnload';
import useOnChange from '../../hooks/useOnChange';
import useSyncEffect from '../../hooks/useSyncEffect';
import usePreventPinchZoomGesture from '../../hooks/usePreventPinchZoomGesture';
import useForceUpdate from '../../hooks/useForceUpdate';
import useShowTransition from '../../hooks/useShowTransition';
@ -272,7 +272,7 @@ const Main: FC<OwnProps & StateProps> = ({
ignoreCache: true,
});
}
}, [lastSyncTime, isMasterTab] as const);
}, [lastSyncTime, isMasterTab, loadCustomEmojis]);
// Sticker sets
useEffect(() => {
@ -324,7 +324,7 @@ const Main: FC<OwnProps & StateProps> = ({
type: parsedLocationHash.type,
});
}
}, [lastSyncTime] as const);
}, [lastSyncTime, openChat]);
const leftColumnTransition = useShowTransition(
isLeftColumnOpen, undefined, true, undefined, shouldSkipHistoryAnimations,
@ -333,8 +333,8 @@ const Main: FC<OwnProps & StateProps> = ({
const forceUpdate = useForceUpdate();
// Handle opening middle column
useOnChange(([prevIsLeftColumnOpen]) => {
if (prevIsLeftColumnOpen === undefined || animationLevel === 0) {
useSyncEffect(([prevIsLeftColumnOpen]) => {
if (prevIsLeftColumnOpen === undefined || isLeftColumnOpen === prevIsLeftColumnOpen || animationLevel === 0) {
return;
}
@ -353,7 +353,7 @@ const Main: FC<OwnProps & StateProps> = ({
willAnimateLeftColumnRef.current = false;
forceUpdate();
});
}, [isLeftColumnOpen]);
}, [animationLevel, forceUpdate, isLeftColumnOpen]);
const rightColumnTransition = useShowTransition(
isRightColumnOpen, undefined, true, undefined, shouldSkipHistoryAnimations,
@ -362,8 +362,8 @@ const Main: FC<OwnProps & StateProps> = ({
const [isNarrowMessageList, setIsNarrowMessageList] = useState(isRightColumnOpen);
// Handle opening right column
useOnChange(([prevIsRightColumnOpen]) => {
if (prevIsRightColumnOpen === undefined) {
useSyncEffect(([prevIsRightColumnOpen]) => {
if (prevIsRightColumnOpen === undefined || isRightColumnOpen === prevIsRightColumnOpen) {
return;
}
@ -382,7 +382,7 @@ const Main: FC<OwnProps & StateProps> = ({
forceUpdate();
setIsNarrowMessageList(isRightColumnOpen);
});
}, [isRightColumnOpen]);
}, [animationLevel, forceUpdate, isRightColumnOpen]);
const className = buildClassName(
leftColumnTransition.hasShownClass && 'left-column-shown',

View File

@ -18,7 +18,7 @@ import { extractCurrentThemeParams, validateHexColor } from '../../util/themeSty
import useInterval from '../../hooks/useInterval';
import useLang from '../../hooks/useLang';
import useOnChange from '../../hooks/useOnChange';
import useSyncEffect from '../../hooks/useSyncEffect';
import useWebAppFrame from './hooks/useWebAppFrame';
import usePrevious from '../../hooks/usePrevious';
import useFlag from '../../hooks/useFlag';
@ -258,20 +258,20 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
}, [handlePopupClose]);
// Notify view that height changed
useOnChange(() => {
useSyncEffect(() => {
setTimeout(() => {
sendViewport();
}, ANIMATION_WAIT);
}, [mainButton?.isVisible, sendViewport]);
// Notify view that theme changed
useOnChange(() => {
useSyncEffect(() => {
setTimeout(() => {
sendTheme();
}, ANIMATION_WAIT);
}, [theme, sendTheme]);
useOnChange(([prevIsPaymentModalOpen]) => {
useSyncEffect(([prevIsPaymentModalOpen]) => {
if (isPaymentModalOpen === prevIsPaymentModalOpen) return;
if (webApp?.slug && !isPaymentModalOpen && paymentStatus) {
sendEvent({
@ -285,7 +285,7 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
slug: undefined,
});
}
}, [isPaymentModalOpen, paymentStatus, sendEvent, setWebAppPaymentSlug, webApp] as const);
}, [isPaymentModalOpen, paymentStatus, sendEvent, setWebAppPaymentSlug, webApp]);
const handleToggleClick = useCallback(() => {
toggleAttachBot({

View File

@ -22,7 +22,7 @@ import renderText from '../../common/helpers/renderText';
import { getUserFullName } from '../../../global/helpers';
import useLang from '../../../hooks/useLang';
import useOnChange from '../../../hooks/useOnChange';
import useSyncEffect from '../../../hooks/useSyncEffect';
import Modal from '../../ui/Modal';
import Button from '../../ui/Button';
@ -170,11 +170,11 @@ const PremiumMainModal: FC<OwnProps & StateProps> = ({
}
}, [isSuccess, showConfetti]);
useOnChange(([prevIsPremium]) => {
useSyncEffect(([prevIsPremium]) => {
if (prevIsPremium === isPremium) return;
showConfetti();
}, [isPremium]);
}, [isPremium, showConfetti]);
if (!promo) return undefined;

View File

@ -120,7 +120,7 @@ const MediaViewerSlides: FC<OwnProps> = ({
forceUpdate();
}, [forceUpdate]);
const selectMediaDebounced = useDebouncedCallback(selectMedia, [], DEBOUNCE_MESSAGE, true);
const selectMediaDebounced = useDebouncedCallback(selectMedia, [selectMedia], DEBOUNCE_MESSAGE, true);
const clearSwipeDirectionDebounced = useDebouncedCallback(() => {
swipeDirectionRef.current = undefined;
}, [], DEBOUNCE_SWIPE, true);

View File

@ -50,7 +50,7 @@ import resetScroll, { patchChromiumScroll } from '../../util/resetScroll';
import fastSmoothScroll, { isAnimatingScroll } from '../../util/fastSmoothScroll';
import renderText from '../common/helpers/renderText';
import useOnChange from '../../hooks/useOnChange';
import useSyncEffect from '../../hooks/useSyncEffect';
import useStickyDates from './hooks/useStickyDates';
import { dispatchHeavyAnimationEvent } from '../../hooks/useHeavyAnimationCheck';
import useLang from '../../hooks/useLang';
@ -196,7 +196,7 @@ const MessageList: FC<OwnProps & StateProps> = ({
const areMessagesLoaded = Boolean(messageIds);
useOnChange(() => {
useSyncEffect(() => {
// We only need it first time when message list appears
if (areMessagesLoaded) {
onTickEnd(() => {
@ -206,24 +206,24 @@ const MessageList: FC<OwnProps & StateProps> = ({
}, [areMessagesLoaded]);
// Updated every time (to be used from intersection callback closure)
useOnChange(() => {
useSyncEffect(() => {
memoFirstUnreadIdRef.current = firstUnreadId;
}, [firstUnreadId]);
useOnChange(() => {
useEffect(() => {
if (!isCurrentUserPremium && isChannelChat && isReady && lastSyncTime) {
loadSponsoredMessages({ chatId });
}
}, [isCurrentUserPremium, chatId, isReady, isChannelChat, lastSyncTime]);
}, [isCurrentUserPremium, chatId, isReady, isChannelChat, lastSyncTime, loadSponsoredMessages]);
// Updated only once when messages are loaded (as we want the unread divider to keep its position)
useOnChange(() => {
useSyncEffect(() => {
if (areMessagesLoaded) {
memoUnreadDividerBeforeIdRef.current = memoFirstUnreadIdRef.current;
}
}, [areMessagesLoaded]);
useOnChange(() => {
useSyncEffect(() => {
memoFocusingIdRef.current = focusingId;
}, [focusingId]);
@ -344,7 +344,7 @@ const MessageList: FC<OwnProps & StateProps> = ({
}, [isChatLoaded, messageIds, loadMoreAround, focusingId, isRestricted]);
// Remember scroll position before repositioning it
useOnChange(() => {
useSyncEffect(() => {
if (!messageIds || !listItemElementsRef.current) {
return;
}
@ -488,7 +488,8 @@ const MessageList: FC<OwnProps & StateProps> = ({
// eslint-disable-next-line no-console
console.timeEnd('scrollTop');
}
// This should match deps for `useOnChange` above
// This should match deps for `useSyncEffect` above
// eslint-disable-next-line react-hooks/exhaustive-deps -- `as const` not yet supported by linter
}, [messageIds, isViewportNewest, containerHeight, hasTools] as const);
useEffectWithPrevDeps(([prevIsSelectModeActive]) => {

View File

@ -61,7 +61,7 @@ import useLang from '../../hooks/useLang';
import useHistoryBack from '../../hooks/useHistoryBack';
import usePrevious from '../../hooks/usePrevious';
import useForceUpdate from '../../hooks/useForceUpdate';
import useOnChange from '../../hooks/useOnChange';
import useSyncEffect from '../../hooks/useSyncEffect';
import useAppLayout from '../../hooks/useAppLayout';
import Transition from '../ui/Transition';
@ -243,7 +243,7 @@ const MiddleColumn: FC<OwnProps & StateProps> = ({
: undefined;
}, [chatId, openChat]);
useOnChange(() => {
useSyncEffect(() => {
setDropAreaState(DropAreaState.None);
setIsNotchShown(undefined);
}, [chatId]);
@ -704,7 +704,7 @@ function useIsReady(
}
}
useOnChange(() => {
useSyncEffect(() => {
if (!withAnimations) {
setIsReady(true);
}

View File

@ -88,7 +88,7 @@ import useContextMenuHandlers from '../../../hooks/useContextMenuHandlers';
import useLang from '../../../hooks/useLang';
import useSendMessageAction from '../../../hooks/useSendMessageAction';
import useInterval from '../../../hooks/useInterval';
import useOnChange from '../../../hooks/useOnChange';
import useSyncEffect from '../../../hooks/useSyncEffect';
import { useStateRef } from '../../../hooks/useStateRef';
import useVoiceRecording from './hooks/useVoiceRecording';
import useClipboardPaste from './hooks/useClipboardPaste';
@ -338,7 +338,7 @@ const Composer: FC<OwnProps & StateProps> = ({
}, [chat, chatId, isReady, lastSyncTime, loadSendAs, sendAsPeerIds]);
const shouldAnimateSendAsButtonRef = useRef(false);
useOnChange(([prevChatId, prevSendAsPeerIds]) => {
useSyncEffect(([prevChatId, prevSendAsPeerIds]) => {
// We only animate send-as button if `sendAsPeerIds` was missing when opening the chat
shouldAnimateSendAsButtonRef.current = Boolean(chatId === prevChatId && sendAsPeerIds && !prevSendAsPeerIds);
}, [chatId, sendAsPeerIds]);

View File

@ -185,7 +185,7 @@ const MessageInput: FC<OwnProps & StateProps> = ({
if (prevHtml !== undefined && prevHtml !== html) {
updateInputHeight(!html.length);
}
}, [html]);
}, [html, updateInputHeight]);
const chatIdRef = useRef(chatId);
chatIdRef.current = chatId;

View File

@ -9,7 +9,7 @@ import type { ISettings } from '../../../types';
import { RE_LINK_TEMPLATE } from '../../../config';
import { selectTabState, selectNoWebPage, selectTheme } from '../../../global/selectors';
import parseMessageInput from '../../../util/parseMessageInput';
import useOnChange from '../../../hooks/useOnChange';
import useSyncEffect from '../../../hooks/useSyncEffect';
import useShowTransition from '../../../hooks/useShowTransition';
import useCurrentOrPrev from '../../../hooks/useCurrentOrPrev';
import useDebouncedMemo from '../../../hooks/useDebouncedMemo';
@ -78,10 +78,10 @@ const WebPagePreview: FC<OwnProps & StateProps> = ({
}
}, [chatId, toggleMessageWebPage, clearWebPagePreview, link, loadWebPagePreview, threadId]);
useOnChange(() => {
useSyncEffect(() => {
clearWebPagePreview();
toggleMessageWebPage({ chatId, threadId });
}, [chatId]);
}, [chatId, clearWebPagePreview, threadId, toggleMessageWebPage]);
const isShown = Boolean(webPagePreview && messageText.length && !noWebPage && !disabled);
const { shouldRender, transitionClassNames } = useShowTransition(isShown);

View File

@ -56,6 +56,7 @@ const useEditing = (
focusEditableElement(messageInput, true);
}
});
// eslint-disable-next-line react-hooks/exhaustive-deps -- `as const` not yet supported by linter
}, [editedMessage, replyingToId, setHtml] as const);
useEffect(() => {

View File

@ -9,7 +9,7 @@ import { LOCAL_MESSAGE_MIN_ID, MESSAGE_LIST_SLICE } from '../../../config';
import { IS_SCROLL_PATCH_NEEDED, MESSAGE_LIST_SENSITIVE_AREA } from '../../../util/environment';
import { debounce } from '../../../util/schedulers';
import { useIntersectionObserver, useOnIntersect } from '../../../hooks/useIntersectionObserver';
import useOnChange from '../../../hooks/useOnChange';
import useSyncEffect from '../../../hooks/useSyncEffect';
const FAB_THRESHOLD = 50;
const NOTCH_THRESHOLD = 1; // Notch has zero height so we at least need a 1px margin to intersect
@ -136,14 +136,16 @@ export default function useScrollHooks(
useOnIntersect(fabTriggerRef, observeIntersectionForNotch);
useOnChange(() => {
const toggleScrollToolsRef = useRef<typeof toggleScrollTools>();
toggleScrollToolsRef.current = toggleScrollTools;
useSyncEffect(() => {
if (isReady) {
toggleScrollTools();
toggleScrollToolsRef.current!();
}
}, [isReady]);
// Workaround for FAB and notch flickering with tall incoming message
useOnChange(() => {
useSyncEffect(() => {
freezeForFab();
freezeForNotch();
@ -151,7 +153,7 @@ export default function useScrollHooks(
unfreezeForNotch();
unfreezeForFab();
}, TOOLS_FREEZE_TIMEOUT);
}, [messageIds]);
}, [freezeForFab, freezeForNotch, messageIds, unfreezeForFab, unfreezeForNotch]);
return { backwardsTriggerRef, forwardsTriggerRef, fabTriggerRef };
}

View File

@ -65,7 +65,7 @@ const Invoice: FC<OwnProps> = ({
contentEl.setAttribute(CUSTOM_APPENDIX_ATTRIBUTE, '');
});
}
}, [shouldAffectAppendix, photoUrl, isInSelectMode, isSelected, theme] as const);
}, [shouldAffectAppendix, photoUrl, isInSelectMode, isSelected, theme]);
return (
<div

View File

@ -161,7 +161,7 @@ const Location: FC<OwnProps> = ({
contentEl.setAttribute(CUSTOM_APPENDIX_ATTRIBUTE, '');
});
}
}, [shouldRenderText, isOwn, isInSelectMode, isSelected, theme, mapBlobUrl] as const);
}, [shouldRenderText, isOwn, isInSelectMode, isSelected, theme, mapBlobUrl]);
useEffect(() => {
// Prevent map refetching for slight location changes

View File

@ -161,7 +161,7 @@ const Photo: FC<OwnProps> = ({
} else {
contentEl.classList.add('has-appendix-thumb');
}
}, [shouldAffectAppendix, fullMediaData, isOwn, isInSelectMode, isSelected, theme] as const);
}, [shouldAffectAppendix, fullMediaData, isOwn, isInSelectMode, isSelected, theme]);
const { width, height, isSmall } = dimensions || calculateMediaDimensions(message, asForwarded, noAvatars, isMobile);

View File

@ -1,6 +1,6 @@
import { useEffect, useRef } from '../../../lib/teact/teact';
import useOnChange from '../../../hooks/useOnChange';
import useSyncEffect from '../../../hooks/useSyncEffect';
import useForceUpdate from '../../../hooks/useForceUpdate';
export default function useAsyncRendering<T extends any[]>(dependencies: T, delay?: number) {
@ -9,7 +9,7 @@ export default function useAsyncRendering<T extends any[]>(dependencies: T, dela
const timeoutRef = useRef<number>();
const forceUpdate = useForceUpdate();
useOnChange(() => {
useSyncEffect(() => {
if (isDisabled) {
return;
}
@ -20,6 +20,7 @@ export default function useAsyncRendering<T extends any[]>(dependencies: T, dela
clearTimeout(timeoutRef.current);
timeoutRef.current = undefined;
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, dependencies);
useEffect(() => {

View File

@ -38,7 +38,7 @@ export default function useProfileState(
}, PROGRAMMATIC_SCROLL_TIMEOUT_MS);
}
}
}, [tabType, isFirstTab, onProfileStateChange]);
}, [tabType, isFirstTab, onProfileStateChange, containerRef]);
// Scroll to top
useEffectWithPrevDeps(([prevProfileState]) => {
@ -70,7 +70,7 @@ export default function useProfileState(
}, PROGRAMMATIC_SCROLL_TIMEOUT_MS);
onProfileStateChange(profileState);
}, [profileState]);
}, [profileState, containerRef, onProfileStateChange]);
const determineProfileState = useCallback(() => {
const container = containerRef.current;

View File

@ -7,7 +7,7 @@ import type { ProfileTabType, SharedMediaType } from '../../../types';
import { MEMBERS_SLICE, MESSAGE_SEARCH_SLICE, SHARED_MEDIA_SLICE } from '../../../config';
import { getMessageContentIds, sortChatIds, sortUserIds } from '../../../global/helpers';
import useOnChange from '../../../hooks/useOnChange';
import useSyncEffect from '../../../hooks/useSyncEffect';
import useInfiniteScroll from '../../../hooks/useInfiniteScroll';
export default function useProfileViewportIds(
@ -150,11 +150,11 @@ function useInfiniteScrollForSharedMedia(
) {
const messageIdsRef = useRef<number[]>();
useOnChange(() => {
useSyncEffect(() => {
messageIdsRef.current = undefined;
}, [topicId]);
useOnChange(() => {
useSyncEffect(() => {
if (currentResultType === forSharedMediaType && chatMessages && foundIds) {
messageIdsRef.current = getMessageContentIds(
chatMessages,

View File

@ -18,7 +18,7 @@ import InputText from '../../ui/InputText';
import RadioGroup from '../../ui/RadioGroup';
import Button from '../../ui/Button';
import FloatingActionButton from '../../ui/FloatingActionButton';
import useOnChange from '../../../hooks/useOnChange';
import useSyncEffect from '../../../hooks/useSyncEffect';
import CalendarModal from '../../common/CalendarModal';
const DEFAULT_USAGE_LIMITS = [1, 10, 100];
@ -64,7 +64,7 @@ const ManageInvite: FC<OwnProps & StateProps> = ({
onBack: onClose,
});
useOnChange(([oldEditingInvite]) => {
useSyncEffect(([oldEditingInvite]) => {
if (oldEditingInvite === editingInvite) return;
if (!editingInvite) {
setTitle('');

View File

@ -3,7 +3,7 @@ import React, { memo, useCallback, useRef } from '../../lib/teact/teact';
import useVideoAutoPause from '../middle/message/hooks/useVideoAutoPause';
import useVideoCleanup from '../../hooks/useVideoCleanup';
import useBuffering from '../../hooks/useBuffering';
import useOnChange from '../../hooks/useOnChange';
import useSyncEffect from '../../hooks/useSyncEffect';
type OwnProps =
{
@ -40,13 +40,13 @@ function OptimizedVideo({
// This is only needed for browsers not allowing autoplay
const { isBuffered, bufferingHandlers } = useBuffering(true, onTimeUpdate);
const { onPlaying: handlePlayingForBuffering, ...otherBufferingHandlers } = bufferingHandlers;
useOnChange(([prevIsBuffered]) => {
useSyncEffect(([prevIsBuffered]) => {
if (prevIsBuffered === undefined) {
return;
}
handleReady();
}, [isBuffered]);
}, [isBuffered, handleReady]);
const handlePlaying = useCallback((e) => {
handlePlayingForAutoPause();

View File

@ -14,7 +14,7 @@ import {
import { selectTabState } from '../global/selectors';
import useEffectWithPrevDeps from './useEffectWithPrevDeps';
import useOnChange from './useOnChange';
import useSyncEffect from './useSyncEffect';
type Handler = (e: Event) => void;
@ -47,7 +47,7 @@ const useAudioPlayer = (
if (onTrackChange) onTrackChange();
}, [onTrackChange]);
useOnChange(() => {
useSyncEffect(() => {
controllerRef.current = register(trackId, trackType, (eventName, e) => {
switch (eventName) {
case 'onPlay': {
@ -105,6 +105,8 @@ const useAudioPlayer = (
if (!isPlaying && !proxy.paused) {
setIsPlaying(true);
// `isPlayingSync` is only needed to help `setIsPlaying` because it is asynchronous
// eslint-disable-next-line react-hooks/exhaustive-deps
isPlayingSync = true;
}

View File

@ -1,7 +1,7 @@
import { useRef } from '../lib/teact/teact';
import fastBlur from '../lib/fastBlur';
import useOnChange from './useOnChange';
import useSyncEffect from './useSyncEffect';
import useBlur from './useBlur';
import { imgToCanvas } from '../util/files';
@ -13,7 +13,8 @@ export default function useBlurSync(dataUri: string | false | undefined) {
let isChanged = false;
useOnChange(() => {
useSyncEffect(() => {
// eslint-disable-next-line react-hooks/exhaustive-deps
isChanged = true;
blurredRef.current = undefined;

View File

@ -2,7 +2,7 @@ import { useEffect, useRef } from '../lib/teact/teact';
import { IS_CANVAS_FILTER_SUPPORTED } from '../util/environment';
import fastBlur from '../lib/fastBlur';
import useOnChange from './useOnChange';
import useSyncEffect from './useSyncEffect';
const RADIUS = 2;
const ITERATIONS = 2;
@ -19,7 +19,7 @@ export default function useCanvasBlur(
const canvasRef = useRef<HTMLCanvasElement>(null);
const isStarted = useRef();
useOnChange(() => {
useSyncEffect(() => {
if (!isDisabled) {
isStarted.current = false;
}

View File

@ -1,7 +1,7 @@
import { useCallback, useRef, useState } from '../lib/teact/teact';
import useRunDebounced from './useRunDebounced';
import useOnChange from './useOnChange';
import useSyncEffect from './useSyncEffect';
import useHeavyAnimationCheck, { isHeavyAnimating } from './useHeavyAnimationCheck';
import useForceUpdate from './useForceUpdate';
@ -12,7 +12,7 @@ export default function useDebouncedMemo<R extends any, D extends any[]>(
const { isFrozen, updateWhenUnfrozen } = useHeavyAnimationFreeze();
const runDebounced = useRunDebounced(ms, true);
useOnChange(() => {
useSyncEffect(() => {
if (isFrozen) {
updateWhenUnfrozen();
return;
@ -21,6 +21,7 @@ export default function useDebouncedMemo<R extends any, D extends any[]>(
runDebounced(() => {
setValue(resolverFn());
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [...dependencies, isFrozen]);
return value;

View File

@ -0,0 +1,8 @@
import { useEffect } from '../lib/teact/teact';
function useEffectOnce(effect: React.EffectCallback) {
// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(effect, []);
}
export default useEffectOnce;

View File

@ -1,13 +1,13 @@
import { useCallback, useRef } from '../lib/teact/teact';
import useForceUpdate from './useForceUpdate';
import useOnChange from './useOnChange';
import useSyncEffect from './useSyncEffect';
export default function useForumPanelRender(isForumPanelOpen = false) {
const shouldRenderForumPanelRef = useRef(isForumPanelOpen);
const forceUpdate = useForceUpdate();
useOnChange(() => {
useSyncEffect(() => {
if (isForumPanelOpen) {
shouldRenderForumPanelRef.current = true;
}

View File

@ -1,9 +1,12 @@
import useOnChange from './useOnChange';
import { useEffect, useRef } from '../lib/teact/teact';
import { useCallback, useRef } from '../lib/teact/teact';
import { getActions } from '../lib/teact/teactn';
import { IS_TEST } from '../config';
import { fastRaf } from '../util/schedulers';
import { IS_IOS } from '../util/environment';
import { getActions } from '../lib/teact/teactn';
import useSyncEffect from './useSyncEffect';
import useEffectOnce from './useEffectOnce';
export const LOCATION_HASH = window.location.hash;
const PATH_BASE = `${window.location.pathname}${window.location.search}`;
@ -241,7 +244,7 @@ export default function useHistoryBack({
const isFirstRender = useRef(true);
const pushState = (forceReplace = false) => {
const pushState = useCallback((forceReplace = false) => {
// Check if the old state should be replaced
const shouldReplace = forceReplace || historyState[historyCursor].shouldBeReplaced;
indexRef.current = shouldReplace ? historyCursor : ++historyCursor;
@ -276,9 +279,9 @@ export default function useHistoryBack({
},
hash: hash ? `#${hash}` : undefined,
});
};
}, [hash, onBack, shouldBeReplaced]);
const processBack = () => {
const processBack = useCallback(() => {
// Only process back on open records
if (indexRef.current && historyState[indexRef.current] && !wasReplaced.current) {
historyState[indexRef.current].isClosed = true;
@ -287,19 +290,19 @@ export default function useHistoryBack({
historyCursor -= cleanupClosed();
}
}
};
}, [shouldBeReplaced]);
// Process back navigation when element is unmounted
useEffect(() => {
useEffectOnce(() => {
isFirstRender.current = false;
return () => {
if (!isActive || wasReplaced.current) return;
processBack();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
});
useOnChange(() => {
useSyncEffect(([prevIsActive]) => {
if (prevIsActive === isActive) return;
if (isFirstRender.current && !isActive) return;
if (isActive) {
@ -307,5 +310,5 @@ export default function useHistoryBack({
} else {
processBack();
}
}, [isActive]);
}, [isActive, processBack, pushState]);
}

View File

@ -4,6 +4,7 @@ import {
} from '../lib/teact/teact';
import { throttle, debounce } from '../util/schedulers';
import useEffectOnce from './useEffectOnce';
import useHeavyAnimationCheck from './useHeavyAnimationCheck';
type TargetCallback = (entry: IntersectionObserverEntry) => void;
@ -155,11 +156,10 @@ export function useIntersectionObserver({
export function useOnIntersect(
targetRef: RefObject<HTMLDivElement>, observe?: ObserveFn, callback?: TargetCallback,
) {
useEffect(() => {
useEffectOnce(() => {
return observe ? observe(targetRef.current!, callback) : undefined;
// Arguments should never change
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
});
}
export function useIsIntersecting(

View File

@ -1,13 +1,13 @@
import * as langProvider from '../util/langProvider';
import useForceUpdate from './useForceUpdate';
import useOnChange from './useOnChange';
import useSyncEffect from './useSyncEffect';
export type LangFn = langProvider.LangFn;
const useLang = (): LangFn => {
const forceUpdate = useForceUpdate();
useOnChange(() => {
useSyncEffect(() => {
return langProvider.addCallback(forceUpdate);
}, [forceUpdate]);

View File

@ -2,7 +2,7 @@ import { useRef } from '../lib/teact/teact';
import usePrevious from './usePrevious';
import useForceUpdate from './useForceUpdate';
import useOnChange from './useOnChange';
import useSyncEffect from './useSyncEffect';
export default function usePrevDuringAnimation(current: any, duration?: number) {
const prev = usePrevious(current, true);
@ -18,7 +18,7 @@ export default function usePrevDuringAnimation(current: any, duration?: number)
timeoutRef.current = undefined;
}
useOnChange(() => {
useSyncEffect(() => {
// When `current` becomes empty
if (duration && !isCurrentPresent && isPrevPresent && !timeoutRef.current) {
timeoutRef.current = window.setTimeout(() => {
@ -26,7 +26,7 @@ export default function usePrevDuringAnimation(current: any, duration?: number)
forceUpdate();
}, duration);
}
}, [current]);
}, [duration, forceUpdate, isCurrentPresent, isPrevPresent]);
return !timeoutRef.current || !duration || isCurrentPresent ? current : prev;
}

View File

@ -1,13 +1,13 @@
import { useRef } from '../lib/teact/teact';
import useOnChange from './useOnChange';
import useSyncEffect from './useSyncEffect';
// Allows to use state value as "silent" dependency in hooks (not causing updates).
// Useful for state values that update frequently (such as controlled input value).
export function useStateRef<T>(value: T) {
const ref = useRef<T>(value);
useOnChange(() => {
useSyncEffect(() => {
ref.current = value;
}, [value]);

View File

@ -1,10 +1,10 @@
import usePrevious from './usePrevious';
const useOnChange = <T extends readonly any[]>(cb: (args: T | readonly []) => void, dependencies: T) => {
const useSyncEffect = <T extends readonly any[]>(cb: (args: T | readonly []) => void, dependencies: T) => {
const prevDeps = usePrevious<T>(dependencies);
if (!prevDeps || dependencies.some((d, i) => d !== prevDeps[i])) {
cb(prevDeps || []);
}
};
export default useOnChange;
export default useSyncEffect;

View File

@ -10,7 +10,7 @@ const THROTTLE = 250;
const useWindowSize = () => {
const [size, setSize] = useState<ApiDimensions>(windowSize.get());
const [isResizing, setIsResizing] = useState(false);
const setIsResizingDebounced = useDebouncedCallback(setIsResizing, [], THROTTLE, true);
const setIsResizingDebounced = useDebouncedCallback(setIsResizing, [setIsResizing], THROTTLE, true);
const result = useMemo(() => ({ ...size, isResizing }), [isResizing, size]);