From 4023821b3f47345840b0bda1a4ad99c7b939793c Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Wed, 20 Sep 2023 10:41:27 +0800 Subject: [PATCH 01/29] Only search when online --- src/ONYXKEYS.ts | 3 + src/components/OptionsListSkeletonView.js | 33 +---- .../OptionsSelector/BaseOptionsSelector.js | 127 ++++++++++-------- src/libs/actions/Report.js | 30 +++++ src/pages/SearchPage.js | 27 ++++ 5 files changed, 137 insertions(+), 83 deletions(-) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index af060ea58901..0274d523094e 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -27,6 +27,9 @@ const ONYXKEYS = { /** Boolean flag set whenever the sidebar has loaded */ IS_SIDEBAR_LOADED: 'isSidebarLoaded', + /** Boolean flag set whenever we are searching for reports in the server */ + IS_SEARCHING_FOR_REPORTS: 'isSearchingForReports', + /** Note: These are Persisted Requests - not all requests in the main queue as the key name might lead one to believe */ PERSISTED_REQUESTS: 'networkRequestQueue', diff --git a/src/components/OptionsListSkeletonView.js b/src/components/OptionsListSkeletonView.js index 15c66affe84d..833cbc0f372e 100644 --- a/src/components/OptionsListSkeletonView.js +++ b/src/components/OptionsListSkeletonView.js @@ -1,11 +1,9 @@ import React from 'react'; import {View} from 'react-native'; import PropTypes from 'prop-types'; -import {Rect, Circle} from 'react-native-svg'; -import SkeletonViewContentLoader from 'react-content-loader/native'; import CONST from '../CONST'; -import themeColors from '../styles/themes/default'; import styles from '../styles/styles'; +import OptionsListSkeletonRow from './OptionsListSkeletonRow'; const propTypes = { /** Whether to animate the skeleton view */ @@ -56,32 +54,11 @@ class OptionsListSkeletonView extends React.Component { lineWidth = '25%'; } skeletonViewItems.push( - - - - - , + shouldAnimate={this.props.shouldAnimate} + lineWidth={lineWidth} + />, ); } diff --git a/src/components/OptionsSelector/BaseOptionsSelector.js b/src/components/OptionsSelector/BaseOptionsSelector.js index bff9f8b6d7d0..0a370cd7b2d5 100755 --- a/src/components/OptionsSelector/BaseOptionsSelector.js +++ b/src/components/OptionsSelector/BaseOptionsSelector.js @@ -17,6 +17,9 @@ import {propTypes as optionsSelectorPropTypes, defaultProps as optionsSelectorDe import setSelection from '../../libs/setSelection'; import compose from '../../libs/compose'; import getPlatform from '../../libs/getPlatform'; +import OptionsListSkeletonRow from '../OptionsListSkeletonRow'; +import Text from '../Text'; +import FormHelpMessage from '../FormHelpMessage'; const propTypes = { /** padding bottom style of safe area */ @@ -344,63 +347,77 @@ class BaseOptionsSelector extends Component { const shouldShowDefaultConfirmButton = !this.props.footerContent && defaultConfirmButtonText; const safeAreaPaddingBottomStyle = shouldShowFooter ? undefined : this.props.safeAreaPaddingBottomStyle; const textInput = ( - (this.textInput = el)} - value={this.props.value} - label={this.props.textInputLabel} - accessibilityLabel={this.props.textInputLabel} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} - onChangeText={this.props.onChangeText} - onSubmitEditing={this.selectFocusedOption} - placeholder={this.props.placeholderText} - maxLength={this.props.maxLength} - keyboardType={this.props.keyboardType} - onBlur={(e) => { - if (!this.props.shouldFocusOnSelectRow) { - return; - } - this.relatedTarget = e.relatedTarget; - }} - selectTextOnFocus - blurOnSubmit={Boolean(this.state.allOptions.length)} - spellCheck={false} - /> + <> + (this.textInput = el)} + value={this.props.value} + label={this.props.textInputLabel} + accessibilityLabel={this.props.textInputLabel} + accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} + onChangeText={this.props.onChangeText} + onSubmitEditing={this.selectFocusedOption} + placeholder={this.props.placeholderText} + maxLength={this.props.maxLength} + keyboardType={this.props.keyboardType} + onBlur={(e) => { + if (!this.props.shouldFocusOnSelectRow) { + return; + } + this.relatedTarget = e.relatedTarget; + }} + selectTextOnFocus + blurOnSubmit={Boolean(this.state.allOptions.length)} + spellCheck={false} + /> + {this.props.textInputAlert && ( + + )} + + + ); const optionsList = ( - (this.list = el)} - optionHoveredStyle={this.props.optionHoveredStyle} - onSelectRow={this.props.onSelectRow ? this.selectRow : undefined} - sections={this.props.sections} - focusedIndex={this.state.focusedIndex} - selectedOptions={this.props.selectedOptions} - canSelectMultipleOptions={this.props.canSelectMultipleOptions} - shouldShowMultipleOptionSelectorAsButton={this.props.shouldShowMultipleOptionSelectorAsButton} - multipleOptionSelectorButtonText={this.props.multipleOptionSelectorButtonText} - onAddToSelection={this.props.onAddToSelection} - hideSectionHeaders={this.props.hideSectionHeaders} - headerMessage={this.props.headerMessage} - boldStyle={this.props.boldStyle} - showTitleTooltip={this.props.showTitleTooltip} - isDisabled={this.props.isDisabled} - shouldHaveOptionSeparator={this.props.shouldHaveOptionSeparator} - highlightSelectedOptions={this.props.highlightSelectedOptions} - onLayout={() => { - if (this.props.selectedOptions.length === 0) { - this.scrollToIndex(this.state.focusedIndex, false); - } - - if (this.props.onLayout) { - this.props.onLayout(); - } - }} - contentContainerStyles={[safeAreaPaddingBottomStyle, ...this.props.contentContainerStyles]} - listContainerStyles={this.props.listContainerStyles} - listStyles={this.props.listStyles} - isLoading={!this.props.shouldShowOptions} - showScrollIndicator={this.props.showScrollIndicator} - isRowMultilineSupported={this.props.isRowMultilineSupported} - /> + <> + {this.props.shouldShowLoader && } + (this.list = el)} + optionHoveredStyle={this.props.optionHoveredStyle} + onSelectRow={this.props.onSelectRow ? this.selectRow : undefined} + sections={this.props.sections} + focusedIndex={this.state.focusedIndex} + selectedOptions={this.props.selectedOptions} + canSelectMultipleOptions={this.props.canSelectMultipleOptions} + shouldShowMultipleOptionSelectorAsButton={this.props.shouldShowMultipleOptionSelectorAsButton} + multipleOptionSelectorButtonText={this.props.multipleOptionSelectorButtonText} + onAddToSelection={this.props.onAddToSelection} + hideSectionHeaders={this.props.hideSectionHeaders} + headerMessage={this.props.headerMessage} + boldStyle={this.props.boldStyle} + showTitleTooltip={this.props.showTitleTooltip} + isDisabled={this.props.isDisabled} + shouldHaveOptionSeparator={this.props.shouldHaveOptionSeparator} + highlightSelectedOptions={this.props.highlightSelectedOptions} + onLayout={() => { + if (this.props.selectedOptions.length === 0) { + this.scrollToIndex(this.state.focusedIndex, false); + } + + if (this.props.onLayout) { + this.props.onLayout(); + } + }} + contentContainerStyles={[safeAreaPaddingBottomStyle, ...this.props.contentContainerStyles]} + listContainerStyles={this.props.listContainerStyles} + listStyles={this.props.listStyles} + isLoading={!this.props.shouldShowOptions} + showScrollIndicator={this.props.showScrollIndicator} + isRowMultilineSupported={this.props.isRowMultilineSupported} + /> + ); return ( 0) { + this.throttledSearchInServer(searchValue); + } + + // When the user searches we will this.setState({searchValue}, this.debouncedUpdateOptions); } @@ -116,6 +123,17 @@ class SearchPage extends Component { return sections; } + /** + * @param {string} searchValue + */ + searchInServer(searchValue) { + if (this.props.network.isOffline) { + return; + } + + Report.searchInServer(searchValue); + } + searchRendered() { Timing.end(CONST.TIMING.SEARCH_RENDER); Performance.markEnd(CONST.TIMING.SEARCH_RENDER); @@ -184,9 +202,11 @@ class SearchPage extends Component { showTitleTooltip shouldShowOptions={didScreenTransitionEnd && isOptionsDataReady} textInputLabel={this.props.translate('optionsSelector.nameEmailOrPhoneNumber')} + textInputAlert={this.props.network.isOffline ? 'You appear to be offline. Search results are limited until you come back online.' : ''} onLayout={this.searchRendered} safeAreaPaddingBottomStyle={safeAreaPaddingBottomStyle} autoFocus + shouldShowLoader={this.props.isSearchingForReports} /> @@ -212,5 +232,12 @@ export default compose( betas: { key: ONYXKEYS.BETAS, }, + isSearchingForReports: { + key: ONYXKEYS.IS_SEARCHING_FOR_REPORTS, + initWithStoredValues: false, + }, + network: { + key: ONYXKEYS.NETWORK, + } }), )(SearchPage); From dae32f291bf7793d257cf85874b8752968b77ce7 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Wed, 20 Sep 2023 12:52:44 +0800 Subject: [PATCH 02/29] Remove Text --- src/components/OptionsSelector/BaseOptionsSelector.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/OptionsSelector/BaseOptionsSelector.js b/src/components/OptionsSelector/BaseOptionsSelector.js index 0a370cd7b2d5..022eaab1ba58 100755 --- a/src/components/OptionsSelector/BaseOptionsSelector.js +++ b/src/components/OptionsSelector/BaseOptionsSelector.js @@ -18,7 +18,6 @@ import setSelection from '../../libs/setSelection'; import compose from '../../libs/compose'; import getPlatform from '../../libs/getPlatform'; import OptionsListSkeletonRow from '../OptionsListSkeletonRow'; -import Text from '../Text'; import FormHelpMessage from '../FormHelpMessage'; const propTypes = { From c0e5658b4092bb23282ddb6169bff290d06fcbba Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Wed, 20 Sep 2023 12:53:03 +0800 Subject: [PATCH 03/29] Add missing file --- src/components/OptionsListSkeletonRow.js | 48 ++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 src/components/OptionsListSkeletonRow.js diff --git a/src/components/OptionsListSkeletonRow.js b/src/components/OptionsListSkeletonRow.js new file mode 100644 index 000000000000..9df9a610bc14 --- /dev/null +++ b/src/components/OptionsListSkeletonRow.js @@ -0,0 +1,48 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import {Rect, Circle} from 'react-native-svg'; +import SkeletonViewContentLoader from 'react-content-loader/native'; +import themeColors from '../styles/themes/default'; +import CONST from '../CONST'; +import styles from '../styles/styles'; + +const propTypes = { + /** Whether to animate the skeleton view */ + shouldAnimate: PropTypes.bool.isRequired, + + /** Line width string */ + lineWidth: PropTypes.string.isRequired, +}; + +function OptionsListSkeletonRow({lineWidth, shouldAnimate}) { + return ( + + + + + + ); +} + +OptionsListSkeletonRow.propTypes = propTypes; +export default OptionsListSkeletonRow; \ No newline at end of file From 61e4199178325c68b07ae16ce567d4340b01fc86 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Wed, 20 Sep 2023 13:01:52 +0800 Subject: [PATCH 04/29] Run prettier --- src/components/OptionsListSkeletonRow.js | 2 +- .../OptionsSelector/BaseOptionsSelector.js | 9 ++-- src/libs/actions/Report.js | 50 ++++++++++--------- src/pages/SearchPage.js | 2 +- 4 files changed, 35 insertions(+), 28 deletions(-) diff --git a/src/components/OptionsListSkeletonRow.js b/src/components/OptionsListSkeletonRow.js index 9df9a610bc14..d7968ef1ba80 100644 --- a/src/components/OptionsListSkeletonRow.js +++ b/src/components/OptionsListSkeletonRow.js @@ -45,4 +45,4 @@ function OptionsListSkeletonRow({lineWidth, shouldAnimate}) { } OptionsListSkeletonRow.propTypes = propTypes; -export default OptionsListSkeletonRow; \ No newline at end of file +export default OptionsListSkeletonRow; diff --git a/src/components/OptionsSelector/BaseOptionsSelector.js b/src/components/OptionsSelector/BaseOptionsSelector.js index 022eaab1ba58..760f86db799a 100755 --- a/src/components/OptionsSelector/BaseOptionsSelector.js +++ b/src/components/OptionsSelector/BaseOptionsSelector.js @@ -375,13 +375,16 @@ class BaseOptionsSelector extends Component { isError={false} /> )} - - ); const optionsList = ( <> - {this.props.shouldShowLoader && } + {this.props.shouldShowLoader && ( + + )} (this.list = el)} optionHoveredStyle={this.props.optionHoveredStyle} diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 51fe69acb6fc..016b5928a8f6 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -2063,29 +2063,33 @@ function clearPrivateNotesError(reportID, accountID) { * @param {string} searchInput */ function searchInServer(searchInput) { - API.read('SearchForReports', {searchInput}, { - optimisticData: [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.IS_SEARCHING_FOR_REPORTS, - value: true, - } - ], - successData: [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.IS_SEARCHING_FOR_REPORTS, - value: false, - } - ], - failureData: [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.IS_SEARCHING_FOR_REPORTS, - value: false, - } - ] - }); + API.read( + 'SearchForReports', + {searchInput}, + { + optimisticData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.IS_SEARCHING_FOR_REPORTS, + value: true, + }, + ], + successData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.IS_SEARCHING_FOR_REPORTS, + value: false, + }, + ], + failureData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.IS_SEARCHING_FOR_REPORTS, + value: false, + }, + ], + }, + ); } export { diff --git a/src/pages/SearchPage.js b/src/pages/SearchPage.js index fb95f3d24f7e..432c64d51be5 100755 --- a/src/pages/SearchPage.js +++ b/src/pages/SearchPage.js @@ -238,6 +238,6 @@ export default compose( }, network: { key: ONYXKEYS.NETWORK, - } + }, }), )(SearchPage); From 0d9a313de199df2554deb4f6ade28be73144e09f Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Thu, 21 Sep 2023 10:24:53 +0800 Subject: [PATCH 05/29] Slow down server search --- src/pages/SearchPage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/SearchPage.js b/src/pages/SearchPage.js index 432c64d51be5..9fae2fe2cd4a 100755 --- a/src/pages/SearchPage.js +++ b/src/pages/SearchPage.js @@ -59,7 +59,7 @@ class SearchPage extends Component { const {recentReports, personalDetails, userToInvite} = OptionsListUtils.getSearchOptions(props.reports, props.personalDetails, '', props.betas); - this.throttledSearchInServer = _.throttle(this.searchInServer.bind(this), 300, {leading: false}); + this.throttledSearchInServer = _.throttle(this.searchInServer.bind(this), 1000, {leading: false}); this.state = { searchValue: '', From 630a6fdf8c8f2c2bb63042d0f778e15e51cdc282 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Mon, 25 Sep 2023 14:33:55 +0800 Subject: [PATCH 06/29] Make NewChatPage also search --- src/languages/en.ts | 3 +++ src/languages/es.ts | 3 +++ src/libs/actions/Report.js | 14 +++++++++++++- src/pages/NewChatPage.js | 14 +++++++++++++- src/pages/SearchPage.js | 17 ++--------------- 5 files changed, 34 insertions(+), 17 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index f7c028d2a106..6f0258a3063e 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1554,6 +1554,9 @@ export default { screenShare: 'Screen share', screenShareRequest: 'Expensify is inviting you to a screen share', }, + search: { + offline: 'You appear to be offline; search results are limited.', + }, genericErrorPage: { title: 'Uh-oh, something went wrong!', body: { diff --git a/src/languages/es.ts b/src/languages/es.ts index a68f33a33730..1cc74b4db2aa 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1576,6 +1576,9 @@ export default { screenShare: 'Compartir pantalla', screenShareRequest: 'Expensify te está invitando a compartir la pantalla', }, + search: { + offline: 'You appear to be offline; search results are limited.', + }, genericErrorPage: { title: '¡Uh-oh, algo salió mal!', body: { diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 8d11dd1163af..14837304081a 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -2131,9 +2131,15 @@ function clearPrivateNotesError(reportID, accountID) { } /** + * @private * @param {string} searchInput */ function searchInServer(searchInput) { + // We do not try to make this request while offline because it sets a loading indicator optimistically + if (isNetworkOffline) { + return; + } + API.read( 'SearchForReports', {searchInput}, @@ -2163,8 +2169,14 @@ function searchInServer(searchInput) { ); } +/** + * @private + * @param {string} searchInput + */ +const throttledSearchInServer = _.throttle(searchInServer, 1000, {leading: false}); + export { - searchInServer, + throttledSearchInServer, addComment, addAttachment, reconnect, diff --git a/src/pages/NewChatPage.js b/src/pages/NewChatPage.js index cb54aa8e5a7b..bd206eb65346 100755 --- a/src/pages/NewChatPage.js +++ b/src/pages/NewChatPage.js @@ -20,6 +20,7 @@ import compose from '../libs/compose'; import personalDetailsPropType from './personalDetailsPropType'; import reportPropTypes from './reportPropTypes'; import variables from '../styles/variables'; +import useNetwork from '../hooks/useNetwork'; const propTypes = { /** Beta features list */ @@ -44,12 +45,13 @@ const defaultProps = { const excludedGroupEmails = _.without(CONST.EXPENSIFY_EMAILS, CONST.EMAIL.CONCIERGE); -function NewChatPage({betas, isGroupChat, personalDetails, reports, translate}) { +function NewChatPage({betas, isGroupChat, personalDetails, reports, translate, isSearchingForReports}) { const [searchTerm, setSearchTerm] = useState(''); const [filteredRecentReports, setFilteredRecentReports] = useState([]); const [filteredPersonalDetails, setFilteredPersonalDetails] = useState([]); const [filteredUserToInvite, setFilteredUserToInvite] = useState(); const [selectedOptions, setSelectedOptions] = useState([]); + const {isOffline} = useNetwork(); const maxParticipantsReached = selectedOptions.length === CONST.REPORT.MAXIMUM_PARTICIPANTS; const headerMessage = OptionsListUtils.getHeaderMessage( @@ -167,6 +169,10 @@ function NewChatPage({betas, isGroupChat, personalDetails, reports, translate}) // eslint-disable-next-line react-hooks/exhaustive-deps }, [reports, personalDetails, searchTerm]); + // When search term updates we will fetch any reports + useEffect(() => { + Report.throttledSearchInServer(searchTerm); + }, [searchTerm]); return ( 1 ? translate('newChatPage.createGroup') : translate('newChatPage.createChat')} + textInputAlert={isOffline ? translate('search.offline') : ''} onConfirmSelection={createGroup} textInputLabel={translate('optionsSelector.nameEmailOrPhoneNumber')} safeAreaPaddingBottomStyle={safeAreaPaddingBottomStyle} + shouldShowLoader={isSearchingForReports} /> @@ -229,5 +237,9 @@ export default compose( betas: { key: ONYXKEYS.BETAS, }, + isSearchingForReports: { + key: ONYXKEYS.IS_SEARCHING_FOR_REPORTS, + initWithStoredValues: false, + }, }), )(NewChatPage); diff --git a/src/pages/SearchPage.js b/src/pages/SearchPage.js index 9fae2fe2cd4a..7dbddc166f47 100755 --- a/src/pages/SearchPage.js +++ b/src/pages/SearchPage.js @@ -59,8 +59,6 @@ class SearchPage extends Component { const {recentReports, personalDetails, userToInvite} = OptionsListUtils.getSearchOptions(props.reports, props.personalDetails, '', props.betas); - this.throttledSearchInServer = _.throttle(this.searchInServer.bind(this), 1000, {leading: false}); - this.state = { searchValue: '', recentReports, @@ -78,7 +76,7 @@ class SearchPage extends Component { onChangeText(searchValue = '') { if (searchValue.length > 0) { - this.throttledSearchInServer(searchValue); + Report.throttledSearchInServer(searchValue); } // When the user searches we will @@ -123,17 +121,6 @@ class SearchPage extends Component { return sections; } - /** - * @param {string} searchValue - */ - searchInServer(searchValue) { - if (this.props.network.isOffline) { - return; - } - - Report.searchInServer(searchValue); - } - searchRendered() { Timing.end(CONST.TIMING.SEARCH_RENDER); Performance.markEnd(CONST.TIMING.SEARCH_RENDER); @@ -202,7 +189,7 @@ class SearchPage extends Component { showTitleTooltip shouldShowOptions={didScreenTransitionEnd && isOptionsDataReady} textInputLabel={this.props.translate('optionsSelector.nameEmailOrPhoneNumber')} - textInputAlert={this.props.network.isOffline ? 'You appear to be offline. Search results are limited until you come back online.' : ''} + textInputAlert={this.props.network.isOffline ? this.props.translate('search.offline') : ''} onLayout={this.searchRendered} safeAreaPaddingBottomStyle={safeAreaPaddingBottomStyle} autoFocus From a66dbd93af2d0cbcb9c7960619e0a1986306c872 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Thu, 28 Sep 2023 20:36:21 +0800 Subject: [PATCH 07/29] Update translations --- src/languages/en.ts | 2 +- src/languages/es.ts | 2 +- src/libs/actions/App.js | 3 ++- src/pages/NewChatPage.js | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 32f1315c44c0..440d96fc0160 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1562,7 +1562,7 @@ export default { screenShareRequest: 'Expensify is inviting you to a screen share', }, search: { - offline: 'You appear to be offline; search results are limited.', + resultsAreLimited: 'Search results are limited.', }, genericErrorPage: { title: 'Uh-oh, something went wrong!', diff --git a/src/languages/es.ts b/src/languages/es.ts index acf7f16d7b7c..6286a4453d53 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1584,7 +1584,7 @@ export default { screenShareRequest: 'Expensify te está invitando a compartir la pantalla', }, search: { - offline: 'You appear to be offline; search results are limited.', + resultsAreLimited: 'Los resultados de búsqueda están limitados.', }, genericErrorPage: { title: '¡Uh-oh, algo salió mal!', diff --git a/src/libs/actions/App.js b/src/libs/actions/App.js index b8be35aa1919..f2017a982fb0 100644 --- a/src/libs/actions/App.js +++ b/src/libs/actions/App.js @@ -204,7 +204,8 @@ function getOnyxDataForOpenOrReconnect(isOpenApp = false) { */ function openApp() { getPolicyParamsForOpenOrReconnect().then((policyParams) => { - API.read('OpenApp', policyParams, getOnyxDataForOpenOrReconnect(true)); + const params = {enablePriorityModeFilter: true, ...policyParams}; + API.read('OpenApp', params, getOnyxDataForOpenOrReconnect(true)); }); } diff --git a/src/pages/NewChatPage.js b/src/pages/NewChatPage.js index aa6fa8dfbbc3..30cc27845144 100755 --- a/src/pages/NewChatPage.js +++ b/src/pages/NewChatPage.js @@ -208,7 +208,7 @@ function NewChatPage({betas, isGroupChat, personalDetails, reports, translate, i shouldShowOptions={isOptionsDataReady} shouldShowConfirmButton confirmButtonText={selectedOptions.length > 1 ? translate('newChatPage.createGroup') : translate('newChatPage.createChat')} - textInputAlert={isOffline ? translate('search.offline') : ''} + textInputAlert={isOffline ? `${translate('common.youAppearToBeOffline')} ${translate('search.resultsAreLimited')}` : ''} onConfirmSelection={createGroup} textInputLabel={translate('optionsSelector.nameEmailOrPhoneNumber')} safeAreaPaddingBottomStyle={safeAreaPaddingBottomStyle} From 1bbde1ae4ac28c8daf5e7b604b67e6fee911962f Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Fri, 29 Sep 2023 17:17:33 +0800 Subject: [PATCH 08/29] Use debounce --- src/libs/actions/Report.js | 5 +++-- src/pages/NewChatPage.js | 2 +- src/pages/SearchPage.js | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 615b0ad6bfae..b05b8510b7c5 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -1,6 +1,7 @@ import {InteractionManager} from 'react-native'; import _ from 'underscore'; import lodashGet from 'lodash/get'; +import lodashDebounce from 'lodash/debounce'; import ExpensiMark from 'expensify-common/lib/ExpensiMark'; import Onyx from 'react-native-onyx'; import Str from 'expensify-common/lib/str'; @@ -2210,10 +2211,10 @@ function searchInServer(searchInput) { * @private * @param {string} searchInput */ -const throttledSearchInServer = _.throttle(searchInServer, 1000, {leading: false}); +const debouncedSearchInServer = lodashDebounce(searchInServer, 300, {leading: false}); export { - throttledSearchInServer, + debouncedSearchInServer, addComment, addAttachment, reconnect, diff --git a/src/pages/NewChatPage.js b/src/pages/NewChatPage.js index 30cc27845144..058efcc57e71 100755 --- a/src/pages/NewChatPage.js +++ b/src/pages/NewChatPage.js @@ -171,7 +171,7 @@ function NewChatPage({betas, isGroupChat, personalDetails, reports, translate, i // When search term updates we will fetch any reports useEffect(() => { - Report.throttledSearchInServer(searchTerm); + Report.debouncedSearchInServer(searchTerm); }, [searchTerm]); return ( 0) { - Report.throttledSearchInServer(searchValue); + Report.debouncedSearchInServer(searchValue); } // When the user searches we will From ad952aeae1c60a986978d8e4cafc62d9968206e2 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Fri, 29 Sep 2023 17:34:35 +0800 Subject: [PATCH 09/29] Make some requested changes --- src/components/OptionsSelector/BaseOptionsSelector.js | 2 +- src/pages/NewChatPage.js | 11 +++++++---- src/pages/SearchPage.js | 7 +++++-- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/components/OptionsSelector/BaseOptionsSelector.js b/src/components/OptionsSelector/BaseOptionsSelector.js index b183f6181009..8ab7e0fda941 100755 --- a/src/components/OptionsSelector/BaseOptionsSelector.js +++ b/src/components/OptionsSelector/BaseOptionsSelector.js @@ -368,7 +368,7 @@ class BaseOptionsSelector extends Component { blurOnSubmit={Boolean(this.state.allOptions.length)} spellCheck={false} /> - {this.props.textInputAlert && ( + {Boolean(this.props.textInputAlert) && ( { - Report.debouncedSearchInServer(searchTerm); - }, [searchTerm]); + const setSearchTermAndDebounceSearchInServer = useCallback((text) => { + if (text) { + Report.debouncedSearchInServer(text); + } + setSearchTerm(text); + }, []); return ( createChat(option)} - onChangeText={setSearchTerm} + onChangeText={setSearchTermAndDebounceSearchInServer} headerMessage={headerMessage} boldStyle shouldFocusOnSelectRow={!Browser.isMobile()} diff --git a/src/pages/SearchPage.js b/src/pages/SearchPage.js index 9a81a2f3d89e..decbb10b6d62 100755 --- a/src/pages/SearchPage.js +++ b/src/pages/SearchPage.js @@ -20,6 +20,7 @@ import compose from '../libs/compose'; import personalDetailsPropType from './personalDetailsPropType'; import reportPropTypes from './reportPropTypes'; import Performance from '../libs/Performance'; +import networkPropTypes from '../components/networkPropTypes'; const propTypes = { /* Onyx Props */ @@ -37,6 +38,8 @@ const propTypes = { ...windowDimensionsPropTypes, ...withLocalizePropTypes, + + ...networkPropTypes, }; const defaultProps = { @@ -75,7 +78,7 @@ class SearchPage extends Component { } onChangeText(searchValue = '') { - if (searchValue.length > 0) { + if (searchValue.length) { Report.debouncedSearchInServer(searchValue); } @@ -192,7 +195,7 @@ class SearchPage extends Component { showTitleTooltip shouldShowOptions={didScreenTransitionEnd && isOptionsDataReady} textInputLabel={this.props.translate('optionsSelector.nameEmailOrPhoneNumber')} - textInputAlert={this.props.network.isOffline ? this.props.translate('search.offline') : ''} + textInputAlert={this.props.network.isOffline ? `${this.props.translate('common.youAppearToBeOffline')} ${this.props.translate('search.resultsAreLimited')}` : ''} onLayout={this.searchRendered} safeAreaPaddingBottomStyle={safeAreaPaddingBottomStyle} autoFocus From 98724e9070da0843f03e561573282098a098771e Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Mon, 2 Oct 2023 10:11:26 -1000 Subject: [PATCH 10/29] Make requested changes --- src/pages/NewChatPage.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/NewChatPage.js b/src/pages/NewChatPage.js index 9a7364df96e1..ee6ce3ce18ab 100755 --- a/src/pages/NewChatPage.js +++ b/src/pages/NewChatPage.js @@ -170,8 +170,8 @@ function NewChatPage({betas, isGroupChat, personalDetails, reports, translate, i }, [reports, personalDetails, searchTerm]); // When search term updates we will fetch any reports - const setSearchTermAndDebounceSearchInServer = useCallback((text) => { - if (text) { + const setSearchTermAndDebounceSearchInServer = useCallback((text = '') => { + if (text.length) { Report.debouncedSearchInServer(text); } setSearchTerm(text); From 3ab1772ca5522ef10aa79cff5860a2d05c0594c9 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Mon, 2 Oct 2023 14:35:59 -1000 Subject: [PATCH 11/29] add missing useCallback --- src/pages/NewChatPage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/NewChatPage.js b/src/pages/NewChatPage.js index ee6ce3ce18ab..84222d1e26ac 100755 --- a/src/pages/NewChatPage.js +++ b/src/pages/NewChatPage.js @@ -1,5 +1,5 @@ import _ from 'underscore'; -import React, {useState, useEffect, useMemo} from 'react'; +import React, {useState, useEffect, useMemo, useCallback} from 'react'; import {View} from 'react-native'; import PropTypes from 'prop-types'; import {withOnyx} from 'react-native-onyx'; From 97e5bb13e862a71771c1ee2d5a21eaf32a5f474f Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Mon, 2 Oct 2023 17:45:45 -1000 Subject: [PATCH 12/29] Change UX so we are showing loading row below options instead of above --- src/components/OptionsList/BaseOptionsList.js | 14 ++++++++++++-- src/components/OptionsList/optionsListPropTypes.js | 4 ++++ .../OptionsSelector/BaseOptionsSelector.js | 8 +------- src/pages/SearchPage.js | 9 ++++++++- 4 files changed, 25 insertions(+), 10 deletions(-) diff --git a/src/components/OptionsList/BaseOptionsList.js b/src/components/OptionsList/BaseOptionsList.js index 5a40c28a86c9..25965ef5c54c 100644 --- a/src/components/OptionsList/BaseOptionsList.js +++ b/src/components/OptionsList/BaseOptionsList.js @@ -10,6 +10,7 @@ import Text from '../Text'; import {propTypes as optionsListPropTypes, defaultProps as optionsListDefaultProps} from './optionsListPropTypes'; import OptionsListSkeletonView from '../OptionsListSkeletonView'; import usePrevious from '../../hooks/usePrevious'; +import OptionsListSkeletonRow from '../OptionsListSkeletonRow'; const propTypes = { /** Determines whether the keyboard gets dismissed in response to a drag */ @@ -65,6 +66,7 @@ function BaseOptionsList({ isDisabled, innerRef, isRowMultilineSupported, + shouldShowHeaderMessage, }) { const flattenedData = useRef(); const previousSections = usePrevious(sections); @@ -189,6 +191,15 @@ function BaseOptionsList({ return option.name === item.searchText; }); + if (item.loadingRow) { + return ( + + ); + } + return ( ; }; - return ( {isLoading ? ( ) : ( <> - {headerMessage ? ( + {shouldShowHeaderMessage && headerMessage ? ( {headerMessage} diff --git a/src/components/OptionsList/optionsListPropTypes.js b/src/components/OptionsList/optionsListPropTypes.js index a2479c878041..967f30d44119 100644 --- a/src/components/OptionsList/optionsListPropTypes.js +++ b/src/components/OptionsList/optionsListPropTypes.js @@ -84,6 +84,9 @@ const propTypes = { /** Whether to wrap large text up to 2 lines */ isRowMultilineSupported: PropTypes.bool, + + /** Whether to show the header message below the input */ + shouldShowHeaderMessage: PropTypes.bool, }; const defaultProps = { @@ -109,6 +112,7 @@ const defaultProps = { shouldDisableRowInnerPadding: false, showScrollIndicator: false, isRowMultilineSupported: false, + shouldShowHeaderMessage: true, }; export {propTypes, defaultProps}; diff --git a/src/components/OptionsSelector/BaseOptionsSelector.js b/src/components/OptionsSelector/BaseOptionsSelector.js index bc545b7c0ebf..b6ee91e23454 100755 --- a/src/components/OptionsSelector/BaseOptionsSelector.js +++ b/src/components/OptionsSelector/BaseOptionsSelector.js @@ -17,7 +17,6 @@ import {propTypes as optionsSelectorPropTypes, defaultProps as optionsSelectorDe import setSelection from '../../libs/setSelection'; import compose from '../../libs/compose'; import getPlatform from '../../libs/getPlatform'; -import OptionsListSkeletonRow from '../OptionsListSkeletonRow'; import FormHelpMessage from '../FormHelpMessage'; const propTypes = { @@ -394,12 +393,6 @@ class BaseOptionsSelector extends Component { ); const optionsList = ( <> - {this.props.shouldShowLoader && ( - - )} (this.list = el)} optionHoveredStyle={this.props.optionHoveredStyle} @@ -432,6 +425,7 @@ class BaseOptionsSelector extends Component { isLoading={!this.props.shouldShowOptions} showScrollIndicator={this.props.showScrollIndicator} isRowMultilineSupported={this.props.isRowMultilineSupported} + shouldShowHeaderMessage={this.props.shouldShowHeaderMessage} /> ); diff --git a/src/pages/SearchPage.js b/src/pages/SearchPage.js index decbb10b6d62..318294517a97 100755 --- a/src/pages/SearchPage.js +++ b/src/pages/SearchPage.js @@ -121,6 +121,13 @@ class SearchPage extends Component { }); } + if (this.props.isSearchingForReports) { + sections.push({ + data: [{loadingRow: true}], + shouldShow: true, + indexOffset, + }) + } return sections; } @@ -199,7 +206,7 @@ class SearchPage extends Component { onLayout={this.searchRendered} safeAreaPaddingBottomStyle={safeAreaPaddingBottomStyle} autoFocus - shouldShowLoader={this.props.isSearchingForReports} + shouldShowHeaderMessage={!this.props.isSearchingForReports} /> From af3fec77c3ee39c185aad5a39ef2cde7141a0950 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Tue, 3 Oct 2023 10:48:03 -1000 Subject: [PATCH 13/29] Remove unneeded changes. Fix propTypes --- src/components/OptionsList/BaseOptionsList.js | 1 + .../OptionsSelector/BaseOptionsSelector.js | 68 +++++++++---------- src/pages/SearchPage.js | 3 +- 3 files changed, 36 insertions(+), 36 deletions(-) diff --git a/src/components/OptionsList/BaseOptionsList.js b/src/components/OptionsList/BaseOptionsList.js index 25965ef5c54c..2521962756a5 100644 --- a/src/components/OptionsList/BaseOptionsList.js +++ b/src/components/OptionsList/BaseOptionsList.js @@ -247,6 +247,7 @@ function BaseOptionsList({ return ; }; + return ( {isLoading ? ( diff --git a/src/components/OptionsSelector/BaseOptionsSelector.js b/src/components/OptionsSelector/BaseOptionsSelector.js index b6ee91e23454..9d2ec7d493c6 100755 --- a/src/components/OptionsSelector/BaseOptionsSelector.js +++ b/src/components/OptionsSelector/BaseOptionsSelector.js @@ -392,42 +392,40 @@ class BaseOptionsSelector extends Component { ); const optionsList = ( - <> - (this.list = el)} - optionHoveredStyle={this.props.optionHoveredStyle} - onSelectRow={this.props.onSelectRow ? this.selectRow : undefined} - sections={this.props.sections} - focusedIndex={this.state.focusedIndex} - selectedOptions={this.props.selectedOptions} - canSelectMultipleOptions={this.props.canSelectMultipleOptions} - shouldShowMultipleOptionSelectorAsButton={this.props.shouldShowMultipleOptionSelectorAsButton} - multipleOptionSelectorButtonText={this.props.multipleOptionSelectorButtonText} - onAddToSelection={this.addToSelection} - hideSectionHeaders={this.props.hideSectionHeaders} - headerMessage={this.props.headerMessage} - boldStyle={this.props.boldStyle} - showTitleTooltip={this.props.showTitleTooltip} - isDisabled={this.props.isDisabled} - shouldHaveOptionSeparator={this.props.shouldHaveOptionSeparator} - highlightSelectedOptions={this.props.highlightSelectedOptions} - onLayout={() => { - if (this.props.selectedOptions.length === 0) { - this.scrollToIndex(this.state.focusedIndex, false); + (this.list = el)} + optionHoveredStyle={this.props.optionHoveredStyle} + onSelectRow={this.props.onSelectRow ? this.selectRow : undefined} + sections={this.props.sections} + focusedIndex={this.state.focusedIndex} + selectedOptions={this.props.selectedOptions} + canSelectMultipleOptions={this.props.canSelectMultipleOptions} + shouldShowMultipleOptionSelectorAsButton={this.props.shouldShowMultipleOptionSelectorAsButton} + multipleOptionSelectorButtonText={this.props.multipleOptionSelectorButtonText} + onAddToSelection={this.addToSelection} + hideSectionHeaders={this.props.hideSectionHeaders} + headerMessage={this.props.headerMessage} + boldStyle={this.props.boldStyle} + showTitleTooltip={this.props.showTitleTooltip} + isDisabled={this.props.isDisabled} + shouldHaveOptionSeparator={this.props.shouldHaveOptionSeparator} + highlightSelectedOptions={this.props.highlightSelectedOptions} + onLayout={() => { + if (this.props.selectedOptions.length === 0) { + this.scrollToIndex(this.state.focusedIndex, false); + } + if (this.props.onLayout) { + this.props.onLayout(); } - if (this.props.onLayout) { - this.props.onLayout(); - } - }} - contentContainerStyles={[safeAreaPaddingBottomStyle, ...this.props.contentContainerStyles]} - listContainerStyles={this.props.listContainerStyles} - listStyles={this.props.listStyles} - isLoading={!this.props.shouldShowOptions} - showScrollIndicator={this.props.showScrollIndicator} - isRowMultilineSupported={this.props.isRowMultilineSupported} - shouldShowHeaderMessage={this.props.shouldShowHeaderMessage} - /> - + }} + contentContainerStyles={[safeAreaPaddingBottomStyle, ...this.props.contentContainerStyles]} + listContainerStyles={this.props.listContainerStyles} + listStyles={this.props.listStyles} + isLoading={!this.props.shouldShowOptions} + showScrollIndicator={this.props.showScrollIndicator} + isRowMultilineSupported={this.props.isRowMultilineSupported} + shouldShowHeaderMessage={this.props.shouldShowHeaderMessage} + /> ); return ( Date: Tue, 3 Oct 2023 10:49:18 -1000 Subject: [PATCH 14/29] Undo bad whitespace change --- src/components/OptionsSelector/BaseOptionsSelector.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/OptionsSelector/BaseOptionsSelector.js b/src/components/OptionsSelector/BaseOptionsSelector.js index 9d2ec7d493c6..415600397e74 100755 --- a/src/components/OptionsSelector/BaseOptionsSelector.js +++ b/src/components/OptionsSelector/BaseOptionsSelector.js @@ -415,9 +415,9 @@ class BaseOptionsSelector extends Component { this.scrollToIndex(this.state.focusedIndex, false); } if (this.props.onLayout) { - this.props.onLayout(); - } - }} + this.props.onLayout(); + } + }} contentContainerStyles={[safeAreaPaddingBottomStyle, ...this.props.contentContainerStyles]} listContainerStyles={this.props.listContainerStyles} listStyles={this.props.listStyles} From 06531cb188c3ef142883fc1fc48de4fe457bc190 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Tue, 3 Oct 2023 10:49:44 -1000 Subject: [PATCH 15/29] undo another whitespace change --- src/components/OptionsSelector/BaseOptionsSelector.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/OptionsSelector/BaseOptionsSelector.js b/src/components/OptionsSelector/BaseOptionsSelector.js index 415600397e74..0daf1d5e6d48 100755 --- a/src/components/OptionsSelector/BaseOptionsSelector.js +++ b/src/components/OptionsSelector/BaseOptionsSelector.js @@ -414,6 +414,7 @@ class BaseOptionsSelector extends Component { if (this.props.selectedOptions.length === 0) { this.scrollToIndex(this.state.focusedIndex, false); } + if (this.props.onLayout) { this.props.onLayout(); } From 95d436b7b98b83c2dc47bb9c518838b306189b50 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Tue, 3 Oct 2023 12:51:42 -1000 Subject: [PATCH 16/29] Fix UI race issue with No results found --- src/libs/actions/Report.js | 25 +++++++++++++++---------- src/pages/NewChatPage.js | 6 +++--- src/pages/SearchPage.js | 6 +++--- 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 0dc40baabe84..659479253ae5 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -2172,9 +2172,10 @@ function clearPrivateNotesError(reportID, accountID) { * @private * @param {string} searchInput */ -function searchInServer(searchInput) { +function searchForReports(searchInput) { // We do not try to make this request while offline because it sets a loading indicator optimistically if (isNetworkOffline) { + Onyx.set(ONYXKEYS.IS_SEARCHING_FOR_REPORTS, false); return; } @@ -2182,13 +2183,6 @@ function searchInServer(searchInput) { 'SearchForReports', {searchInput}, { - optimisticData: [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.IS_SEARCHING_FOR_REPORTS, - value: true, - }, - ], successData: [ { onyxMethod: Onyx.METHOD.MERGE, @@ -2211,10 +2205,21 @@ function searchInServer(searchInput) { * @private * @param {string} searchInput */ -const debouncedSearchInServer = lodashDebounce(searchInServer, 300, {leading: false}); +const debouncedSearchInServer = lodashDebounce(searchForReports, 300, {leading: false}); + +/** + * @param {string} searchInput + */ +function searchInServer(searchInput) { + // Why not set this in optimistic data? It won't run until the API request happens and while the API request is debounced + // 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); +} export { - debouncedSearchInServer, + searchInServer, addComment, addAttachment, reconnect, diff --git a/src/pages/NewChatPage.js b/src/pages/NewChatPage.js index 84222d1e26ac..2b34a077fb0b 100755 --- a/src/pages/NewChatPage.js +++ b/src/pages/NewChatPage.js @@ -170,9 +170,9 @@ function NewChatPage({betas, isGroupChat, personalDetails, reports, translate, i }, [reports, personalDetails, searchTerm]); // When search term updates we will fetch any reports - const setSearchTermAndDebounceSearchInServer = useCallback((text = '') => { + const setSearchTermAndSearchInServer = useCallback((text = '') => { if (text.length) { - Report.debouncedSearchInServer(text); + Report.searchInServer(text); } setSearchTerm(text); }, []); @@ -204,7 +204,7 @@ function NewChatPage({betas, isGroupChat, personalDetails, reports, translate, i selectedOptions={selectedOptions} value={searchTerm} onSelectRow={(option) => createChat(option)} - onChangeText={setSearchTermAndDebounceSearchInServer} + onChangeText={setSearchTermAndSearchInServer} headerMessage={headerMessage} boldStyle shouldFocusOnSelectRow={!Browser.isMobile()} diff --git a/src/pages/SearchPage.js b/src/pages/SearchPage.js index 8ee9aa8c3f57..f57f86c50189 100755 --- a/src/pages/SearchPage.js +++ b/src/pages/SearchPage.js @@ -2,7 +2,7 @@ import _ from 'underscore'; import React, {Component} from 'react'; import {View} from 'react-native'; import PropTypes from 'prop-types'; -import {withOnyx} from 'react-native-onyx'; +import Onyx, {withOnyx} from 'react-native-onyx'; import OptionsSelector from '../components/OptionsSelector'; import * as OptionsListUtils from '../libs/OptionsListUtils'; import * as ReportUtils from '../libs/ReportUtils'; @@ -80,7 +80,7 @@ class SearchPage extends Component { onChangeText(searchValue = '') { if (searchValue.length) { - Report.debouncedSearchInServer(searchValue); + Report.searchInServer(searchValue); } // When the user searches we will @@ -127,7 +127,7 @@ class SearchPage extends Component { data: [{loadingRow: true}], shouldShow: true, indexOffset, - }) + }); } return sections; } From 3d1071046b52f07eab742b6f8ed031a5b75a2260 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Tue, 3 Oct 2023 13:20:53 -1000 Subject: [PATCH 17/29] Add loadingRow to NewChat page --- src/pages/NewChatPage.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/pages/NewChatPage.js b/src/pages/NewChatPage.js index 2b34a077fb0b..a58748aa7546 100755 --- a/src/pages/NewChatPage.js +++ b/src/pages/NewChatPage.js @@ -104,8 +104,16 @@ function NewChatPage({betas, isGroupChat, personalDetails, reports, translate, i }); } + if (isSearchingForReports) { + sectionsList.push({ + data: [{loadingRow: true}], + shouldShow: true, + indexOffset, + }); + } + return sectionsList; - }, [translate, filteredPersonalDetails, filteredRecentReports, filteredUserToInvite, maxParticipantsReached, selectedOptions]); + }, [translate, filteredPersonalDetails, filteredRecentReports, filteredUserToInvite, maxParticipantsReached, selectedOptions, isSearchingForReports]); /** * Removes a selected option from list if already selected. If not already selected add this option to the list. @@ -215,7 +223,7 @@ function NewChatPage({betas, isGroupChat, personalDetails, reports, translate, i onConfirmSelection={createGroup} textInputLabel={translate('optionsSelector.nameEmailOrPhoneNumber')} safeAreaPaddingBottomStyle={safeAreaPaddingBottomStyle} - shouldShowLoader={isSearchingForReports} + shouldShowHeaderMessage={!isSearchingForReports} /> From 4c801f7f479b366eaf7cc8d6607212da358323ec Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Tue, 3 Oct 2023 13:34:17 -1000 Subject: [PATCH 18/29] Clean up --- .../OptionsSelector/BaseOptionsSelector.js | 61 +++++++++---------- src/pages/SearchPage.js | 1 - 2 files changed, 30 insertions(+), 32 deletions(-) diff --git a/src/components/OptionsSelector/BaseOptionsSelector.js b/src/components/OptionsSelector/BaseOptionsSelector.js index e024ce6135d8..0e53683e2e99 100755 --- a/src/components/OptionsSelector/BaseOptionsSelector.js +++ b/src/components/OptionsSelector/BaseOptionsSelector.js @@ -360,38 +360,30 @@ class BaseOptionsSelector extends Component { const shouldShowDefaultConfirmButton = !this.props.footerContent && defaultConfirmButtonText; const safeAreaPaddingBottomStyle = shouldShowFooter ? undefined : this.props.safeAreaPaddingBottomStyle; const textInput = ( - <> - (this.textInput = el)} - value={this.props.value} - label={this.props.textInputLabel} - accessibilityLabel={this.props.textInputLabel} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} - onChangeText={this.props.onChangeText} - onSubmitEditing={this.selectFocusedOption} - placeholder={this.props.placeholderText} - maxLength={this.props.maxLength} - keyboardType={this.props.keyboardType} - onBlur={(e) => { - if (!this.props.shouldFocusOnSelectRow) { - return; - } - this.relatedTarget = e.relatedTarget; - }} - selectTextOnFocus - blurOnSubmit={Boolean(this.state.allOptions.length)} - spellCheck={false} - shouldInterceptSwipe={this.props.shouldTextInputInterceptSwipe} - /> - {Boolean(this.props.textInputAlert) && ( - - )} - + (this.textInput = el)} + value={this.props.value} + label={this.props.textInputLabel} + accessibilityLabel={this.props.textInputLabel} + accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} + onChangeText={this.props.onChangeText} + onSubmitEditing={this.selectFocusedOption} + placeholder={this.props.placeholderText} + maxLength={this.props.maxLength} + keyboardType={this.props.keyboardType} + onBlur={(e) => { + if (!this.props.shouldFocusOnSelectRow) { + return; + } + this.relatedTarget = e.relatedTarget; + }} + selectTextOnFocus + blurOnSubmit={Boolean(this.state.allOptions.length)} + spellCheck={false} + shouldInterceptSwipe={this.props.shouldTextInputInterceptSwipe} + /> ); + const optionsList = ( (this.list = el)} @@ -451,6 +443,13 @@ class BaseOptionsSelector extends Component { {this.props.children} {this.props.shouldShowTextInput && textInput} + {Boolean(this.props.textInputAlert) && ( + + )} {optionsList} diff --git a/src/pages/SearchPage.js b/src/pages/SearchPage.js index f57f86c50189..46f086bfd854 100755 --- a/src/pages/SearchPage.js +++ b/src/pages/SearchPage.js @@ -83,7 +83,6 @@ class SearchPage extends Component { Report.searchInServer(searchValue); } - // When the user searches we will this.setState({searchValue}, this.debouncedUpdateOptions); } From 92c0ed48fcfad44f6ca21302ccb93927e37286d4 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Tue, 3 Oct 2023 13:35:10 -1000 Subject: [PATCH 19/29] remove new line --- src/components/OptionsSelector/BaseOptionsSelector.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/OptionsSelector/BaseOptionsSelector.js b/src/components/OptionsSelector/BaseOptionsSelector.js index 0e53683e2e99..fa1c1415b256 100755 --- a/src/components/OptionsSelector/BaseOptionsSelector.js +++ b/src/components/OptionsSelector/BaseOptionsSelector.js @@ -383,7 +383,6 @@ class BaseOptionsSelector extends Component { shouldInterceptSwipe={this.props.shouldTextInputInterceptSwipe} /> ); - const optionsList = ( (this.list = el)} From b101e17d4fcf8829d124669a850e6b5d19dbcf81 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Tue, 3 Oct 2023 16:21:26 -1000 Subject: [PATCH 20/29] Style --- src/pages/SearchPage.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/pages/SearchPage.js b/src/pages/SearchPage.js index 46f086bfd854..59b26d7469b5 100755 --- a/src/pages/SearchPage.js +++ b/src/pages/SearchPage.js @@ -2,7 +2,7 @@ import _ from 'underscore'; import React, {Component} from 'react'; import {View} from 'react-native'; import PropTypes from 'prop-types'; -import Onyx, {withOnyx} from 'react-native-onyx'; +import {withOnyx} from 'react-native-onyx'; import OptionsSelector from '../components/OptionsSelector'; import * as OptionsListUtils from '../libs/OptionsListUtils'; import * as ReportUtils from '../libs/ReportUtils'; @@ -202,7 +202,9 @@ class SearchPage extends Component { showTitleTooltip shouldShowOptions={didScreenTransitionEnd && isOptionsDataReady} textInputLabel={this.props.translate('optionsSelector.nameEmailOrPhoneNumber')} - textInputAlert={this.props.network.isOffline ? `${this.props.translate('common.youAppearToBeOffline')} ${this.props.translate('search.resultsAreLimited')}` : ''} + textInputAlert={ + this.props.network.isOffline ? `${this.props.translate('common.youAppearToBeOffline')} ${this.props.translate('search.resultsAreLimited')}` : '' + } onLayout={this.searchRendered} safeAreaPaddingBottomStyle={safeAreaPaddingBottomStyle} autoFocus From 007d2d3defdf0287c0fb9b91bc561b438f464668 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Wed, 4 Oct 2023 11:49:08 -1000 Subject: [PATCH 21/29] Show loading spinner instead of skeleton --- src/components/OptionsList/BaseOptionsList.js | 15 ++++----------- .../OptionsList/optionsListPropTypes.js | 6 +++--- .../OptionsSelector/BaseOptionsSelector.js | 3 ++- src/components/TextInput/BaseTextInput.js | 5 ++++- src/pages/NewChatPage.js | 12 ++---------- src/pages/SearchPage.js | 9 +-------- 6 files changed, 16 insertions(+), 34 deletions(-) diff --git a/src/components/OptionsList/BaseOptionsList.js b/src/components/OptionsList/BaseOptionsList.js index 2521962756a5..358b4a8c77e1 100644 --- a/src/components/OptionsList/BaseOptionsList.js +++ b/src/components/OptionsList/BaseOptionsList.js @@ -66,7 +66,7 @@ function BaseOptionsList({ isDisabled, innerRef, isRowMultilineSupported, - shouldShowHeaderMessage, + isLoadingNewOptions, }) { const flattenedData = useRef(); const previousSections = usePrevious(sections); @@ -191,15 +191,6 @@ function BaseOptionsList({ return option.name === item.searchText; }); - if (item.loadingRow) { - return ( - - ); - } - return ( ) : ( <> - {shouldShowHeaderMessage && headerMessage ? ( + {/* If we are loading new options we will avoid showing any header message. This is mostly because one of the header messages says there are no options. */} + {/* This is confusing because we might be in the process of loading fresh options from the server. */} + {!isLoadingNewOptions && headerMessage ? ( {headerMessage} diff --git a/src/components/OptionsList/optionsListPropTypes.js b/src/components/OptionsList/optionsListPropTypes.js index 967f30d44119..fb40311dfe76 100644 --- a/src/components/OptionsList/optionsListPropTypes.js +++ b/src/components/OptionsList/optionsListPropTypes.js @@ -85,8 +85,8 @@ const propTypes = { /** Whether to wrap large text up to 2 lines */ isRowMultilineSupported: PropTypes.bool, - /** Whether to show the header message below the input */ - shouldShowHeaderMessage: PropTypes.bool, + /** Whether we are loading new options */ + isLoadingNewOptions: PropTypes.bool, }; const defaultProps = { @@ -112,7 +112,7 @@ const defaultProps = { shouldDisableRowInnerPadding: false, showScrollIndicator: false, isRowMultilineSupported: false, - shouldShowHeaderMessage: true, + isLoadingNewOptions: true, }; export {propTypes, defaultProps}; diff --git a/src/components/OptionsSelector/BaseOptionsSelector.js b/src/components/OptionsSelector/BaseOptionsSelector.js index fa1c1415b256..6e0339e4859c 100755 --- a/src/components/OptionsSelector/BaseOptionsSelector.js +++ b/src/components/OptionsSelector/BaseOptionsSelector.js @@ -381,6 +381,7 @@ class BaseOptionsSelector extends Component { blurOnSubmit={Boolean(this.state.allOptions.length)} spellCheck={false} shouldInterceptSwipe={this.props.shouldTextInputInterceptSwipe} + isLoading={this.props.isLoadingNewOptions} /> ); const optionsList = ( @@ -417,7 +418,7 @@ class BaseOptionsSelector extends Component { isLoading={!this.props.shouldShowOptions} showScrollIndicator={this.props.showScrollIndicator} isRowMultilineSupported={this.props.isRowMultilineSupported} - shouldShowHeaderMessage={this.props.shouldShowHeaderMessage} + isLoadingNewOptions={this.props.isLoadingNewOptions} /> ); return ( diff --git a/src/components/TextInput/BaseTextInput.js b/src/components/TextInput/BaseTextInput.js index 91591de7f045..28baed106b1c 100644 --- a/src/components/TextInput/BaseTextInput.js +++ b/src/components/TextInput/BaseTextInput.js @@ -1,6 +1,6 @@ import _ from 'underscore'; import React, {useState, useRef, useEffect, useCallback, useMemo} from 'react'; -import {Animated, View, StyleSheet} from 'react-native'; +import {Animated, View, StyleSheet, ActivityIndicator} from 'react-native'; import Str from 'expensify-common/lib/str'; import RNTextInput from '../RNTextInput'; import TextInputLabel from './TextInputLabel'; @@ -368,6 +368,9 @@ function BaseTextInput(props) { // `dataset.submitOnEnter` is used to indicate that pressing Enter on this input should call the submit callback. dataSet={{submitOnEnter: isMultiline && props.submitOnEnter}} /> + {props.isLoading && ( + + )} {Boolean(props.secureTextEntry) && ( diff --git a/src/pages/SearchPage.js b/src/pages/SearchPage.js index 59b26d7469b5..1cf1e97e5990 100755 --- a/src/pages/SearchPage.js +++ b/src/pages/SearchPage.js @@ -121,13 +121,6 @@ class SearchPage extends Component { }); } - if (this.props.isSearchingForReports) { - sections.push({ - data: [{loadingRow: true}], - shouldShow: true, - indexOffset, - }); - } return sections; } @@ -208,7 +201,7 @@ class SearchPage extends Component { onLayout={this.searchRendered} safeAreaPaddingBottomStyle={safeAreaPaddingBottomStyle} autoFocus - shouldShowHeaderMessage={!this.props.isSearchingForReports} + isLoadingNewOptions={this.props.isSearchingForReports} /> From 4e6826a7faf852647d6c4fbd624d880f5f2dce9e Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Wed, 4 Oct 2023 11:51:14 -1000 Subject: [PATCH 22/29] Undo skeleton row changes --- src/components/OptionsList/BaseOptionsList.js | 1 - src/components/OptionsListSkeletonRow.js | 48 ------------------- src/components/OptionsListSkeletonView.js | 33 +++++++++++-- 3 files changed, 28 insertions(+), 54 deletions(-) delete mode 100644 src/components/OptionsListSkeletonRow.js diff --git a/src/components/OptionsList/BaseOptionsList.js b/src/components/OptionsList/BaseOptionsList.js index 358b4a8c77e1..9fa25be0868a 100644 --- a/src/components/OptionsList/BaseOptionsList.js +++ b/src/components/OptionsList/BaseOptionsList.js @@ -10,7 +10,6 @@ import Text from '../Text'; import {propTypes as optionsListPropTypes, defaultProps as optionsListDefaultProps} from './optionsListPropTypes'; import OptionsListSkeletonView from '../OptionsListSkeletonView'; import usePrevious from '../../hooks/usePrevious'; -import OptionsListSkeletonRow from '../OptionsListSkeletonRow'; const propTypes = { /** Determines whether the keyboard gets dismissed in response to a drag */ diff --git a/src/components/OptionsListSkeletonRow.js b/src/components/OptionsListSkeletonRow.js deleted file mode 100644 index d7968ef1ba80..000000000000 --- a/src/components/OptionsListSkeletonRow.js +++ /dev/null @@ -1,48 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import {Rect, Circle} from 'react-native-svg'; -import SkeletonViewContentLoader from 'react-content-loader/native'; -import themeColors from '../styles/themes/default'; -import CONST from '../CONST'; -import styles from '../styles/styles'; - -const propTypes = { - /** Whether to animate the skeleton view */ - shouldAnimate: PropTypes.bool.isRequired, - - /** Line width string */ - lineWidth: PropTypes.string.isRequired, -}; - -function OptionsListSkeletonRow({lineWidth, shouldAnimate}) { - return ( - - - - - - ); -} - -OptionsListSkeletonRow.propTypes = propTypes; -export default OptionsListSkeletonRow; diff --git a/src/components/OptionsListSkeletonView.js b/src/components/OptionsListSkeletonView.js index 833cbc0f372e..15c66affe84d 100644 --- a/src/components/OptionsListSkeletonView.js +++ b/src/components/OptionsListSkeletonView.js @@ -1,9 +1,11 @@ import React from 'react'; import {View} from 'react-native'; import PropTypes from 'prop-types'; +import {Rect, Circle} from 'react-native-svg'; +import SkeletonViewContentLoader from 'react-content-loader/native'; import CONST from '../CONST'; +import themeColors from '../styles/themes/default'; import styles from '../styles/styles'; -import OptionsListSkeletonRow from './OptionsListSkeletonRow'; const propTypes = { /** Whether to animate the skeleton view */ @@ -54,11 +56,32 @@ class OptionsListSkeletonView extends React.Component { lineWidth = '25%'; } skeletonViewItems.push( - , + animate={this.props.shouldAnimate} + height={CONST.LHN_SKELETON_VIEW_ITEM_HEIGHT} + backgroundColor={themeColors.skeletonLHNIn} + foregroundColor={themeColors.skeletonLHNOut} + style={styles.mr5} + > + + + + , ); } From 96400edcbe72105e0e4b948673864296161efa04 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Wed, 4 Oct 2023 13:08:18 -1000 Subject: [PATCH 23/29] run prettier --- src/components/TextInput/BaseTextInput.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/TextInput/BaseTextInput.js b/src/components/TextInput/BaseTextInput.js index 28baed106b1c..826e9ed5e957 100644 --- a/src/components/TextInput/BaseTextInput.js +++ b/src/components/TextInput/BaseTextInput.js @@ -369,7 +369,11 @@ function BaseTextInput(props) { dataSet={{submitOnEnter: isMultiline && props.submitOnEnter}} /> {props.isLoading && ( - + )} {Boolean(props.secureTextEntry) && ( Date: Thu, 5 Oct 2023 10:43:15 -1000 Subject: [PATCH 24/29] Call OpenApp when the priorityMode changes --- src/libs/actions/App.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/libs/actions/App.js b/src/libs/actions/App.js index f2017a982fb0..d1a8db889f81 100644 --- a/src/libs/actions/App.js +++ b/src/libs/actions/App.js @@ -209,6 +209,18 @@ function openApp() { }); } +let priorityMode; +Onyx.connect({ + key: ONYXKEYS.NVP_PRIORITY_MODE, + callback: (nextPriorityMode) => { + // When someone switches their priority mode we need to fetch all their chats. This is only possible via the OpenApp command. + if (nextPriorityMode === CONST.PRIORITY_MODE.DEFAULT && priorityMode === CONST.PRIORITY_MODE.GSD) { + openApp(); + } + priorityMode = nextPriorityMode; + }, +}); + /** * Fetches data when the app reconnects to the network * @param {Number} [updateIDFrom] the ID of the Onyx update that we want to start fetching from From 94411aedd97d85c94a9a01953a22c217e88f774b Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Fri, 6 Oct 2023 10:16:13 -1000 Subject: [PATCH 25/29] Fix loading when offline --- src/libs/actions/App.js | 25 +++++++++++++------------ src/libs/actions/Report.js | 7 ++++++- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/src/libs/actions/App.js b/src/libs/actions/App.js index d1a8db889f81..e07845066c6a 100644 --- a/src/libs/actions/App.js +++ b/src/libs/actions/App.js @@ -41,6 +41,19 @@ Onyx.connect({ callback: (val) => (preferredLocale = val), }); +let priorityMode; +Onyx.connect({ + key: ONYXKEYS.NVP_PRIORITY_MODE, + callback: (nextPriorityMode) => { + // When someone switches their priority mode we need to fetch all their chats. This is only possible via the OpenApp command. + if (nextPriorityMode === CONST.PRIORITY_MODE.DEFAULT && priorityMode === CONST.PRIORITY_MODE.GSD) { + // eslint-disable-next-line no-use-before-define + openApp(); + } + priorityMode = nextPriorityMode; + }, +}); + let resolveIsReadyPromise; const isReadyToOpenApp = new Promise((resolve) => { resolveIsReadyPromise = resolve; @@ -209,18 +222,6 @@ function openApp() { }); } -let priorityMode; -Onyx.connect({ - key: ONYXKEYS.NVP_PRIORITY_MODE, - callback: (nextPriorityMode) => { - // When someone switches their priority mode we need to fetch all their chats. This is only possible via the OpenApp command. - if (nextPriorityMode === CONST.PRIORITY_MODE.DEFAULT && priorityMode === CONST.PRIORITY_MODE.GSD) { - openApp(); - } - priorityMode = nextPriorityMode; - }, -}); - /** * Fetches data when the app reconnects to the network * @param {Number} [updateIDFrom] the ID of the Onyx update that we want to start fetching from diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 659479253ae5..e0d325bdb103 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -2205,12 +2205,17 @@ function searchForReports(searchInput) { * @private * @param {string} searchInput */ -const debouncedSearchInServer = lodashDebounce(searchForReports, 300, {leading: false}); +const debouncedSearchInServer = lodashDebounce(searchForReports, CONST.TIMING.SEARCH_FOR_REPORTS_DEBOUNCE_TIME, {leading: false}); /** * @param {string} searchInput */ function searchInServer(searchInput) { + if (isNetworkOffline) { + Onyx.set(ONYXKEYS.IS_SEARCHING_FOR_REPORTS, false); + return; + } + // Why not set this in optimistic data? It won't run until the API request happens and while the API request is debounced // 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. From 3816569e71d305d6c5a52a2894a4ba9c4615b200 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Fri, 6 Oct 2023 10:16:35 -1000 Subject: [PATCH 26/29] Add constant for debounce time --- src/CONST.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/CONST.ts b/src/CONST.ts index 0a262d868de9..9eaf77ed6fad 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -664,6 +664,7 @@ const CONST = { TOOLTIP_SENSE: 1000, TRIE_INITIALIZATION: 'trie_initialization', COMMENT_LENGTH_DEBOUNCE_TIME: 500, + SEARCH_FOR_REPORTS_DEBOUNCE_TIME: 300, }, PRIORITY_MODE: { GSD: 'gsd', From 5719736b972fd294f11718b5c0f28310e96d7586 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Tue, 10 Oct 2023 11:31:17 -1000 Subject: [PATCH 27/29] make requested comment change --- src/components/OptionsList/BaseOptionsList.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/OptionsList/BaseOptionsList.js b/src/components/OptionsList/BaseOptionsList.js index a82217767ab6..edea0b8d1aba 100644 --- a/src/components/OptionsList/BaseOptionsList.js +++ b/src/components/OptionsList/BaseOptionsList.js @@ -247,7 +247,7 @@ function BaseOptionsList({ ) : ( <> {/* If we are loading new options we will avoid showing any header message. This is mostly because one of the header messages says there are no options. */} - {/* This is confusing because we might be in the process of loading fresh options from the server. */} + {/* This is misleading because we might be in the process of loading fresh options from the server. */} {!isLoadingNewOptions && headerMessage ? ( {headerMessage} From 992ae36b0419515af2964f84e11b335b4bac2491 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Thu, 12 Oct 2023 09:02:54 -1000 Subject: [PATCH 28/29] Update src/libs/actions/App.js Co-authored-by: Rory Abraham <47436092+roryabraham@users.noreply.github.com> --- src/libs/actions/App.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/App.js b/src/libs/actions/App.js index 52ba02725be4..a1d64154906c 100644 --- a/src/libs/actions/App.js +++ b/src/libs/actions/App.js @@ -48,7 +48,7 @@ let priorityMode; Onyx.connect({ key: ONYXKEYS.NVP_PRIORITY_MODE, callback: (nextPriorityMode) => { - // When someone switches their priority mode we need to fetch all their chats. This is only possible via the OpenApp command. + // When someone switches their priority mode we need to fetch all their chats because only #focus mode works with a subset of a user's chats. This is only possible via the OpenApp command. if (nextPriorityMode === CONST.PRIORITY_MODE.DEFAULT && priorityMode === CONST.PRIORITY_MODE.GSD) { // eslint-disable-next-line no-use-before-define openApp(); From 352d376521fe44135f693fecd19a8661fc87f776 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Thu, 12 Oct 2023 09:10:38 -1000 Subject: [PATCH 29/29] Use withNetwork. propType comment --- src/pages/SearchPage.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/SearchPage.js b/src/pages/SearchPage.js index 12a71c456821..272fb30de858 100755 --- a/src/pages/SearchPage.js +++ b/src/pages/SearchPage.js @@ -21,6 +21,7 @@ import personalDetailsPropType from './personalDetailsPropType'; import reportPropTypes from './reportPropTypes'; import Performance from '../libs/Performance'; import networkPropTypes from '../components/networkPropTypes'; +import {withNetwork} from '../components/OnyxProvider'; const propTypes = { /* Onyx Props */ @@ -39,6 +40,7 @@ const propTypes = { ...withLocalizePropTypes, + /** Network info */ network: networkPropTypes, /** Whether we are searching for reports in the server */ @@ -221,6 +223,7 @@ SearchPage.defaultProps = defaultProps; export default compose( withLocalize, withWindowDimensions, + withNetwork(), withOnyx({ reports: { key: ONYXKEYS.COLLECTION.REPORT, @@ -235,8 +238,5 @@ export default compose( key: ONYXKEYS.IS_SEARCHING_FOR_REPORTS, initWithStoredValues: false, }, - network: { - key: ONYXKEYS.NETWORK, - }, }), )(SearchPage);