Skip to content

Commit bb4a236

Browse files
authored
Merge pull request Expensify#64940 from callstack-internal/fix/reduce-state-updates-in-onlayout
Reduce re-renders of MoneyRequestReportPreview
2 parents 905ffe5 + 6c86f0c commit bb4a236

3 files changed

Lines changed: 55 additions & 16 deletions

File tree

src/components/ReportActionItem/MoneyRequestReportPreview/MoneyRequestReportPreviewContent.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -596,7 +596,10 @@ function MoneyRequestReportPreviewContent({
596596
}, [carouselTransactions.length]);
597597

598598
return (
599-
<View onLayout={onWrapperLayout}>
599+
<View
600+
onLayout={onWrapperLayout}
601+
testID="MoneyRequestReportPreviewContent-wrapper"
602+
>
600603
<OfflineWithFeedback
601604
pendingAction={iouReport?.pendingFields?.preview}
602605
shouldDisableOpacity={!!(action.pendingAction ?? action.isOptimisticAction)}

src/components/ReportActionItem/MoneyRequestReportPreview/index.tsx

Lines changed: 39 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, {useCallback, useMemo, useState} from 'react';
1+
import React, {useCallback, useMemo, useRef, useState} from 'react';
22
import type {LayoutChangeEvent, ListRenderItem} from 'react-native';
33
import {usePersonalDetails} from '@components/OnyxListItemProvider';
44
import TransactionPreview from '@components/ReportActionItem/TransactionPreview';
@@ -52,11 +52,42 @@ function MoneyRequestReportPreview({
5252
const lastTransactionViolations = useTransactionViolations(lastTransaction?.transactionID);
5353
const isTrackExpenseAction = isTrackExpenseActionReportActionsUtils(action);
5454
const isSplitBillAction = isSplitBillActionReportActionsUtils(action);
55-
const [currentWidth, setCurrentWidth] = useState<number>(0);
56-
const [currentWrapperWidth, setCurrentWrapperWidth] = useState<number>(0);
55+
56+
const widthsRef = useRef<{currentWidth: number | null; currentWrapperWidth: number | null}>({currentWidth: null, currentWrapperWidth: null});
57+
const [widths, setWidths] = useState({currentWidth: 0, currentWrapperWidth: 0});
58+
59+
const updateWidths = useCallback(() => {
60+
const {currentWidth, currentWrapperWidth} = widthsRef.current;
61+
62+
if (currentWidth && currentWrapperWidth) {
63+
setWidths({currentWidth, currentWrapperWidth});
64+
}
65+
}, []);
66+
67+
const onCarouselLayout = useCallback(
68+
(e: LayoutChangeEvent) => {
69+
const newWidth = e.nativeEvent.layout.width;
70+
if (widthsRef.current.currentWidth !== newWidth) {
71+
widthsRef.current.currentWidth = newWidth;
72+
updateWidths();
73+
}
74+
},
75+
[updateWidths],
76+
);
77+
const onWrapperLayout = useCallback(
78+
(e: LayoutChangeEvent) => {
79+
const newWrapperWidth = e.nativeEvent.layout.width;
80+
if (widthsRef.current.currentWrapperWidth !== newWrapperWidth) {
81+
widthsRef.current.currentWrapperWidth = newWrapperWidth;
82+
updateWidths();
83+
}
84+
},
85+
[updateWidths],
86+
);
87+
5788
const reportPreviewStyles = useMemo(
58-
() => StyleUtils.getMoneyRequestReportPreviewStyle(shouldUseNarrowLayout, transactions.length, currentWidth, currentWrapperWidth),
59-
[StyleUtils, currentWidth, currentWrapperWidth, shouldUseNarrowLayout, transactions.length],
89+
() => StyleUtils.getMoneyRequestReportPreviewStyle(shouldUseNarrowLayout, transactions.length, widths.currentWidth, widths.currentWrapperWidth),
90+
[StyleUtils, widths, shouldUseNarrowLayout, transactions.length],
6091
);
6192
const shouldShowPayerAndReceiver = useMemo(() => {
6293
if (!isIOUReport(iouReport) && action.childType !== CONST.REPORT.TYPE.IOU) {
@@ -121,13 +152,9 @@ function MoneyRequestReportPreview({
121152
invoiceReceiverPolicy={invoiceReceiverPolicy}
122153
lastTransactionViolations={lastTransactionViolations}
123154
renderTransactionItem={renderItem}
124-
onCarouselLayout={(e: LayoutChangeEvent) => {
125-
setCurrentWidth(e.nativeEvent.layout.width);
126-
}}
127-
onWrapperLayout={(e: LayoutChangeEvent) => {
128-
setCurrentWrapperWidth(e.nativeEvent.layout.width);
129-
}}
130-
currentWidth={currentWidth}
155+
onCarouselLayout={onCarouselLayout}
156+
onWrapperLayout={onWrapperLayout}
157+
currentWidth={widths.currentWidth}
131158
reportPreviewStyles={reportPreviewStyles}
132159
shouldDisplayContextMenu={shouldDisplayContextMenu}
133160
isInvoice={isInvoice}

tests/ui/MoneyRequestReportPreview.test.tsx

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import {PortalProvider} from '@gorhom/portal';
22
import * as NativeNavigation from '@react-navigation/native';
33
import {fireEvent, render, screen} from '@testing-library/react-native';
4-
import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
4+
import type {OnyxCollection, OnyxEntry, OnyxMergeInput} from 'react-native-onyx';
55
import Onyx from 'react-native-onyx';
66
import ComposeProviders from '@components/ComposeProviders';
77
import {LocaleContextProvider} from '@components/LocaleContextProvider';
@@ -10,7 +10,6 @@ import OptionsListContextProvider from '@components/OptionListContextProvider';
1010
import MoneyRequestReportPreview from '@components/ReportActionItem/MoneyRequestReportPreview';
1111
import type {MoneyRequestReportPreviewProps} from '@components/ReportActionItem/MoneyRequestReportPreview/types';
1212
import ScreenWrapper from '@components/ScreenWrapper';
13-
import TransactionPreviewSkeletonView from '@components/TransactionPreviewSkeletonView';
1413
import {convertToDisplayString} from '@libs/CurrencyUtils';
1514
import DateUtils from '@libs/DateUtils';
1615
import {translateLocal} from '@libs/Localize';
@@ -100,6 +99,9 @@ const getTransactionDisplayAmountAndHeaderText = (transaction: Transaction) => {
10099
};
101100

102101
const setCurrentWidth = () => {
102+
fireEvent(screen.getByTestId('MoneyRequestReportPreviewContent-wrapper'), 'layout', {
103+
nativeEvent: {layout: {width: 600}},
104+
});
103105
fireEvent(screen.getByTestId('carouselWidthSetter'), 'layout', {
104106
nativeEvent: {layout: {width: 500}},
105107
});
@@ -147,6 +149,7 @@ describe('MoneyRequestReportPreview', () => {
147149
await waitForBatchedUpdatesWithAct();
148150
setCurrentWidth();
149151
await Onyx.mergeCollection(ONYXKEYS.COLLECTION.TRANSACTION, mockOnyxTransactions).then(waitForBatchedUpdates);
152+
await waitForBatchedUpdatesWithAct();
150153
const {reportName: moneyRequestReportPreviewName = ''} = mockChatReport;
151154
for (const transaction of arrayOfTransactions) {
152155
const {transactionDisplayAmount, transactionHeaderText} = getTransactionDisplayAmountAndHeaderText(transaction);
@@ -163,13 +166,19 @@ describe('MoneyRequestReportPreview', () => {
163166
await waitForBatchedUpdatesWithAct();
164167
setCurrentWidth();
165168
await Onyx.multiSet({...mockOnyxTransactions, ...mockOnyxViolations});
169+
await waitForBatchedUpdatesWithAct();
166170
expect(screen.getAllByText(translateLocal('violations.reviewRequired'))).toHaveLength(2);
167171
});
168172

169173
it('renders a skeleton if the transaction is empty', async () => {
170174
renderPage({});
171175
await waitForBatchedUpdatesWithAct();
172176
setCurrentWidth();
173-
expect(screen.getAllByTestId(TransactionPreviewSkeletonView.displayName)).toHaveLength(2);
177+
178+
await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${mockTransaction.transactionID}`, {} as OnyxMergeInput<`transactions_${string}`>);
179+
await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${mockSecondTransactionID}`, {} as OnyxMergeInput<`transactions_${string}`>);
180+
await waitForBatchedUpdatesWithAct();
181+
182+
expect(screen.getAllByTestId('TransactionPreviewSkeletonView')).toHaveLength(2);
174183
});
175184
});

0 commit comments

Comments
 (0)