Reports page and TransactionItemRow performance improvements[part 1]#65421
Conversation
Kicu
left a comment
There was a problem hiding this comment.
Very good changes 👍 I left a bunch of comments but none are super important.
Good job!
src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx
Outdated
Show resolved
Hide resolved
|
|
||
| const turnOnMobileSelectionMode = () => { | ||
| Onyx.merge(ONYXKEYS.MOBILE_SELECTION_MODE, {isEnabled: true}); | ||
| Onyx.merge(ONYXKEYS.MOBILE_SELECTION_MODE, true); |
| }, | ||
| [], | ||
| ); | ||
|
|
There was a problem hiding this comment.
amazing 👏
Can you tell me in short what part of code is responsible for disabling selection mode now in these cases?
There was a problem hiding this comment.
- For the first
useEffect:
this useEffect and this one from Search/index are doing the same thing, so these effects were redundant. - For the second one: now when everything uses
useMobileSelctionModewe don't need it to clear it after leaving because it is cleared on entering a screen that usesuseMobileSelctionMode.
| const selectedTransactionsKeys = Object.keys(selectedTransactions ?? {}); | ||
|
|
||
| if (shouldUseNarrowLayout && selectionMode?.isEnabled && headerButtonsOptions.length > 0) { | ||
| if (shouldUseNarrowLayout && !!selectionMode && headerButtonsOptions.length > 0) { |
There was a problem hiding this comment.
same case - selection mode is used via useOnyx and not via useSelectionMode. Why?
| }, | ||
| }, | ||
| }); | ||
| [], |
There was a problem hiding this comment.
the change with useAnimatedScrollHandler is that you are now passing [] as a dependency list, right?
Does that mean we will do less re-renders now because in the past we'd do more?
There was a problem hiding this comment.
Previously useAnimatedScrollHandler would recreate in every rerender causing additional overhead. Passing empty dependency array stops that.
suneox
left a comment
There was a problem hiding this comment.
Overall, the change set looks good. There are just a few points that need clarification and improvement. I’m currently stuck with the iOS build but will resolve it soon to complete the checklist.
| type SearchFiltersBarProps = { | ||
| queryJSON: SearchQueryJSON; | ||
| headerButtonsOptions: Array<DropdownOption<SearchHeaderOptionValue>>; | ||
| isMobileSelectionModeEnabled: boolean; |
There was a problem hiding this comment.
NAB: I'm not sure if we need to introduce this prop, since all the places using this component already pass the value from the useMobileSelectionMode hook
There was a problem hiding this comment.
Each useMobileSelectionMode is adding additional overhead with useOnyx, useRef and the useEfffect which it calls.
Since the performance is our number one concern and it's so easy to pass isMobileSelectionModeEnabled as it is in the SearchFiltersBar parent I think we should keep it that way.
| onSearchRouterFocus?: () => void; | ||
| headerButtonsOptions: Array<DropdownOption<SearchHeaderOptionValue>>; | ||
| handleSearch: (value: string) => void; | ||
| isMobileSelectionModeEnabled: boolean; |
There was a problem hiding this comment.
NAB: same above all the places using this component already pass the value from the useMobileSelectionMode hook so i think we don't need to introduce this props
There was a problem hiding this comment.
The same answear as above ☝️
| handleActionButtonPress(currentSearchHash, transactionItem, () => onSelectRow(item), shouldUseNarrowLayout && !!canSelectMultiple); | ||
| }} | ||
| onButtonPress={handleActionButtonPress} | ||
| onCheckboxPress={() => onCheckboxPress?.(item)} |
There was a problem hiding this comment.
If this component has a re-render issue, I think we should wrap all calback functions with useCallback, rather than just wrapping onButtonPress
Reviewer Checklist
Screenshots/VideosAndroid: HybridAppScreen.Recording.2025-07-09.at.16.39.55.mp4Android: mWeb ChromeScreen.Recording.2025-07-09.at.16.36.02.mp4iOS: HybridAppScreen.Recording.2025-07-09.at.16.27.48.mp4iOS: mWeb SafariScreen.Recording.2025-07-09.at.16.32.49.mp4MacOS: Chrome / SafariScreen.Recording.2025-07-09.at.16.18.51.mp4MacOS: DesktopScreen.Recording.2025-07-09.at.16.30.48.mp4 |
suneox
left a comment
There was a problem hiding this comment.
The current change doesn't break existing functionality, so i think we can proceed with this PR. Based on my test data, I've only seen a slight improvement. Looking forward to the next investigation around flattening the view hierarchy it may bring even more improvements!
mountiny
left a comment
There was a problem hiding this comment.
Lets get this one out, thanks!
| [ONYXKEYS.REVIEW_DUPLICATES]: OnyxTypes.ReviewDuplicates; | ||
| [ONYXKEYS.ADD_NEW_COMPANY_CARD]: OnyxTypes.AddNewCompanyCardFeed; | ||
| [ONYXKEYS.ASSIGN_CARD]: OnyxTypes.AssignCard; | ||
| [ONYXKEYS.MOBILE_SELECTION_MODE]: OnyxTypes.MobileSelectionMode; |
There was a problem hiding this comment.
Did you remove this type as its not used anymore?
| const baseKey = isChat | ||
| ? `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${(item as ReportActionListItemType).reportActionID}` | ||
| : `${ONYXKEYS.COLLECTION.TRANSACTION}${(item as TransactionListItemType).transactionID}`; | ||
| // Check if the base key matches the newSearchResultKey (TransactionListItemType) |
There was a problem hiding this comment.
| // Check if the base key matches the newSearchResultKey (TransactionListItemType) | |
| // Check if the base key matches the newSearchResultKey (TransactionListItemType) |
| : `${ONYXKEYS.COLLECTION.TRANSACTION}${(item as TransactionListItemType).transactionID}`; | ||
| // Check if the base key matches the newSearchResultKey (TransactionListItemType) | ||
| const isBaseKeyMatch = baseKey === newSearchResultKey; | ||
| // Check if any transaction within the transactions array (TransactionGroupListItemType) matches the newSearchResultKey |
There was a problem hiding this comment.
| // Check if any transaction within the transactions array (TransactionGroupListItemType) matches the newSearchResultKey | |
| // Check if any transaction within the transactions array (TransactionGroupListItemType) matches the newSearchResultKey |
| const transactionKey = `${ONYXKEYS.COLLECTION.TRANSACTION}${transaction.transactionID}`; | ||
| return transactionKey === newSearchResultKey; | ||
| }); | ||
| // Determine if either the base key or any transaction key matches |
There was a problem hiding this comment.
| // Determine if either the base key or any transaction key matches | |
| // Determine if either the base key or any transaction key matches |
|
✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release. |
|
🚀 Deployed to staging by https://github.com/mountiny in version: 9.1.79-0 🚀
|
|
🚀 Deployed to production by https://github.com/Julesssss in version: 9.1.79-11 🚀
|
| } else if (selectedItemsLength === 0 && selectionMode?.isEnabled && !wasSelectionOnRef.current) { | ||
| turnOffMobileSelectionMode(); | ||
| } |
There was a problem hiding this comment.
Coming from the #66027 checklist: we’ve removed this logic, so in case the user navigates back using the Android back button, we need to call turnOffMobileSelectionMode
| }); | ||
| } | ||
| if (isEmptyObject(newTransactionList)) { | ||
| return; |
There was a problem hiding this comment.
This early return causes an issue where the selected transactions are not cleared when we previously have selected transactions, but then all selected items are removed from the list.
Explanation of Change
This PR refactors Search related components SearchContext, SearchList, SearchPage etc. to stop redundant rerenders that cascade all the way down to search list items.
Key points of the refactor:
{isEnabled: boolean}which was causing redundant value updates in Onyx when setting this onyx key to the same value(cause of new object creation)useMobileSelectionModehook to not runturnOffMobileSelectionModewhen selectionMode is already off, this saves rerenders all over the app as this hook is widely used.SearchPage: I refactoredlastNonEmptySearchResultsto be stored inuseRefinstead ofuseStateto avoid extra rerender every time new search data comes from the BE.SearchContext:setCurrentSearchHashfunction to not makeSearchContextrerender(which rerenders everything) when we set hash to the same hash.clearSelectedTransactionsfunction to not makeSearchContextrerender, when selected transactions are already cleared.Search/index: stoped runningsetSelectedTransactionsin case, when there are no selectedTransactions.Fixed Issues
$ #63672
PROPOSAL:
Tests
Offline tests
QA Steps
// TODO: These must be filled out, or the issue title must include "[No QA]."
PR Author Checklist
### Fixed Issuessection aboveTestssectionOffline stepssectionQA stepssectioncanBeMissingparam foruseOnyxtoggleReportand notonIconClick)src/languages/*files and using the translation methodSTYLE.md) were followedAvatar, I verified the components usingAvatarare working as expected)StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))Avataris modified, I verified thatAvataris working as expected in all cases)Designlabel and/or tagged@Expensify/designso the design team can review the changes.ScrollViewcomponent to make it scrollable when more elements are added to the page.mainbranch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTeststeps.Screenshots/Videos
Android: Native
Screen.Recording.2025-07-07.at.12.09.45.mov
Android: mWeb Chrome
Screen.Recording.2025-07-07.at.12.14.54.mov
iOS: Native
Screen.Recording.2025-07-07.at.10.32.47.mov
iOS: mWeb Safari
Screen.Recording.2025-07-07.at.12.01.02.mov
MacOS: Chrome / Safari
Screen.Recording.2025-07-07.at.12.19.43.mov
MacOS: Desktop