diff --git a/src/components/FlatList/index.android.tsx b/src/components/FlatList/index.android.tsx index 863930203863..1246367d29e8 100644 --- a/src/components/FlatList/index.android.tsx +++ b/src/components/FlatList/index.android.tsx @@ -1,7 +1,7 @@ import {useFocusEffect} from '@react-navigation/native'; import type {ForwardedRef} from 'react'; import React, {forwardRef, useCallback, useContext} from 'react'; -import type {FlatListProps} from 'react-native'; +import type {FlatListProps, NativeScrollEvent, NativeSyntheticEvent} from 'react-native'; import {FlatList} from 'react-native'; import {ActionListContext} from '@pages/home/ReportScreenContext'; @@ -22,6 +22,9 @@ function CustomFlatList(props: FlatListProps, ref: ForwardedRef) } }, [scrollPosition?.offset, ref]); + // eslint-disable-next-line react-hooks/exhaustive-deps + const onMomentumScrollEnd = useCallback((event: NativeSyntheticEvent) => setScrollPosition({offset: event.nativeEvent.contentOffset.y}), []); + useFocusEffect( useCallback(() => { onScreenFocus(); @@ -32,10 +35,8 @@ function CustomFlatList(props: FlatListProps, ref: ForwardedRef) // eslint-disable-next-line react/jsx-props-no-spreading {...props} - onScroll={(event) => props.onScroll?.(event)} - onMomentumScrollEnd={(event) => { - setScrollPosition({offset: event.nativeEvent.contentOffset.y}); - }} + onScroll={props.onScroll} + onMomentumScrollEnd={onMomentumScrollEnd} ref={ref} /> ); diff --git a/src/components/HeaderWithBackButton/types.ts b/src/components/HeaderWithBackButton/types.ts index 83afbad8636b..c3ffb500080b 100644 --- a/src/components/HeaderWithBackButton/types.ts +++ b/src/components/HeaderWithBackButton/types.ts @@ -1,9 +1,9 @@ import type {ReactNode} from 'react'; -import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx'; import type {Action} from '@hooks/useSingleExecution'; import type {StepCounterParams} from '@src/languages/types'; import type {AnchorPosition} from '@src/styles'; -import type {PersonalDetails, Policy, Report} from '@src/types/onyx'; +import type {Policy, Report} from '@src/types/onyx'; import type ChildrenProps from '@src/types/utils/ChildrenProps'; import type IconAsset from '@src/types/utils/IconAsset'; @@ -101,9 +101,6 @@ type HeaderWithBackButtonProps = Partial & { /** The report's policy, if we're showing the details for a report and need info about it for AvatarWithDisplay */ policy?: OnyxEntry; - /** Policies, if we're showing the details for a report and need participant details for AvatarWithDisplay */ - personalDetails?: OnyxCollection; - /** Single execution function to prevent concurrent navigation actions */ singleExecution?: (action: Action) => Action; diff --git a/src/components/InvertedFlatList/BaseInvertedFlatList.tsx b/src/components/InvertedFlatList/BaseInvertedFlatList.tsx index e28400505280..0549e19c2eb4 100644 --- a/src/components/InvertedFlatList/BaseInvertedFlatList.tsx +++ b/src/components/InvertedFlatList/BaseInvertedFlatList.tsx @@ -6,6 +6,11 @@ import FlatList from '@components/FlatList'; const WINDOW_SIZE = 15; const AUTOSCROLL_TO_TOP_THRESHOLD = 128; +const maintainVisibleContentPosition = { + minIndexForVisible: 0, + autoscrollToTopThreshold: AUTOSCROLL_TO_TOP_THRESHOLD, +}; + function BaseInvertedFlatList(props: FlatListProps, ref: ForwardedRef) { return ( (props: FlatListProps, ref: ForwardedRef ); diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index f5545f402b14..93eac30d5477 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -1,9 +1,8 @@ import {FlashList} from '@shopify/flash-list'; import type {ReactElement} from 'react'; -import React, {memo, useCallback} from 'react'; +import React, {memo, useCallback, useMemo} from 'react'; import {StyleSheet, View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; -import withCurrentReportID from '@components/withCurrentReportID'; import usePermissions from '@hooks/usePermissions'; import useThemeStyles from '@hooks/useThemeStyles'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; @@ -28,7 +27,6 @@ function LHNOptionsList({ preferredLocale = CONST.LOCALES.DEFAULT, personalDetails = {}, transactions = {}, - currentReportID = '', draftComments = {}, transactionViolations = {}, onFirstItemRendered = () => {}, @@ -86,7 +84,7 @@ function LHNOptionsList({ lastReportActionTransaction={lastReportActionTransaction} receiptTransactions={transactions} viewMode={optionMode} - isFocused={!shouldDisableFocusOptions && reportID === currentReportID} + isFocused={!shouldDisableFocusOptions} onSelectRow={onSelectRow} preferredLocale={preferredLocale} comment={itemComment} @@ -98,7 +96,6 @@ function LHNOptionsList({ ); }, [ - currentReportID, draftComments, onSelectRow, optionMode, @@ -116,6 +113,8 @@ function LHNOptionsList({ ], ); + const extraData = useMemo(() => [reportActions, reports, policy, personalDetails], [reportActions, reports, policy, personalDetails]); + return ( @@ -136,33 +135,31 @@ function LHNOptionsList({ LHNOptionsList.displayName = 'LHNOptionsList'; -export default withCurrentReportID( - withOnyx({ - reports: { - key: ONYXKEYS.COLLECTION.REPORT, - }, - reportActions: { - key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, - }, - policy: { - key: ONYXKEYS.COLLECTION.POLICY, - }, - preferredLocale: { - key: ONYXKEYS.NVP_PREFERRED_LOCALE, - }, - personalDetails: { - key: ONYXKEYS.PERSONAL_DETAILS_LIST, - }, - transactions: { - key: ONYXKEYS.COLLECTION.TRANSACTION, - }, - draftComments: { - key: ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, - }, - transactionViolations: { - key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, - }, - })(memo(LHNOptionsList)), -); +export default withOnyx({ + reports: { + key: ONYXKEYS.COLLECTION.REPORT, + }, + reportActions: { + key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, + }, + policy: { + key: ONYXKEYS.COLLECTION.POLICY, + }, + preferredLocale: { + key: ONYXKEYS.NVP_PREFERRED_LOCALE, + }, + personalDetails: { + key: ONYXKEYS.PERSONAL_DETAILS_LIST, + }, + transactions: { + key: ONYXKEYS.COLLECTION.TRANSACTION, + }, + draftComments: { + key: ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, + }, + transactionViolations: { + key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, + }, +})(memo(LHNOptionsList)); export type {LHNOptionsListProps}; diff --git a/src/components/LHNOptionsList/OptionRowLHNData.tsx b/src/components/LHNOptionsList/OptionRowLHNData.tsx index a3394190d0c1..9b22b50b64fe 100644 --- a/src/components/LHNOptionsList/OptionRowLHNData.tsx +++ b/src/components/LHNOptionsList/OptionRowLHNData.tsx @@ -1,5 +1,6 @@ import {deepEqual} from 'fast-equals'; import React, {useEffect, useMemo, useRef} from 'react'; +import useCurrentReportID from '@hooks/useCurrentReportID'; import * as ReportUtils from '@libs/ReportUtils'; import SidebarUtils from '@libs/SidebarUtils'; import * as Report from '@userActions/Report'; @@ -32,6 +33,8 @@ function OptionRowLHNData({ ...propsToForward }: OptionRowLHNDataProps) { const reportID = propsToForward.reportID; + const currentReportIDValue = useCurrentReportID(); + const isReportFocused = isFocused && currentReportIDValue?.currentReportID === reportID; const optionItemRef = useRef(); @@ -85,7 +88,7 @@ function OptionRowLHNData({ ); diff --git a/src/components/LHNOptionsList/types.ts b/src/components/LHNOptionsList/types.ts index c122ab018392..4ca30358f9b1 100644 --- a/src/components/LHNOptionsList/types.ts +++ b/src/components/LHNOptionsList/types.ts @@ -3,7 +3,6 @@ import type {RefObject} from 'react'; import type {LayoutChangeEvent, StyleProp, TextStyle, View, ViewStyle} from 'react-native'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; -import type {CurrentReportIDContextValue} from '@components/withCurrentReportID'; import type CONST from '@src/CONST'; import type {OptionData} from '@src/libs/ReportUtils'; import type {Locale, PersonalDetailsList, Policy, Report, ReportAction, ReportActions, Transaction, TransactionViolation} from '@src/types/onyx'; @@ -64,7 +63,7 @@ type CustomLHNOptionsListProps = { reportIDsWithErrors: Record; }; -type LHNOptionsListProps = CustomLHNOptionsListProps & CurrentReportIDContextValue & LHNOptionsListOnyxProps; +type LHNOptionsListProps = CustomLHNOptionsListProps & LHNOptionsListOnyxProps; type OptionRowLHNDataProps = { /** Whether row should be focused */ diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 102f85ea49b9..9db0be391598 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -21,7 +21,6 @@ import ConfirmModal from './ConfirmModal'; import HeaderWithBackButton from './HeaderWithBackButton'; import * as Expensicons from './Icon/Expensicons'; import MoneyReportHeaderStatusBar from './MoneyReportHeaderStatusBar'; -import {usePersonalDetails} from './OnyxProvider'; import SettlementButton from './SettlementButton'; type PaymentType = DeepValueOf; @@ -46,7 +45,6 @@ type MoneyReportHeaderProps = MoneyReportHeaderOnyxProps & { }; function MoneyReportHeader({session, policy, chatReport, nextStep, report: moneyRequestReport}: MoneyReportHeaderProps) { - const personalDetails = usePersonalDetails() || CONST.EMPTY_OBJECT; const styles = useThemeStyles(); const {translate} = useLocalize(); const {windowWidth, isSmallScreenWidth} = useWindowDimensions(); @@ -117,7 +115,6 @@ function MoneyReportHeader({session, policy, chatReport, nextStep, report: money shouldShowPinButton={false} report={moneyRequestReport} policy={policy} - personalDetails={personalDetails} shouldShowBackButton={isSmallScreenWidth} onBackButtonPress={() => Navigation.goBack(undefined, false, true)} // Shows border if no buttons or next steps are showing below the header diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index fe8cc3506b3f..a4670439896f 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -22,7 +22,6 @@ import HeaderWithBackButton from './HeaderWithBackButton'; import HoldBanner from './HoldBanner'; import * as Expensicons from './Icon/Expensicons'; import MoneyRequestHeaderStatusBar from './MoneyRequestHeaderStatusBar'; -import {usePersonalDetails} from './OnyxProvider'; import ProcessMoneyRequestHoldMenu from './ProcessMoneyRequestHoldMenu'; type MoneyRequestHeaderOnyxProps = { @@ -55,7 +54,6 @@ type MoneyRequestHeaderProps = MoneyRequestHeaderOnyxProps & { }; function MoneyRequestHeader({session, parentReport, report, parentReportAction, transaction, shownHoldUseExplanation = false, policy}: MoneyRequestHeaderProps) { - const personalDetails = usePersonalDetails() || CONST.EMPTY_OBJECT; const styles = useThemeStyles(); const {translate} = useLocalize(); const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false); @@ -173,7 +171,6 @@ function MoneyRequestHeader({session, parentReport, report, parentReportAction, ownerAccountID: parentReport?.ownerAccountID, }} policy={policy} - personalDetails={personalDetails} shouldShowBackButton={isSmallScreenWidth} onBackButtonPress={() => Navigation.goBack(undefined, false, true)} /> diff --git a/src/libs/Pusher/pusher.ts b/src/libs/Pusher/pusher.ts index bc48111eadc5..6e5074fd92d3 100644 --- a/src/libs/Pusher/pusher.ts +++ b/src/libs/Pusher/pusher.ts @@ -1,5 +1,6 @@ import isObject from 'lodash/isObject'; import type {Channel, ChannelAuthorizerGenerator, Options} from 'pusher-js/with-encryption'; +import {InteractionManager} from 'react-native'; import Onyx from 'react-native-onyx'; import type {LiteralUnion, ValueOf} from 'type-fest'; import Log from '@libs/Log'; @@ -228,48 +229,50 @@ function subscribe( onResubscribe = () => {}, ): Promise { return new Promise((resolve, reject) => { - // We cannot call subscribe() before init(). Prevent any attempt to do this on dev. - if (!socket) { - throw new Error(`[Pusher] instance not found. Pusher.subscribe() + InteractionManager.runAfterInteractions(() => { + // We cannot call subscribe() before init(). Prevent any attempt to do this on dev. + if (!socket) { + throw new Error(`[Pusher] instance not found. Pusher.subscribe() most likely has been called before Pusher.init()`); - } + } - Log.info('[Pusher] Attempting to subscribe to channel', false, {channelName, eventName}); - let channel = getChannel(channelName); - - if (!channel || !channel.subscribed) { - channel = socket.subscribe(channelName); - let isBound = false; - channel.bind('pusher:subscription_succeeded', () => { - // Check so that we do not bind another event with each reconnect attempt - if (!isBound) { - bindEventToChannel(channel, eventName, eventCallback); - resolve(); - isBound = true; - return; - } - - // When subscribing for the first time we register a success callback that can be - // called multiple times when the subscription succeeds again in the future - // e.g. as a result of Pusher disconnecting and reconnecting. This callback does - // not fire on the first subscription_succeeded event. - onResubscribe(); - }); - - channel.bind('pusher:subscription_error', (data: PusherSubscribtionErrorData = {}) => { - const {type, error, status} = data; - Log.hmmm('[Pusher] Issue authenticating with Pusher during subscribe attempt.', { - channelName, - status, - type, - error, + Log.info('[Pusher] Attempting to subscribe to channel', false, {channelName, eventName}); + let channel = getChannel(channelName); + + if (!channel || !channel.subscribed) { + channel = socket.subscribe(channelName); + let isBound = false; + channel.bind('pusher:subscription_succeeded', () => { + // Check so that we do not bind another event with each reconnect attempt + if (!isBound) { + bindEventToChannel(channel, eventName, eventCallback); + resolve(); + isBound = true; + return; + } + + // When subscribing for the first time we register a success callback that can be + // called multiple times when the subscription succeeds again in the future + // e.g. as a result of Pusher disconnecting and reconnecting. This callback does + // not fire on the first subscription_succeeded event. + onResubscribe(); }); - reject(error); - }); - } else { - bindEventToChannel(channel, eventName, eventCallback); - resolve(); - } + + channel.bind('pusher:subscription_error', (data: PusherSubscribtionErrorData = {}) => { + const {type, error, status} = data; + Log.hmmm('[Pusher] Issue authenticating with Pusher during subscribe attempt.', { + channelName, + status, + type, + error, + }); + reject(error); + }); + } else { + bindEventToChannel(channel, eventName, eventCallback); + resolve(); + } + }); }); } diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 7ad12cf3e1ed..9aded787e262 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -2873,6 +2873,17 @@ function clearNewRoomFormError() { }); } +function getReportDraftStatus(reportID: string) { + if (!allReports) { + return false; + } + + if (!allReports[reportID]) { + return false; + } + return allReports[reportID]?.hasDraft; +} + function resolveActionableMentionWhisper(reportId: string, reportAction: OnyxEntry, resolution: ValueOf) { const message = reportAction?.message?.[0]; if (!message) { @@ -2923,6 +2934,7 @@ function resolveActionableMentionWhisper(reportId: string, reportAction: OnyxEnt } export { + getReportDraftStatus, searchInServer, addComment, addAttachment, diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index da5a8e4aae27..2e19a2c6a940 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -2,7 +2,7 @@ import {useIsFocused} from '@react-navigation/native'; import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; import React, {memo, useCallback, useEffect, useMemo, useRef, useState} from 'react'; -import {View} from 'react-native'; +import {InteractionManager, View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import Banner from '@components/Banner'; @@ -387,8 +387,13 @@ function ReportScreen({ Performance.markEnd(CONST.TIMING.CHAT_RENDER); fetchReportIfNeeded(); - ComposerActions.setShouldShowComposeInput(true); + const interactionTask = InteractionManager.runAfterInteractions(() => { + ComposerActions.setShouldShowComposeInput(true); + }); return () => { + if (interactionTask) { + interactionTask.cancel(); + } if (!didSubscribeToReportLeavingEvents) { return; } @@ -474,10 +479,20 @@ function ReportScreen({ // any `pendingFields.createChat` or `pendingFields.addWorkspaceRoom` fields are set to null. // Existing reports created will have empty fields for `pendingFields`. const didCreateReportSuccessfully = !report.pendingFields || (!report.pendingFields.addWorkspaceRoom && !report.pendingFields.createChat); + let interactionTask; if (!didSubscribeToReportLeavingEvents.current && didCreateReportSuccessfully) { - Report.subscribeToReportLeavingEvents(reportID); - didSubscribeToReportLeavingEvents.current = true; + interactionTask = InteractionManager.runAfterInteractions(() => { + Report.subscribeToReportLeavingEvents(reportID); + didSubscribeToReportLeavingEvents.current = true; + }); } + + return () => { + if (!interactionTask) { + return; + } + interactionTask.cancel(); + }; }, [report, didSubscribeToReportLeavingEvents, reportID]); const onListLayout = useCallback((e) => { @@ -571,8 +586,8 @@ function ReportScreen({ )} {/* Note: The ReportActionsSkeletonView should be allowed to mount even if the initial report actions are not loaded. - If we prevent rendering the report while they are loading then - we'll unnecessarily unmount the ReportActionsView which will clear the new marker lines initial state. */} + If we prevent rendering the report while they are loading then + we'll unnecessarily unmount the ReportActionsView which will clear the new marker lines initial state. */} {(!isReportReadyForDisplay || isLoadingInitialReportActions || isLoading) && } {isReportReadyForDisplay ? ( @@ -584,9 +599,7 @@ function ReportScreen({ isEmptyChat={isEmptyChat} lastReportAction={lastReportAction} /> - ) : ( - - )} + ) : null} diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx index af2d0b9eab56..308eac71b5b7 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx @@ -419,15 +419,23 @@ function ComposerWithSuggestions( Report.setReportWithDraft(reportID, true); } + const hasDraftStatus = Report.getReportDraftStatus(reportID); + + /** + * The extra `!hasDraftStatus` check is to prevent the draft being set + * when the user navigates to the ReportScreen. This doesn't alter anything + * in terms of functionality. + */ // The draft has been deleted. - if (newCommentConverted.length === 0) { + if (newCommentConverted.length === 0 && hasDraftStatus) { Report.setReportWithDraft(reportID, false); } commentRef.current = newCommentConverted; + const isDraftCommentEmpty = getDraftComment(reportID) === ''; if (shouldDebounceSaveComment) { debouncedSaveReportComment(reportID, newCommentConverted); - } else { + } else if (isDraftCommentEmpty && newCommentConverted.length !== 0) { Report.saveReportComment(reportID, newCommentConverted || ''); } if (newCommentConverted) { @@ -676,13 +684,6 @@ function ComposerWithSuggestions( useEffect(() => { // Scrolls the composer to the bottom and sets the selection to the end, so that longer drafts are easier to edit updateMultilineInputRange(textInputRef.current, !!shouldAutoFocus); - - if (value.length === 0) { - return; - } - - Report.setReportWithDraft(reportID, true); - // eslint-disable-next-line react-hooks/exhaustive-deps }, []); useImperativeHandle( diff --git a/src/pages/home/report/ReportActionCompose/SilentCommentUpdater/index.tsx b/src/pages/home/report/ReportActionCompose/SilentCommentUpdater/index.tsx index 1abc6567bc7b..f3780528cabe 100644 --- a/src/pages/home/report/ReportActionCompose/SilentCommentUpdater/index.tsx +++ b/src/pages/home/report/ReportActionCompose/SilentCommentUpdater/index.tsx @@ -17,7 +17,17 @@ function SilentCommentUpdater({comment, commentRef, reportID, value, updateComme const prevPreferredLocale = usePrevious(preferredLocale); useEffect(() => { + /** + * Schedules the callback to run when the main thread is idle. + */ + if ('requestIdleCallback' in window) { + const callbackID = requestIdleCallback(() => { + updateComment(comment ?? ''); + }); + return () => cancelIdleCallback(callbackID); + } updateComment(comment ?? ''); + // eslint-disable-next-line react-hooks/exhaustive-deps -- We need to run this on mount }, []); diff --git a/src/pages/home/report/ReportActionsList.js b/src/pages/home/report/ReportActionsList.js index 3ace2ebeb436..9a39a272cd50 100644 --- a/src/pages/home/report/ReportActionsList.js +++ b/src/pages/home/report/ReportActionsList.js @@ -125,6 +125,8 @@ function isMessageUnread(message, lastReadTime) { return Boolean(message && lastReadTime && message.created && lastReadTime < message.created); } +const onScrollToIndexFailed = () => {}; + function ReportActionsList({ report, parentReportAction, @@ -302,7 +304,9 @@ function ReportActionsList({ if (unsubscribe) { unsubscribe(); } - Report.unsubscribeFromReportChannel(report.reportID); + InteractionManager.runAfterInteractions(() => { + Report.unsubscribeFromReportChannel(report.reportID); + }); }; newActionUnsubscribeMap[report.reportID] = cleanup; @@ -329,11 +333,12 @@ function ReportActionsList({ } }; - const trackVerticalScrolling = (event) => { + const trackVerticalScrolling = useCallback((event) => { scrollingVerticalOffset.current = event.nativeEvent.contentOffset.y; handleUnreadFloatingButton(); onScroll(event); - }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); const scrollToBottomAndMarkReportAsRead = () => { reportScrollManager.scrollToBottom(); @@ -432,7 +437,7 @@ function ReportActionsList({ // Native mobile does not render updates flatlist the changes even though component did update called. // To notify there something changes we can use extraData prop to flatlist - const extraData = [isSmallScreenWidth ? currentUnreadMarker : undefined, ReportUtils.isArchivedRoom(report)]; + const extraData = useMemo(() => [isSmallScreenWidth ? currentUnreadMarker : undefined, ReportUtils.isArchivedRoom(report)], [currentUnreadMarker, isSmallScreenWidth, report]); const hideComposer = !ReportUtils.canUserPerformWriteAction(report); const shouldShowReportRecipientLocalTime = ReportUtils.canShowReportRecipientLocalTime(personalDetailsList, report, currentUserPersonalDetails.accountID) && !isComposerFullSize; @@ -509,7 +514,7 @@ function ReportActionsList({ keyboardShouldPersistTaps="handled" onLayout={onLayoutInner} onScroll={trackVerticalScrolling} - onScrollToIndexFailed={() => {}} + onScrollToIndexFailed={onScrollToIndexFailed} extraData={extraData} /> diff --git a/src/pages/home/report/ReportActionsView.js b/src/pages/home/report/ReportActionsView.js index 5e9d863dd62d..449f9b8b504b 100755 --- a/src/pages/home/report/ReportActionsView.js +++ b/src/pages/home/report/ReportActionsView.js @@ -2,6 +2,7 @@ import {useIsFocused} from '@react-navigation/native'; import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; import React, {useCallback, useContext, useEffect, useMemo, useRef} from 'react'; +import {InteractionManager} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import networkPropTypes from '@components/networkPropTypes'; @@ -117,7 +118,15 @@ function ReportActionsView(props) { }; useEffect(() => { - openReportIfNecessary(); + const interactionTask = InteractionManager.runAfterInteractions(() => { + openReportIfNecessary(); + }); + return () => { + if (!interactionTask) { + return; + } + interactionTask.cancel(); + }; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); @@ -171,10 +180,20 @@ function ReportActionsView(props) { // any `pendingFields.createChat` or `pendingFields.addWorkspaceRoom` fields are set to null. // Existing reports created will have empty fields for `pendingFields`. const didCreateReportSuccessfully = !props.report.pendingFields || (!props.report.pendingFields.addWorkspaceRoom && !props.report.pendingFields.createChat); + let interactionTask; if (!didSubscribeToReportTypingEvents.current && didCreateReportSuccessfully) { - Report.subscribeToReportTypingEvents(reportID); - didSubscribeToReportTypingEvents.current = true; + interactionTask = InteractionManager.runAfterInteractions(() => { + Report.subscribeToReportTypingEvents(reportID); + didSubscribeToReportTypingEvents.current = true; + }); } + + return () => { + if (!interactionTask) { + return; + } + interactionTask.cancel(); + }; }, [props.report.pendingFields, didSubscribeToReportTypingEvents, reportID]); const oldestReportAction = useMemo(() => _.last(props.reportActions), [props.reportActions]); diff --git a/src/pages/home/sidebar/SidebarLinks.js b/src/pages/home/sidebar/SidebarLinks.js index 0a97f00c5002..dc5d5d4d07dd 100644 --- a/src/pages/home/sidebar/SidebarLinks.js +++ b/src/pages/home/sidebar/SidebarLinks.js @@ -1,7 +1,7 @@ /* eslint-disable rulesdir/onyx-props-must-have-default */ import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; -import React, {useCallback, useEffect, useMemo, useRef} from 'react'; +import React, {memo, useCallback, useEffect, useMemo, useRef} from 'react'; import {InteractionManager, StyleSheet, View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; @@ -173,5 +173,5 @@ export default withOnyx({ activePolicy: { key: ({activeWorkspaceID}) => `${ONYXKEYS.COLLECTION.POLICY}${activeWorkspaceID}`, }, -})(SidebarLinks); +})(memo(SidebarLinks)); export {basePropTypes};