From 9ad08725c1a525a845923076e6d916649cfbc20e Mon Sep 17 00:00:00 2001 From: Tomasz Misiukiewicz Date: Fri, 15 Dec 2023 09:46:43 +0100 Subject: [PATCH 1/7] optimize search in BaseOptionSelector and implement in money request --- src/CONST.ts | 1 + src/components/OptionRow.js | 2 +- .../OptionsSelector/BaseOptionsSelector.js | 12 ++- .../optionsSelectorPropTypes.js | 3 - src/libs/actions/Report.ts | 5 +- .../MoneyRequestParticipantsSelector.js | 91 +++++++++++-------- 6 files changed, 61 insertions(+), 53 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 2733da56e597..04a2152b19ce 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -704,6 +704,7 @@ const CONST = { TRIE_INITIALIZATION: 'trie_initialization', COMMENT_LENGTH_DEBOUNCE_TIME: 500, SEARCH_FOR_REPORTS_DEBOUNCE_TIME: 300, + SEARCH_OPTION_LIST_DEBOUNCE_TIME: 200, }, PRIORITY_MODE: { GSD: 'gsd', diff --git a/src/components/OptionRow.js b/src/components/OptionRow.js index 1a8c395ddd8b..b6628d7444ab 100644 --- a/src/components/OptionRow.js +++ b/src/components/OptionRow.js @@ -323,7 +323,7 @@ export default React.memo( prevProps.showSelectedState === nextProps.showSelectedState && prevProps.highlightSelected === nextProps.highlightSelected && prevProps.showTitleTooltip === nextProps.showTitleTooltip && - !_.isEqual(prevProps.option.icons, nextProps.option.icons) && + _.isEqual(prevProps.option.icons, nextProps.option.icons) && prevProps.optionIsFocused === nextProps.optionIsFocused && prevProps.option.text === nextProps.option.text && prevProps.option.alternateText === nextProps.option.alternateText && diff --git a/src/components/OptionsSelector/BaseOptionsSelector.js b/src/components/OptionsSelector/BaseOptionsSelector.js index 30e069d60aab..4b101df45380 100755 --- a/src/components/OptionsSelector/BaseOptionsSelector.js +++ b/src/components/OptionsSelector/BaseOptionsSelector.js @@ -80,6 +80,7 @@ class BaseOptionsSelector extends Component { this.incrementPage = this.incrementPage.bind(this); this.sliceSections = this.sliceSections.bind(this); this.calculateAllVisibleOptionsCount = this.calculateAllVisibleOptionsCount.bind(this); + this.debouncedUpdateSearchValue = _.debounce(this.updateSearchValue, CONST.TIMING.SEARCH_OPTION_LIST_DEBOUNCE_TIME); this.relatedTarget = null; const allOptions = this.flattenSections(); @@ -94,6 +95,7 @@ class BaseOptionsSelector extends Component { shouldShowReferralModal: false, errorMessage: '', paginationPage: 1, + value: '', }; } @@ -161,7 +163,7 @@ class BaseOptionsSelector extends Component { }, () => { // If we just toggled an option on a multi-selection page or cleared the search input, scroll to top - if (this.props.selectedOptions.length !== prevProps.selectedOptions.length || (!!prevProps.value && !this.props.value)) { + if (this.props.selectedOptions.length !== prevProps.selectedOptions.length || (!!prevProps.value && !this.state.value)) { this.scrollToIndex(0); return; } @@ -247,6 +249,7 @@ class BaseOptionsSelector extends Component { this.setState({ paginationPage: 1, errorMessage: value.length > this.props.maxLength ? this.props.translate('common.error.characterLimitExceedCounter', {length: value.length, limit: this.props.maxLength}) : '', + value, }); this.props.onChangeText(value); @@ -415,7 +418,7 @@ class BaseOptionsSelector extends Component { this.relatedTarget = null; } if (this.textInput.isFocused()) { - setSelection(this.textInput, 0, this.props.value.length); + setSelection(this.textInput, 0, this.state.value.length); } } const selectedOption = this.props.onSelectRow(option); @@ -440,7 +443,7 @@ class BaseOptionsSelector extends Component { if (this.props.shouldShowTextInput && this.props.shouldPreventDefaultFocusOnSelectRow) { this.textInput.focus(); if (this.textInput.isFocused()) { - setSelection(this.textInput, 0, this.props.value.length); + setSelection(this.textInput, 0, this.state.value.length); } } this.props.onAddToSelection(option); @@ -468,11 +471,10 @@ class BaseOptionsSelector extends Component { const textInput = ( (this.textInput = el)} - value={this.props.value} label={this.props.textInputLabel} accessibilityLabel={this.props.textInputLabel} role={CONST.ROLE.PRESENTATION} - onChangeText={this.updateSearchValue} + onChangeText={this.debouncedUpdateSearchValue} errorText={this.state.errorMessage} onSubmitEditing={this.selectFocusedOption} placeholder={this.props.placeholderText} diff --git a/src/components/OptionsSelector/optionsSelectorPropTypes.js b/src/components/OptionsSelector/optionsSelectorPropTypes.js index 8593569dfafd..e52187fa76d7 100644 --- a/src/components/OptionsSelector/optionsSelectorPropTypes.js +++ b/src/components/OptionsSelector/optionsSelectorPropTypes.js @@ -27,9 +27,6 @@ const propTypes = { }), ).isRequired, - /** Value in the search input field */ - value: PropTypes.string.isRequired, - /** Callback fired when text changes */ onChangeText: PropTypes.func, diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 9977693896ee..1c320c172992 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -1,7 +1,6 @@ import {format as timezoneFormat, utcToZonedTime} from 'date-fns-tz'; import ExpensiMark from 'expensify-common/lib/ExpensiMark'; import Str from 'expensify-common/lib/str'; -import lodashDebounce from 'lodash/debounce'; import isEmpty from 'lodash/isEmpty'; import {DeviceEventEmitter, InteractionManager} from 'react-native'; import Onyx, {OnyxCollection, OnyxEntry, OnyxUpdate} from 'react-native-onyx'; @@ -2495,8 +2494,6 @@ function searchForReports(searchInput: string) { API.read('SearchForReports', parameters, {successData, failureData}); } -const debouncedSearchInServer = lodashDebounce(searchForReports, CONST.TIMING.SEARCH_FOR_REPORTS_DEBOUNCE_TIME, {leading: false}); - function searchInServer(searchInput: string) { if (isNetworkOffline || !searchInput.trim().length) { Onyx.set(ONYXKEYS.IS_SEARCHING_FOR_REPORTS, false); @@ -2507,7 +2504,7 @@ function searchInServer(searchInput: string) { // we want to show the loading state right away. Otherwise, we will see a flashing UI where the client options are sorted and // tell the user there are no options, then we start searching, and tell them there are no options again. Onyx.set(ONYXKEYS.IS_SEARCHING_FOR_REPORTS, true); - debouncedSearchInServer(searchInput); + searchForReports(searchInput); } function clearNewRoomFormError() { diff --git a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js index d8d644479270..1fd2e4eb8d69 100755 --- a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js +++ b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js @@ -6,6 +6,7 @@ import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import Button from '@components/Button'; import FormHelpMessage from '@components/FormHelpMessage'; +import {usePersonalDetails} from '@components/OnyxProvider'; import OptionsSelector from '@components/OptionsSelector'; import refPropTypes from '@components/refPropTypes'; import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; @@ -16,7 +17,6 @@ import * as Browser from '@libs/Browser'; import compose from '@libs/compose'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as ReportUtils from '@libs/ReportUtils'; -import personalDetailsPropType from '@pages/personalDetailsPropType'; import reportPropTypes from '@pages/reportPropTypes'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -48,9 +48,6 @@ const propTypes = { }), ), - /** All of the personal details for everyone */ - personalDetails: PropTypes.objectOf(personalDetailsPropType), - /** All reports shared with the user */ reports: PropTypes.objectOf(reportPropTypes), @@ -73,7 +70,6 @@ const defaultProps = { participants: [], forwardedRef: undefined, safeAreaPaddingBottomStyle: {}, - personalDetails: {}, reports: {}, betas: [], isDistanceRequest: false, @@ -84,7 +80,6 @@ function MoneyRequestParticipantsSelector({ forwardedRef, betas, participants, - personalDetails, reports, translate, navigateToRequest, @@ -103,7 +98,7 @@ function MoneyRequestParticipantsSelector({ userToInvite: null, }); const {isOffline} = useNetwork(); - + const personalDetails = usePersonalDetails(); const maxParticipantsReached = participants.length === CONST.REPORT.MAXIMUM_PARTICIPANTS; /** @@ -160,20 +155,32 @@ function MoneyRequestParticipantsSelector({ } return newSections; - }, [maxParticipantsReached, newChatOptions, participants, personalDetails, translate, searchTerm]); + }, [maxParticipantsReached, newChatOptions.personalDetails, newChatOptions.recentReports, newChatOptions.userToInvite, participants, personalDetails, searchTerm, translate]); /** * Adds a single participant to the request * * @param {Object} option */ - const addSingleParticipant = (option) => { - onAddParticipants( - [{accountID: option.accountID, login: option.login, isPolicyExpenseChat: option.isPolicyExpenseChat, reportID: option.reportID, selected: true, searchText: option.searchText}], - false, - ); - navigateToRequest(); - }; + const addSingleParticipant = useCallback( + (option) => { + onAddParticipants( + [ + { + accountID: option.accountID, + login: option.login, + isPolicyExpenseChat: option.isPolicyExpenseChat, + reportID: option.reportID, + selected: true, + searchText: option.searchText, + }, + ], + false, + ); + navigateToRequest(); + }, + [onAddParticipants, navigateToRequest], + ); /** * Removes a selected option from list if already selected. If not already selected add this option to the list. @@ -215,12 +222,16 @@ function MoneyRequestParticipantsSelector({ [participants, onAddParticipants], ); - const headerMessage = OptionsListUtils.getHeaderMessage( - newChatOptions.personalDetails.length + newChatOptions.recentReports.length !== 0, - Boolean(newChatOptions.userToInvite), - searchTerm.trim(), - maxParticipantsReached, - _.some(participants, (participant) => participant.searchText.toLowerCase().includes(searchTerm.trim().toLowerCase())), + const headerMessage = useMemo( + () => + OptionsListUtils.getHeaderMessage( + newChatOptions.personalDetails.length + newChatOptions.recentReports.length !== 0, + Boolean(newChatOptions.userToInvite), + searchTerm.trim(), + maxParticipantsReached, + _.some(participants, (participant) => participant.searchText.toLowerCase().includes(searchTerm.trim().toLowerCase())), + ), + [maxParticipantsReached, newChatOptions.personalDetails.length, newChatOptions.recentReports.length, newChatOptions.userToInvite, participants, searchTerm], ); const isOptionsDataReady = ReportUtils.isReportDataReady() && OptionsListUtils.isPersonalDetailsReady(personalDetails); @@ -278,23 +289,26 @@ function MoneyRequestParticipantsSelector({ navigateToSplit(); }, [shouldShowSplitBillErrorMessage, navigateToSplit]); - const footerContent = ( - - {shouldShowSplitBillErrorMessage && ( - ( + + {shouldShowSplitBillErrorMessage && ( + + )} +