Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf: improve chat switch performance #32336

Merged
merged 47 commits into from
Mar 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
fcd95ef
perf: make LHNOptionsList lightweight
hurali97 Nov 17, 2023
eca2295
feat: add util methods for report and reportactions
hurali97 Nov 17, 2023
def3086
perf: add InteractionManager to lazily call methods
hurali97 Nov 17, 2023
7f51b48
perf: avoid unnecessary updating onyx
hurali97 Nov 17, 2023
cacb926
perf: memoize SidebarLinks
hurali97 Nov 17, 2023
320f011
Merge branch 'main' of https://github.com/callstack-internal/Expensif…
hurali97 Nov 28, 2023
e592749
Merge branch 'main' of https://github.com/callstack-internal/Expensif…
hurali97 Dec 1, 2023
127bff0
Merge branch 'main' of https://github.com/callstack-internal/Expensif…
hurali97 Dec 1, 2023
518426c
perf: avoid re-creating function and object instances
hurali97 Dec 1, 2023
e094eba
perf: delay updateComment to run on mount when JS thread is idle
hurali97 Dec 1, 2023
f215b7f
fix: linting and ts issues
hurali97 Dec 4, 2023
49fedcc
perf: apply perf improvements from pr 30168
hurali97 Dec 5, 2023
a270f08
Merge branch 'main' of https://github.com/callstack-internal/Expensif…
hurali97 Dec 6, 2023
41d47dc
fix: personal details
hurali97 Dec 7, 2023
c968ccf
revert: bring back old improvements for LHN
hurali97 Dec 8, 2023
6975566
fix: personalDetails in HeaderView
hurali97 Dec 8, 2023
b7fa916
refactor: remove unused code
hurali97 Dec 8, 2023
dc1783b
Merge branch 'main' of https://github.com/callstack-internal/Expensif…
hurali97 Dec 11, 2023
027af7f
fix: linting
hurali97 Dec 11, 2023
200a7bd
fix: add reports to extraData in LHNOptionsList
hurali97 Dec 11, 2023
15f47bc
Merge branch 'main' of https://github.com/callstack-internal/Expensif…
hurali97 Dec 12, 2023
fac391e
Merge branch 'main' of https://github.com/callstack-internal/Expensif…
hurali97 Dec 14, 2023
e37e16d
fix: skeleton not showing and screen transition on mWeb
hurali97 Dec 14, 2023
5d2fc39
Merge branch 'main' of https://github.com/callstack-internal/Expensif…
hurali97 Dec 15, 2023
aa3b9cb
Merge branch 'main' of https://github.com/callstack-internal/Expensif…
hurali97 Dec 18, 2023
2c553a1
Merge branch 'main' of https://github.com/callstack-internal/Expensif…
hurali97 Dec 19, 2023
fcf15a3
refactor: use didScreenTransitionEnd
hurali97 Dec 19, 2023
e4682e9
fix: typecheck in AvatarWithDisplayName
hurali97 Dec 19, 2023
b570ca2
Merge branch 'main' of https://github.com/callstack-internal/Expensif…
hurali97 Dec 22, 2023
d43aea5
Merge branch 'main' of https://github.com/callstack-internal/Expensif…
hurali97 Dec 27, 2023
5125f5c
Merge branch 'main' of https://github.com/callstack-internal/Expensif…
hurali97 Dec 29, 2023
da159d6
Merge branch 'main' of https://github.com/callstack-internal/Expensif…
hurali97 Jan 3, 2024
eb7eaa6
refactor: remove not needed personal details
hurali97 Jan 3, 2024
50a9bc3
Merge branch 'main' of https://github.com/callstack-internal/Expensif…
hurali97 Jan 8, 2024
8b71ff6
test: fix onEntryTransition not being called in UnitTests
hurali97 Jan 8, 2024
c99037a
Merge branch 'main' of https://github.com/callstack-internal/Expensif…
hurali97 Jan 10, 2024
d247973
Merge branch 'main' of https://github.com/callstack-internal/Expensif…
hurali97 Jan 15, 2024
7787e9c
Merge branch 'main' of https://github.com/callstack-internal/Expensif…
hurali97 Feb 7, 2024
487ea3d
refactor: remove dead code
hurali97 Feb 7, 2024
a6625e1
Merge branch 'main' of https://github.com/callstack-internal/Expensif…
hurali97 Feb 9, 2024
5fd6ef0
Merge branch 'main' of https://github.com/callstack-internal/Expensif…
hurali97 Feb 12, 2024
a30e72d
Merge branch 'main' of https://github.com/callstack-internal/Expensif…
hurali97 Feb 13, 2024
99163c7
Merge branch 'main' of https://github.com/callstack-internal/Expensif…
hurali97 Feb 16, 2024
86e1e5c
fix: skeleton loader being shown on chat switch each time
hurali97 Feb 16, 2024
a28dcdc
fix: prettier
hurali97 Feb 16, 2024
46c97ef
Merge branch 'main' into hur/perf/chat-switch
jbroma Feb 20, 2024
8baf559
Merge branch 'main' into hur/perf/chat-switch
jbroma Mar 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions src/components/FlatList/index.android.tsx
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -22,6 +22,9 @@ function CustomFlatList<T>(props: FlatListProps<T>, ref: ForwardedRef<FlatList>)
}
}, [scrollPosition?.offset, ref]);

// eslint-disable-next-line react-hooks/exhaustive-deps
const onMomentumScrollEnd = useCallback((event: NativeSyntheticEvent<NativeScrollEvent>) => setScrollPosition({offset: event.nativeEvent.contentOffset.y}), []);

useFocusEffect(
useCallback(() => {
onScreenFocus();
Expand All @@ -32,10 +35,8 @@ function CustomFlatList<T>(props: FlatListProps<T>, ref: ForwardedRef<FlatList>)
<FlatList<T>
// 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}
/>
);
Expand Down
7 changes: 2 additions & 5 deletions src/components/HeaderWithBackButton/types.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -101,9 +101,6 @@ type HeaderWithBackButtonProps = Partial<ChildrenProps> & {
/** The report's policy, if we're showing the details for a report and need info about it for AvatarWithDisplay */
policy?: OnyxEntry<Policy>;

/** Policies, if we're showing the details for a report and need participant details for AvatarWithDisplay */
personalDetails?: OnyxCollection<PersonalDetails>;

/** Single execution function to prevent concurrent navigation actions */
singleExecution?: <T extends unknown[]>(action: Action<T>) => Action<T>;

Expand Down
10 changes: 6 additions & 4 deletions src/components/InvertedFlatList/BaseInvertedFlatList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,19 @@ 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<T>(props: FlatListProps<T>, ref: ForwardedRef<FlatList>) {
return (
<FlatList
// eslint-disable-next-line react/jsx-props-no-spreading
{...props}
ref={ref}
windowSize={WINDOW_SIZE}
maintainVisibleContentPosition={{
minIndexForVisible: 0,
autoscrollToTopThreshold: AUTOSCROLL_TO_TOP_THRESHOLD,
}}
maintainVisibleContentPosition={maintainVisibleContentPosition}
inverted
/>
);
Expand Down
65 changes: 31 additions & 34 deletions src/components/LHNOptionsList/LHNOptionsList.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -28,7 +27,6 @@ function LHNOptionsList({
preferredLocale = CONST.LOCALES.DEFAULT,
personalDetails = {},
transactions = {},
currentReportID = '',
draftComments = {},
transactionViolations = {},
onFirstItemRendered = () => {},
Expand Down Expand Up @@ -86,7 +84,7 @@ function LHNOptionsList({
lastReportActionTransaction={lastReportActionTransaction}
receiptTransactions={transactions}
viewMode={optionMode}
isFocused={!shouldDisableFocusOptions && reportID === currentReportID}
isFocused={!shouldDisableFocusOptions}
onSelectRow={onSelectRow}
preferredLocale={preferredLocale}
comment={itemComment}
Expand All @@ -98,7 +96,6 @@ function LHNOptionsList({
);
},
[
currentReportID,
draftComments,
onSelectRow,
optionMode,
Expand All @@ -116,6 +113,8 @@ function LHNOptionsList({
],
);

const extraData = useMemo(() => [reportActions, reports, policy, personalDetails], [reportActions, reports, policy, personalDetails]);

return (
<View style={style ?? styles.flex1}>
<FlashList
Expand All @@ -127,7 +126,7 @@ function LHNOptionsList({
keyExtractor={keyExtractor}
renderItem={renderItem}
estimatedItemSize={optionMode === CONST.OPTION_MODE.COMPACT ? variables.optionRowHeightCompact : variables.optionRowHeight}
extraData={[currentReportID]}
extraData={extraData}
showsVerticalScrollIndicator={false}
/>
</View>
Expand All @@ -136,33 +135,31 @@ function LHNOptionsList({

LHNOptionsList.displayName = 'LHNOptionsList';

export default withCurrentReportID(
withOnyx<LHNOptionsListProps, LHNOptionsListOnyxProps>({
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<LHNOptionsListProps, LHNOptionsListOnyxProps>({
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};
5 changes: 4 additions & 1 deletion src/components/LHNOptionsList/OptionRowLHNData.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -32,6 +33,8 @@ function OptionRowLHNData({
...propsToForward
}: OptionRowLHNDataProps) {
const reportID = propsToForward.reportID;
const currentReportIDValue = useCurrentReportID();
const isReportFocused = isFocused && currentReportIDValue?.currentReportID === reportID;

const optionItemRef = useRef<OptionData>();

Expand Down Expand Up @@ -85,7 +88,7 @@ function OptionRowLHNData({
<OptionRowLHN
// eslint-disable-next-line react/jsx-props-no-spreading
{...propsToForward}
isFocused={isFocused}
isFocused={isReportFocused}
optionItem={optionItem}
/>
);
Expand Down
3 changes: 1 addition & 2 deletions src/components/LHNOptionsList/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -64,7 +63,7 @@ type CustomLHNOptionsListProps = {
reportIDsWithErrors: Record<string, OnyxCommon.Errors>;
};

type LHNOptionsListProps = CustomLHNOptionsListProps & CurrentReportIDContextValue & LHNOptionsListOnyxProps;
type LHNOptionsListProps = CustomLHNOptionsListProps & LHNOptionsListOnyxProps;

type OptionRowLHNDataProps = {
/** Whether row should be focused */
Expand Down
3 changes: 0 additions & 3 deletions src/components/MoneyReportHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<typeof CONST.IOU.PAYMENT_TYPE>;
Expand All @@ -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();
Expand Down Expand Up @@ -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
Expand Down
3 changes: 0 additions & 3 deletions src/components/MoneyRequestHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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)}
/>
Expand Down
81 changes: 42 additions & 39 deletions src/libs/Pusher/pusher.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -228,48 +229,50 @@ function subscribe<EventName extends PusherEventName>(
onResubscribe = () => {},
): Promise<void> {
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();
}
});
});
}

Expand Down
Loading
Loading