From a9ab1fe00b93684e6aea73b32e93d30bc1ca4979 Mon Sep 17 00:00:00 2001 From: "marta.sudol" Date: Thu, 24 Apr 2025 12:17:32 +0200 Subject: [PATCH 01/26] fix: avoid duplicated /SearchForReport calls --- .../new-expensify/search/Search-overview.md | 54 +++++++++++++++++++ .../Search/SearchAutocompleteList.tsx | 12 ++++- .../SearchPageHeader/SearchPageHeader.tsx | 4 +- .../SearchPageHeaderInput.tsx | 15 +++++- .../Search/SearchRouter/SearchRouter.tsx | 7 ++- src/components/Search/index.tsx | 18 +++++-- src/components/Search/types.ts | 8 +++ src/pages/Search/SearchPage.tsx | 17 ++++-- 8 files changed, 121 insertions(+), 14 deletions(-) create mode 100644 docs/articles/new-expensify/search/Search-overview.md diff --git a/docs/articles/new-expensify/search/Search-overview.md b/docs/articles/new-expensify/search/Search-overview.md new file mode 100644 index 000000000000..2594504728a1 --- /dev/null +++ b/docs/articles/new-expensify/search/Search-overview.md @@ -0,0 +1,54 @@ +--- +title: Search Functionality in New Expensify +description: Learn how to effectively use the powerful search feature in New Expensify to find and filter your financial data quickly and efficiently. +--- +
+ +The Search functionality in New Expensify provides a powerful way to locate and filter financial data. +With advanced autocomplete suggestions and predefined filters, you can quickly find specific expenses, reports, +invoices, and more across the platform. + +## Main Uses + +- **Locate specific transactions** - Quickly find expenses, invoices, or other financial data using keywords or filters. +- **Filter by status** - Easily view items based on their current state (Drafts, Outstanding, Approved, etc.). +- **Save frequent searches** - Store commonly used search parameters for quick access in the future. + +## Search Components +The search functionality in Expensify consists of several key components: + +- **Search input** - Located at the top of the Reports view for entering search terms. +- **Left-hand navigation (LHN)** - Allows switching between different data types and saved searches. +- **Predefined filters** - Quick-access filters at the top of each list view. +- **Autocomplete modal** - Suggestions that appear as you type in the search input. +- **Results list** - The formatted list of search results displayed below the filters, showing matching items based on +your search criteria and selected filters. + +# How Search Works +## Basic Navigation +1. When you select a data type from the LHN (Expenses, Expense Reports, Chats, Invoices, or Trips), the system calls the `/Search` endpoint to display results. +2. Selecting a predefined filter (All, Drafts, Outstanding, etc.) also triggers the `/Search` endpoint with the appropriate parameters. + +## Using Predefined Filters +Each data type offers specific predefined filters for quick access: + +1. Navigate to the desired data type view (e.g., Expenses) via the LHN. +2. At the top of the list, select one of the predefined filters: + - **All** - Shows all items (default selection) + - **Drafts** - Items not yet submitted + - **Outstanding** - Items awaiting action + - **Approved** - Items that have received approval + - **Done** - Completed items + - **Paid** - Items that have been reimbursed or paid + +Note: Available filters may vary depending on the data type selected. + +## Using the Search Input +The search input provides powerful functionality: +1. Click in the search field at the top of the Reports view. +2. Begin typing your search term. +3. The autocomplete modal appears with suggestions: + - The first option always shows your exact search text with a magnifying glass icon + - Additional suggestions based on your input from `/SearchForReports` and Onyx data +4. Select first suggestion (magnifying glass icon with search text) or press Enter to execute the search (`/Search` endpoint call). +5. Clicking on any other suggestion (retrieved from either `/SearchForReports` or Onyx data) directly opens the selected report by calling the `/OpenReport` endpoint. diff --git a/src/components/Search/SearchAutocompleteList.tsx b/src/components/Search/SearchAutocompleteList.tsx index 2ce9c5329a70..c0dba05747df 100644 --- a/src/components/Search/SearchAutocompleteList.tsx +++ b/src/components/Search/SearchAutocompleteList.tsx @@ -16,7 +16,6 @@ import useLocalize from '@hooks/useLocalize'; import usePolicy from '@hooks/usePolicy'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; -import {searchInServer} from '@libs/actions/Report'; import {getCardFeedKey, getCardFeedNamesWithType} from '@libs/CardFeedUtils'; import {getCardDescription, isCard, isCardHiddenFromSearch, mergeCardListWithWorkspaceFeeds} from '@libs/CardUtils'; import memoize from '@libs/memoize'; @@ -56,6 +55,9 @@ type SearchAutocompleteListProps = { /** Value of TextInput */ autocompleteQueryValue: string; + /** Callback to trigger search action * */ + handleSearch?: (value: string) => void; + /** An optional item to always display on the top of the router list */ searchQueryItem?: SearchQueryItem; @@ -129,6 +131,7 @@ function SearchRouterItem(props: UserListItemProps | SearchQueryList function SearchAutocompleteList( { autocompleteQueryValue, + handleSearch, searchQueryItem, getAdditionalSections, onListItemPress, @@ -470,7 +473,12 @@ function SearchAutocompleteList( }, [autocompleteQueryValue, filterOptions, searchOptions]); useEffect(() => { - searchInServer(autocompleteQueryValue.trim()); + if (!handleSearch) { + return; + } + handleSearch(autocompleteQueryValue); + // eslint-disable-next-line react-compiler/react-compiler + // eslint-disable-next-line react-hooks/exhaustive-deps }, [autocompleteQueryValue]); /* Sections generation */ diff --git a/src/components/Search/SearchPageHeader/SearchPageHeader.tsx b/src/components/Search/SearchPageHeader/SearchPageHeader.tsx index 3e3c3f9d4259..9427890d0470 100644 --- a/src/components/Search/SearchPageHeader/SearchPageHeader.tsx +++ b/src/components/Search/SearchPageHeader/SearchPageHeader.tsx @@ -28,11 +28,12 @@ type SearchPageHeaderProps = { hideSearchRouterList?: () => void; onSearchRouterFocus?: () => void; headerButtonsOptions: Array>; + handleSearch: (value: string) => void; }; type SearchHeaderOptionValue = DeepValueOf | undefined; -function SearchPageHeader({queryJSON, searchName, searchRouterListVisible, hideSearchRouterList, onSearchRouterFocus, headerButtonsOptions}: SearchPageHeaderProps) { +function SearchPageHeader({queryJSON, searchName, searchRouterListVisible, hideSearchRouterList, onSearchRouterFocus, headerButtonsOptions, handleSearch}: SearchPageHeaderProps) { const styles = useThemeStyles(); const {shouldUseNarrowLayout} = useResponsiveLayout(); const {selectedTransactions} = useSearchContext(); @@ -85,6 +86,7 @@ function SearchPageHeader({queryJSON, searchName, searchRouterListVisible, hideS searchName={searchName} hideSearchRouterList={hideSearchRouterList} inputRightComponent={InputRightComponent} + handleSearch={handleSearch} /> ); } diff --git a/src/components/Search/SearchPageHeader/SearchPageHeaderInput.tsx b/src/components/Search/SearchPageHeader/SearchPageHeaderInput.tsx index 0be5ef0ae02c..d84b9313cd6d 100644 --- a/src/components/Search/SearchPageHeader/SearchPageHeaderInput.tsx +++ b/src/components/Search/SearchPageHeader/SearchPageHeaderInput.tsx @@ -58,9 +58,10 @@ type SearchPageHeaderInputProps = { onSearchRouterFocus?: () => void; searchName?: string; inputRightComponent: React.ReactNode; + handleSearch: (value: string) => void; }; -function SearchPageHeaderInput({queryJSON, searchRouterListVisible, hideSearchRouterList, onSearchRouterFocus, searchName, inputRightComponent}: SearchPageHeaderInputProps) { +function SearchPageHeaderInput({queryJSON, searchRouterListVisible, hideSearchRouterList, onSearchRouterFocus, searchName, inputRightComponent, handleSearch}: SearchPageHeaderInputProps) { const {translate} = useLocalize(); const {canUseLeftHandBar} = usePermissions(); const [showPopupButton, setShowPopupButton] = useState(true); @@ -117,7 +118,6 @@ function SearchPageHeaderInput({queryJSON, searchRouterListVisible, hideSearchRo useEffect(() => { setTextInputValue(isDefaultQuery ? '' : queryText); - setAutocompleteQueryValue(isDefaultQuery ? '' : queryText); }, [isDefaultQuery, queryText]); useEffect(() => { @@ -143,6 +143,15 @@ function SearchPageHeaderInput({queryJSON, searchRouterListVisible, hideSearchRo // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + const handleSearchAction = useCallback((value: string) => { + if (!isAutocompleteListVisible) { + return; + } + handleSearch(value); + // eslint-disable-next-line react-compiler/react-compiler + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isAutocompleteListVisible]); + const onSearchQueryChange = useCallback( (userQuery: string) => { const singleLineUserQuery = StringUtils.lineBreaksToSpaces(userQuery, true); @@ -289,6 +298,7 @@ function SearchPageHeaderInput({queryJSON, searchRouterListVisible, hideSearchRo { + searchInServer(value); + }; + const onListItemPress = useCallback( (item: OptionData | SearchQueryItem) => { if (isSearchQueryItem(item)) { @@ -345,6 +349,7 @@ function SearchRouter({onRouterClose, shouldHideInputCaret, isSearchRouterDispla /> ; currentSearchResults?: SearchResults; lastNonEmptySearchResults?: SearchResults; + handleSearch: (value: SearchParams) => void; }; function mapTransactionItemToSelectedEntry(item: TransactionListItemType): [string, SelectedTransactionInfo] { @@ -124,7 +132,7 @@ function prepareTransactionsList(item: TransactionListItemType, selectedTransact }; } -function Search({queryJSON, currentSearchResults, lastNonEmptySearchResults, onSearchListScroll, contentContainerStyle}: SearchProps) { +function Search({queryJSON, currentSearchResults, lastNonEmptySearchResults, onSearchListScroll, contentContainerStyle, handleSearch}: SearchProps) { const {isOffline} = useNetwork(); const {shouldUseNarrowLayout} = useResponsiveLayout(); const styles = useThemeStyles(); @@ -187,8 +195,8 @@ function Search({queryJSON, currentSearchResults, lastNonEmptySearchResults, onS return; } - search({queryJSON, offset}); - }, [isOffline, offset, queryJSON]); + handleSearch({queryJSON, offset}); + }, [handleSearch, isOffline, offset, queryJSON]); const {newSearchResultKey, handleSelectionListScroll} = useSearchHighlightAndScroll({ searchResults, diff --git a/src/components/Search/types.ts b/src/components/Search/types.ts index 39875b85d038..282a45adb7f8 100644 --- a/src/components/Search/types.ts +++ b/src/components/Search/types.ts @@ -74,6 +74,8 @@ type SearchContext = { setLastSearchType: (type: string | undefined) => void; lastSearchType: string | undefined; isOnSearch: boolean; + isPredefinedFilterSelected: boolean; + setIsPredefinedFilterSelected: (isPredefinedFilterSelected: boolean) => void; }; type ASTNode = { @@ -143,6 +145,11 @@ type SearchAutocompleteQueryRange = { value: string; }; +type SearchParams = { + queryJSON: SearchQueryJSON; + offset: number; +} + export type { SelectedTransactionInfo, SelectedTransactions, @@ -167,5 +174,6 @@ export type { SearchAutocompleteResult, PaymentData, SearchAutocompleteQueryRange, + SearchParams, TableColumnSize, }; diff --git a/src/pages/Search/SearchPage.tsx b/src/pages/Search/SearchPage.tsx index e1e05b64c9ba..1ea795caeda8 100644 --- a/src/pages/Search/SearchPage.tsx +++ b/src/pages/Search/SearchPage.tsx @@ -1,4 +1,4 @@ -import React, {useEffect, useMemo, useState} from 'react'; +import React, {useCallback, useEffect, useMemo, useState} from 'react'; import {InteractionManager, View} from 'react-native'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import type {DropdownOption} from '@components/ButtonWithDropdownMenu/types'; @@ -16,7 +16,7 @@ import {useSearchContext} from '@components/Search/SearchContext'; import SearchPageHeader from '@components/Search/SearchPageHeader/SearchPageHeader'; import type {SearchHeaderOptionValue} from '@components/Search/SearchPageHeader/SearchPageHeader'; import SearchStatusBar from '@components/Search/SearchPageHeader/SearchStatusBar'; -import type {PaymentData} from '@components/Search/types'; +import type {PaymentData, SearchParams} from '@components/Search/types'; import {usePlaybackContext} from '@components/VideoPlayerContexts/PlaybackContext'; import useActiveWorkspace from '@hooks/useActiveWorkspace'; import useLocalize from '@hooks/useLocalize'; @@ -31,7 +31,7 @@ import { deleteMoneyRequestOnSearch, exportSearchItemsToCSV, getLastPolicyPaymentMethod, - payMoneyRequestOnSearch, + payMoneyRequestOnSearch, search, unholdMoneyRequestOnSearch, } from '@libs/actions/Search'; import Navigation from '@libs/Navigation/Navigation'; @@ -46,6 +46,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import type {SearchResults} from '@src/types/onyx'; +import { searchInServer } from '@libs/actions/Report'; import SearchPageNarrow from './SearchPageNarrow'; import SearchTypeMenu from './SearchTypeMenu'; @@ -357,6 +358,14 @@ function SearchPage({route}: SearchPageProps) { // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + const handleSearchAction = useCallback((value: SearchParams | string) => { + if (typeof value === 'string') { + searchInServer(value); + } else { + search(value); + } + }, []); + if (shouldUseNarrowLayout) { return ( <> @@ -452,6 +461,7 @@ function SearchPage({route}: SearchPageProps) { From d1f5510c5d3f2fc880a07f92bac9e30f18ff39b0 Mon Sep 17 00:00:00 2001 From: "marta.sudol" Date: Thu, 24 Apr 2025 12:34:13 +0200 Subject: [PATCH 02/26] fix: avoid duplicated /SearchForReport calls --- docs/_data/_routes.yml | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/docs/_data/_routes.yml b/docs/_data/_routes.yml index 7a555c4314f5..905b69875cab 100644 --- a/docs/_data/_routes.yml +++ b/docs/_data/_routes.yml @@ -11,7 +11,7 @@ platforms: url: www.expensify.com description: "Your account settings look like this:" image: /assets/images/settings-old-dot.svg - + # Hubs are comprised of subcategories and articles. Subcategories contain multiple related articles, but there can be standalone articles as well hubs: - href: getting-started @@ -38,7 +38,7 @@ platforms: title: Domains icon: /assets/images/domains.svg description: Claim and verify your company’s domain to access additional management and security features. - + - href: bank-accounts-and-payments title: Bank Accounts & Payments icon: /assets/images/send-money.svg @@ -88,8 +88,8 @@ platforms: title: Travel icon: /assets/images/plane.svg description: Manage all your corporate travel needs with Expensify Travel. - - + + - href: new-expensify title: New Expensify hub-title: New Expensify - Help & Resources @@ -97,7 +97,7 @@ platforms: url: new.expensify.com description: "Your account settings look like this:" image: /assets/images/settings-new-dot.svg - + hubs: - href: getting-started title: Getting Started @@ -113,7 +113,7 @@ platforms: title: Workspaces icon: /assets/images/shield.svg description: Configure rules, settings, and limits for your company’s spending. - + - href: reports-and-expenses title: Reports & Expenses icon: /assets/images/envelope-receipt.svg @@ -128,12 +128,12 @@ platforms: title: Connect Credit Cards icon: /assets/images/bank-card.svg description: Track credit card transactions and reconcile company cards. - + - href: expensify-card title: Expensify Card icon: /assets/images/hand-card.svg description: Explore the perks and benefits of the Expensify Card. - + - href: travel title: Travel icon: /assets/images/plane.svg @@ -148,9 +148,13 @@ platforms: title: Settings icon: /assets/images/gears.svg description: Manage profile settings and notifications. - + - href: billing-and-subscriptions title: Expensify Billing & Subscriptions icon: /assets/images/subscription-annual.svg description: Review Expensify's subscription options, plan types, and payment methods. - + + - href: search-overview + title: Search Functionality in New Expensify + icon: /assets/images/magnifying-glass.svg + description: Search feature overview. From 52b835f2ebb0bb29e51517aeb580c0c66797541b Mon Sep 17 00:00:00 2001 From: "marta.sudol" Date: Thu, 24 Apr 2025 12:37:38 +0200 Subject: [PATCH 03/26] fix: avoid duplicated /SearchForReport calls --- .../Search/SearchPageHeader/SearchPageHeader.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/Search/SearchPageHeader/SearchPageHeader.tsx b/src/components/Search/SearchPageHeader/SearchPageHeader.tsx index 9427890d0470..62244fd382c2 100644 --- a/src/components/Search/SearchPageHeader/SearchPageHeader.tsx +++ b/src/components/Search/SearchPageHeader/SearchPageHeader.tsx @@ -37,16 +37,16 @@ function SearchPageHeader({queryJSON, searchName, searchRouterListVisible, hideS const styles = useThemeStyles(); const {shouldUseNarrowLayout} = useResponsiveLayout(); const {selectedTransactions} = useSearchContext(); - const [selectionMode] = useOnyx(ONYXKEYS.MOBILE_SELECTION_MODE); + const [selectionMode] = useOnyx(ONYXKEYS.MOBILE_SELECTION_MODE, {canBeMissing: true}); const personalDetails = usePersonalDetails(); - const [reports] = useOnyx(ONYXKEYS.COLLECTION.REPORT); + const [reports] = useOnyx(ONYXKEYS.COLLECTION.REPORT, {canBeMissing: true}); const taxRates = getAllTaxRates(); - const [userCardList] = useOnyx(ONYXKEYS.CARD_LIST); - const [workspaceCardFeeds] = useOnyx(ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST); + const [userCardList] = useOnyx(ONYXKEYS.CARD_LIST, {canBeMissing: true}); + const [workspaceCardFeeds] = useOnyx(ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST, {canBeMissing: true}); const allCards = useMemo(() => mergeCardListWithWorkspaceFeeds(workspaceCardFeeds ?? CONST.EMPTY_OBJECT, userCardList), [userCardList, workspaceCardFeeds]); - const [currencyList = {}] = useOnyx(ONYXKEYS.CURRENCY_LIST); - const [policyCategories] = useOnyx(ONYXKEYS.COLLECTION.POLICY_CATEGORIES); - const [policyTagsLists] = useOnyx(ONYXKEYS.COLLECTION.POLICY_TAGS); + const [currencyList = {}] = useOnyx(ONYXKEYS.CURRENCY_LIST, {canBeMissing: true}); + const [policyCategories] = useOnyx(ONYXKEYS.COLLECTION.POLICY_CATEGORIES, {canBeMissing: true}); + const [policyTagsLists] = useOnyx(ONYXKEYS.COLLECTION.POLICY_TAGS, {canBeMissing: true}); const selectedTransactionsKeys = Object.keys(selectedTransactions ?? {}); From 2e9d962335f56c29e48e17e8d28ba99f848e9fcd Mon Sep 17 00:00:00 2001 From: "marta.sudol" Date: Thu, 24 Apr 2025 12:44:00 +0200 Subject: [PATCH 04/26] fix: avoid duplicated /SearchForReport calls --- .../Search/SearchAutocompleteList.tsx | 4 ++-- .../SearchPageHeader/SearchPageHeaderInput.tsx | 17 +++++++++-------- src/components/Search/index.tsx | 9 +-------- src/components/Search/types.ts | 2 +- src/pages/Search/SearchPage.tsx | 2 +- 5 files changed, 14 insertions(+), 20 deletions(-) diff --git a/src/components/Search/SearchAutocompleteList.tsx b/src/components/Search/SearchAutocompleteList.tsx index 6c20a52ec424..3aa954a577d5 100644 --- a/src/components/Search/SearchAutocompleteList.tsx +++ b/src/components/Search/SearchAutocompleteList.tsx @@ -508,8 +508,8 @@ function SearchAutocompleteList( return; } handleSearch(autocompleteQueryValue); - // eslint-disable-next-line react-compiler/react-compiler - // eslint-disable-next-line react-hooks/exhaustive-deps + // eslint-disable-next-line react-compiler/react-compiler + // eslint-disable-next-line react-hooks/exhaustive-deps }, [autocompleteQueryValue]); /* Sections generation */ diff --git a/src/components/Search/SearchPageHeader/SearchPageHeaderInput.tsx b/src/components/Search/SearchPageHeader/SearchPageHeaderInput.tsx index 78c0f1fa1710..3753892a3df7 100644 --- a/src/components/Search/SearchPageHeader/SearchPageHeaderInput.tsx +++ b/src/components/Search/SearchPageHeader/SearchPageHeaderInput.tsx @@ -144,14 +144,15 @@ function SearchPageHeaderInput({queryJSON, searchRouterListVisible, hideSearchRo // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - const handleSearchAction = useCallback((value: string) => { - if (!isAutocompleteListVisible) { - return; - } - handleSearch(value); - // eslint-disable-next-line react-compiler/react-compiler - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isAutocompleteListVisible]); + const handleSearchAction = useCallback( + (value: string) => { + if (!isAutocompleteListVisible) { + return; + } + handleSearch(value); + }, + [isAutocompleteListVisible], + ); const onSearchQueryChange = useCallback( (userQuery: string) => { diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx index a4c92f07d268..7604ccdc88ea 100644 --- a/src/components/Search/index.tsx +++ b/src/components/Search/index.tsx @@ -47,14 +47,7 @@ import type SearchResults from '@src/types/onyx/SearchResults'; import {useSearchContext} from './SearchContext'; import SearchList from './SearchList'; import SearchScopeProvider from './SearchScopeProvider'; -import type { - SearchColumnType, - SearchParams, - SearchQueryJSON, - SelectedTransactionInfo, - SelectedTransactions, - SortOrder, -} from './types'; +import type {SearchColumnType, SearchParams, SearchQueryJSON, SelectedTransactionInfo, SelectedTransactions, SortOrder} from './types'; type SearchProps = { queryJSON: SearchQueryJSON; diff --git a/src/components/Search/types.ts b/src/components/Search/types.ts index 38f4d125bea2..03ed49c821b1 100644 --- a/src/components/Search/types.ts +++ b/src/components/Search/types.ts @@ -152,7 +152,7 @@ type SearchAutocompleteQueryRange = { type SearchParams = { queryJSON: SearchQueryJSON; offset: number; -} +}; export type { SelectedTransactionInfo, diff --git a/src/pages/Search/SearchPage.tsx b/src/pages/Search/SearchPage.tsx index bbec411cb340..aa65435d2605 100644 --- a/src/pages/Search/SearchPage.tsx +++ b/src/pages/Search/SearchPage.tsx @@ -26,6 +26,7 @@ import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import {turnOffMobileSelectionMode} from '@libs/actions/MobileSelectionMode'; +import {searchInServer} from '@libs/actions/Report'; import { approveMoneyRequestOnSearch, deleteMoneyRequestOnSearch, @@ -48,7 +49,6 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import type {SearchResults} from '@src/types/onyx'; -import { searchInServer } from '@libs/actions/Report'; import SearchPageNarrow from './SearchPageNarrow'; import SearchTypeMenu from './SearchTypeMenu'; From 712ca22edce2057a23b340e5fa1752ac20405833 Mon Sep 17 00:00:00 2001 From: "marta.sudol" Date: Thu, 24 Apr 2025 13:42:59 +0200 Subject: [PATCH 05/26] fix: avoid duplicated /SearchForReport calls --- .../Search/SearchPageHeader/SearchPageHeaderInput.tsx | 2 +- src/components/Search/index.tsx | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/Search/SearchPageHeader/SearchPageHeaderInput.tsx b/src/components/Search/SearchPageHeader/SearchPageHeaderInput.tsx index 3753892a3df7..8fc66dd684ba 100644 --- a/src/components/Search/SearchPageHeader/SearchPageHeaderInput.tsx +++ b/src/components/Search/SearchPageHeader/SearchPageHeaderInput.tsx @@ -151,7 +151,7 @@ function SearchPageHeaderInput({queryJSON, searchRouterListVisible, hideSearchRo } handleSearch(value); }, - [isAutocompleteListVisible], + [handleSearch, isAutocompleteListVisible], ); const onSearchQueryChange = useCallback( diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx index 7604ccdc88ea..c94d61d34b1d 100644 --- a/src/components/Search/index.tsx +++ b/src/components/Search/index.tsx @@ -55,7 +55,7 @@ type SearchProps = { contentContainerStyle?: StyleProp; currentSearchResults?: SearchResults; lastNonEmptySearchResults?: SearchResults; - handleSearch: (value: SearchParams) => void; + handleSearch?: (value: SearchParams) => void; }; function mapTransactionItemToSelectedEntry(item: TransactionListItemType): [string, SelectedTransactionInfo] { @@ -198,6 +198,10 @@ function Search({queryJSON, currentSearchResults, lastNonEmptySearchResults, onS return; } + if (!handleSearch) { + return; + } + handleSearch({queryJSON, offset}); }, [handleSearch, isOffline, offset, queryJSON]); From fdf27e4c9f3799d3b485b9f3013f232484735656 Mon Sep 17 00:00:00 2001 From: "marta.sudol" Date: Thu, 24 Apr 2025 13:50:34 +0200 Subject: [PATCH 06/26] fix: avoid duplicated /SearchForReport calls --- src/components/Search/types.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/Search/types.ts b/src/components/Search/types.ts index 03ed49c821b1..f765c0ba122e 100644 --- a/src/components/Search/types.ts +++ b/src/components/Search/types.ts @@ -78,8 +78,6 @@ type SearchContext = { isExportMode: boolean; setExportMode: (on: boolean) => void; isOnSearch: boolean; - isPredefinedFilterSelected: boolean; - setIsPredefinedFilterSelected: (isPredefinedFilterSelected: boolean) => void; }; type ASTNode = { From 214c235314eb61ca5545af82dc7ce27623d49fe0 Mon Sep 17 00:00:00 2001 From: "marta.sudol" Date: Thu, 24 Apr 2025 13:54:26 +0200 Subject: [PATCH 07/26] fix: avoid duplicated /SearchForReport calls --- src/components/Search/SearchPageHeader/SearchPageHeader.tsx | 2 +- .../Search/SearchPageHeader/SearchPageHeaderInput.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/Search/SearchPageHeader/SearchPageHeader.tsx b/src/components/Search/SearchPageHeader/SearchPageHeader.tsx index 62244fd382c2..de3133bdbe18 100644 --- a/src/components/Search/SearchPageHeader/SearchPageHeader.tsx +++ b/src/components/Search/SearchPageHeader/SearchPageHeader.tsx @@ -28,7 +28,7 @@ type SearchPageHeaderProps = { hideSearchRouterList?: () => void; onSearchRouterFocus?: () => void; headerButtonsOptions: Array>; - handleSearch: (value: string) => void; + handleSearch?: (value: string) => void; }; type SearchHeaderOptionValue = DeepValueOf | undefined; diff --git a/src/components/Search/SearchPageHeader/SearchPageHeaderInput.tsx b/src/components/Search/SearchPageHeader/SearchPageHeaderInput.tsx index 8fc66dd684ba..d99d789888c7 100644 --- a/src/components/Search/SearchPageHeader/SearchPageHeaderInput.tsx +++ b/src/components/Search/SearchPageHeader/SearchPageHeaderInput.tsx @@ -59,7 +59,7 @@ type SearchPageHeaderInputProps = { onSearchRouterFocus?: () => void; searchName?: string; inputRightComponent: React.ReactNode; - handleSearch: (value: string) => void; + handleSearch?: (value: string) => void; }; function SearchPageHeaderInput({queryJSON, searchRouterListVisible, hideSearchRouterList, onSearchRouterFocus, searchName, inputRightComponent, handleSearch}: SearchPageHeaderInputProps) { @@ -146,7 +146,7 @@ function SearchPageHeaderInput({queryJSON, searchRouterListVisible, hideSearchRo const handleSearchAction = useCallback( (value: string) => { - if (!isAutocompleteListVisible) { + if (!isAutocompleteListVisible || !handleSearch) { return; } handleSearch(value); From c2849bb60557a52a6b7d7bca10d102fd21d9c3b6 Mon Sep 17 00:00:00 2001 From: "marta.sudol" Date: Fri, 25 Apr 2025 08:33:30 +0200 Subject: [PATCH 08/26] fix: avoid duplicated /SearchForReport calls --- src/components/Search/SearchAutocompleteList.tsx | 4 +--- .../Search/SearchPageHeader/SearchPageHeaderInput.tsx | 1 + src/components/Search/SearchRouter/SearchRouter.tsx | 6 +----- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/src/components/Search/SearchAutocompleteList.tsx b/src/components/Search/SearchAutocompleteList.tsx index 3aa954a577d5..d47c52141959 100644 --- a/src/components/Search/SearchAutocompleteList.tsx +++ b/src/components/Search/SearchAutocompleteList.tsx @@ -508,9 +508,7 @@ function SearchAutocompleteList( return; } handleSearch(autocompleteQueryValue); - // eslint-disable-next-line react-compiler/react-compiler - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [autocompleteQueryValue]); + }, [autocompleteQueryValue, handleSearch]); /* Sections generation */ const sections: Array> = []; diff --git a/src/components/Search/SearchPageHeader/SearchPageHeaderInput.tsx b/src/components/Search/SearchPageHeader/SearchPageHeaderInput.tsx index d99d789888c7..fc94fb5d6166 100644 --- a/src/components/Search/SearchPageHeader/SearchPageHeaderInput.tsx +++ b/src/components/Search/SearchPageHeader/SearchPageHeaderInput.tsx @@ -119,6 +119,7 @@ function SearchPageHeaderInput({queryJSON, searchRouterListVisible, hideSearchRo useEffect(() => { setTextInputValue(isDefaultQuery ? '' : queryText); + setAutocompleteQueryValue(isDefaultQuery ? '' : queryText); }, [isDefaultQuery, queryText]); useEffect(() => { diff --git a/src/components/Search/SearchRouter/SearchRouter.tsx b/src/components/Search/SearchRouter/SearchRouter.tsx index 8c8d9c9c2528..f225414ce7ca 100644 --- a/src/components/Search/SearchRouter/SearchRouter.tsx +++ b/src/components/Search/SearchRouter/SearchRouter.tsx @@ -239,10 +239,6 @@ function SearchRouter({onRouterClose, shouldHideInputCaret, isSearchRouterDispla [setSelection, setTextInputValue], ); - const handleSearchAction = (value: string) => { - searchInServer(value); - }; - const onListItemPress = useCallback( (item: OptionData | SearchQueryItem) => { if (isSearchQueryItem(item)) { @@ -351,7 +347,7 @@ function SearchRouter({onRouterClose, shouldHideInputCaret, isSearchRouterDispla /> Date: Mon, 28 Apr 2025 09:44:55 +0200 Subject: [PATCH 09/26] Fix bug with /SearchForReports calls on input focus --- .../Search/SearchAutocompleteList.tsx | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/src/components/Search/SearchAutocompleteList.tsx b/src/components/Search/SearchAutocompleteList.tsx index d47c52141959..a973b5c1f647 100644 --- a/src/components/Search/SearchAutocompleteList.tsx +++ b/src/components/Search/SearchAutocompleteList.tsx @@ -1,5 +1,5 @@ import {Str} from 'expensify-common'; -import React, {forwardRef, useCallback, useEffect, useMemo, useState} from 'react'; +import React, {forwardRef, useCallback, useEffect, useMemo, useRef, useState} from 'react'; import type {ForwardedRef} from 'react'; import {useOnyx} from 'react-native-onyx'; import * as Expensicons from '@components/Icon/Expensicons'; @@ -503,12 +503,28 @@ function SearchAutocompleteList( return reportOptions.slice(0, 20); }, [autocompleteQueryValue, filterOptions, searchOptions]); - useEffect(() => { - if (!handleSearch) { - return; - } + // Store the initial query value +const initialRenderRef = useRef(true); +const prevQueryRef = useRef(autocompleteQueryValue); + +useEffect(() => { + if (!handleSearch) { + return; + } + + // If this is the first render, just store the query but don't call the API + if (initialRenderRef.current) { + initialRenderRef.current = false; + prevQueryRef.current = autocompleteQueryValue; + return; + } + + // Only call the API if the query has changed from the previous value + if (prevQueryRef.current !== autocompleteQueryValue) { + prevQueryRef.current = autocompleteQueryValue; handleSearch(autocompleteQueryValue); - }, [autocompleteQueryValue, handleSearch]); + } +}, [autocompleteQueryValue, handleSearch]); /* Sections generation */ const sections: Array> = []; From a0c27f6a66fd563261764064b3fb1d5f27885539 Mon Sep 17 00:00:00 2001 From: "marta.sudol" Date: Mon, 28 Apr 2025 09:51:34 +0200 Subject: [PATCH 10/26] Moved Search feature documentation to contributingGuides --- .../features/Search.md | 0 docs/_data/_routes.yml | 5 ----- 2 files changed, 5 deletions(-) rename docs/articles/new-expensify/search/Search-overview.md => contributingGuides/features/Search.md (100%) diff --git a/docs/articles/new-expensify/search/Search-overview.md b/contributingGuides/features/Search.md similarity index 100% rename from docs/articles/new-expensify/search/Search-overview.md rename to contributingGuides/features/Search.md diff --git a/docs/_data/_routes.yml b/docs/_data/_routes.yml index 905b69875cab..8cf9cdc8891d 100644 --- a/docs/_data/_routes.yml +++ b/docs/_data/_routes.yml @@ -153,8 +153,3 @@ platforms: title: Expensify Billing & Subscriptions icon: /assets/images/subscription-annual.svg description: Review Expensify's subscription options, plan types, and payment methods. - - - href: search-overview - title: Search Functionality in New Expensify - icon: /assets/images/magnifying-glass.svg - description: Search feature overview. From 69fe49061608a200dfa9fae67851b4504bf307aa Mon Sep 17 00:00:00 2001 From: "marta.sudol" Date: Mon, 28 Apr 2025 09:55:45 +0200 Subject: [PATCH 11/26] Revert docs/_data_.routes.yml to match main --- docs/_data/_routes.yml | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/docs/_data/_routes.yml b/docs/_data/_routes.yml index 8cf9cdc8891d..7a555c4314f5 100644 --- a/docs/_data/_routes.yml +++ b/docs/_data/_routes.yml @@ -11,7 +11,7 @@ platforms: url: www.expensify.com description: "Your account settings look like this:" image: /assets/images/settings-old-dot.svg - + # Hubs are comprised of subcategories and articles. Subcategories contain multiple related articles, but there can be standalone articles as well hubs: - href: getting-started @@ -38,7 +38,7 @@ platforms: title: Domains icon: /assets/images/domains.svg description: Claim and verify your company’s domain to access additional management and security features. - + - href: bank-accounts-and-payments title: Bank Accounts & Payments icon: /assets/images/send-money.svg @@ -88,8 +88,8 @@ platforms: title: Travel icon: /assets/images/plane.svg description: Manage all your corporate travel needs with Expensify Travel. - - + + - href: new-expensify title: New Expensify hub-title: New Expensify - Help & Resources @@ -97,7 +97,7 @@ platforms: url: new.expensify.com description: "Your account settings look like this:" image: /assets/images/settings-new-dot.svg - + hubs: - href: getting-started title: Getting Started @@ -113,7 +113,7 @@ platforms: title: Workspaces icon: /assets/images/shield.svg description: Configure rules, settings, and limits for your company’s spending. - + - href: reports-and-expenses title: Reports & Expenses icon: /assets/images/envelope-receipt.svg @@ -128,12 +128,12 @@ platforms: title: Connect Credit Cards icon: /assets/images/bank-card.svg description: Track credit card transactions and reconcile company cards. - + - href: expensify-card title: Expensify Card icon: /assets/images/hand-card.svg description: Explore the perks and benefits of the Expensify Card. - + - href: travel title: Travel icon: /assets/images/plane.svg @@ -148,8 +148,9 @@ platforms: title: Settings icon: /assets/images/gears.svg description: Manage profile settings and notifications. - + - href: billing-and-subscriptions title: Expensify Billing & Subscriptions icon: /assets/images/subscription-annual.svg description: Review Expensify's subscription options, plan types, and payment methods. + From fda4689ca96044a31ea86653d3f2557b7d4a7cfc Mon Sep 17 00:00:00 2001 From: "marta.sudol" Date: Mon, 28 Apr 2025 10:07:21 +0200 Subject: [PATCH 12/26] Fix prettier issues --- .../Search/SearchAutocompleteList.tsx | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/components/Search/SearchAutocompleteList.tsx b/src/components/Search/SearchAutocompleteList.tsx index a973b5c1f647..999fdfe6c51f 100644 --- a/src/components/Search/SearchAutocompleteList.tsx +++ b/src/components/Search/SearchAutocompleteList.tsx @@ -504,27 +504,27 @@ function SearchAutocompleteList( }, [autocompleteQueryValue, filterOptions, searchOptions]); // Store the initial query value -const initialRenderRef = useRef(true); -const prevQueryRef = useRef(autocompleteQueryValue); + const initialRenderRef = useRef(true); + const prevQueryRef = useRef(autocompleteQueryValue); -useEffect(() => { - if (!handleSearch) { - return; - } + useEffect(() => { + if (!handleSearch) { + return; + } - // If this is the first render, just store the query but don't call the API - if (initialRenderRef.current) { - initialRenderRef.current = false; - prevQueryRef.current = autocompleteQueryValue; - return; - } + // If this is the first render, just store the query but don't call the API + if (initialRenderRef.current) { + initialRenderRef.current = false; + prevQueryRef.current = autocompleteQueryValue; + return; + } - // Only call the API if the query has changed from the previous value - if (prevQueryRef.current !== autocompleteQueryValue) { - prevQueryRef.current = autocompleteQueryValue; - handleSearch(autocompleteQueryValue); - } -}, [autocompleteQueryValue, handleSearch]); + // Only call the API if the query has changed from the previous value + if (prevQueryRef.current !== autocompleteQueryValue) { + prevQueryRef.current = autocompleteQueryValue; + handleSearch(autocompleteQueryValue); + } + }, [autocompleteQueryValue, handleSearch]); /* Sections generation */ const sections: Array> = []; From b4a006c678afac5ca994507ff164d56aef8448de Mon Sep 17 00:00:00 2001 From: "marta.sudol" Date: Mon, 28 Apr 2025 12:22:38 +0200 Subject: [PATCH 13/26] Fix tests --- src/components/Search/SearchAutocompleteList.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/components/Search/SearchAutocompleteList.tsx b/src/components/Search/SearchAutocompleteList.tsx index 999fdfe6c51f..cbf8512bd22d 100644 --- a/src/components/Search/SearchAutocompleteList.tsx +++ b/src/components/Search/SearchAutocompleteList.tsx @@ -503,7 +503,7 @@ function SearchAutocompleteList( return reportOptions.slice(0, 20); }, [autocompleteQueryValue, filterOptions, searchOptions]); - // Store the initial query value + // Store references for tracking input changes const initialRenderRef = useRef(true); const prevQueryRef = useRef(autocompleteQueryValue); @@ -512,14 +512,15 @@ function SearchAutocompleteList( return; } - // If this is the first render, just store the query but don't call the API + // For the first render, store the query and call the API if (initialRenderRef.current) { initialRenderRef.current = false; prevQueryRef.current = autocompleteQueryValue; + handleSearch(autocompleteQueryValue); return; } - - // Only call the API if the query has changed from the previous value + + // For subsequent renders, only call the API if the query has changed if (prevQueryRef.current !== autocompleteQueryValue) { prevQueryRef.current = autocompleteQueryValue; handleSearch(autocompleteQueryValue); From 65a1c8dda160733e69a07d58f2d415e71b62e2e3 Mon Sep 17 00:00:00 2001 From: "marta.sudol" Date: Mon, 28 Apr 2025 12:26:53 +0200 Subject: [PATCH 14/26] Fix prettier --- src/components/Search/SearchAutocompleteList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Search/SearchAutocompleteList.tsx b/src/components/Search/SearchAutocompleteList.tsx index cbf8512bd22d..59289dcf0697 100644 --- a/src/components/Search/SearchAutocompleteList.tsx +++ b/src/components/Search/SearchAutocompleteList.tsx @@ -519,7 +519,7 @@ function SearchAutocompleteList( handleSearch(autocompleteQueryValue); return; } - + // For subsequent renders, only call the API if the query has changed if (prevQueryRef.current !== autocompleteQueryValue) { prevQueryRef.current = autocompleteQueryValue; From 26ad8a443eff68a69ff73170662459f64665bad9 Mon Sep 17 00:00:00 2001 From: "marta.sudol" Date: Mon, 28 Apr 2025 13:53:04 +0200 Subject: [PATCH 15/26] Fix prettier --- .../Search/SearchAutocompleteList.tsx | 22 +++++-------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/src/components/Search/SearchAutocompleteList.tsx b/src/components/Search/SearchAutocompleteList.tsx index 59289dcf0697..dcd13b1e4d4d 100644 --- a/src/components/Search/SearchAutocompleteList.tsx +++ b/src/components/Search/SearchAutocompleteList.tsx @@ -503,28 +503,18 @@ function SearchAutocompleteList( return reportOptions.slice(0, 20); }, [autocompleteQueryValue, filterOptions, searchOptions]); - // Store references for tracking input changes - const initialRenderRef = useRef(true); + // Only call search when input value changes, not on focus const prevQueryRef = useRef(autocompleteQueryValue); useEffect(() => { - if (!handleSearch) { + // Only proceed if handleSearch exists and query has changed + if (!handleSearch || prevQueryRef.current === autocompleteQueryValue) { return; } - // For the first render, store the query and call the API - if (initialRenderRef.current) { - initialRenderRef.current = false; - prevQueryRef.current = autocompleteQueryValue; - handleSearch(autocompleteQueryValue); - return; - } - - // For subsequent renders, only call the API if the query has changed - if (prevQueryRef.current !== autocompleteQueryValue) { - prevQueryRef.current = autocompleteQueryValue; - handleSearch(autocompleteQueryValue); - } + // Update reference and call search API + prevQueryRef.current = autocompleteQueryValue; + handleSearch(autocompleteQueryValue); }, [autocompleteQueryValue, handleSearch]); /* Sections generation */ From 3c974692b42f57a894eca8061748986224e78c46 Mon Sep 17 00:00:00 2001 From: "marta.sudol" Date: Tue, 29 Apr 2025 15:01:38 +0200 Subject: [PATCH 16/26] Use getQueryWithoutFilters to rely on query comparison to handle search action. --- .../Search/SearchAutocompleteList.tsx | 22 +++++++++++++++---- .../SearchPageHeaderInput.tsx | 14 ++++++++++-- src/libs/SearchQueryUtils.ts | 21 ++++++++++++++++++ 3 files changed, 51 insertions(+), 6 deletions(-) diff --git a/src/components/Search/SearchAutocompleteList.tsx b/src/components/Search/SearchAutocompleteList.tsx index dcd13b1e4d4d..cee77e66efe3 100644 --- a/src/components/Search/SearchAutocompleteList.tsx +++ b/src/components/Search/SearchAutocompleteList.tsx @@ -34,7 +34,13 @@ import { getQueryWithoutAutocompletedPart, parseForAutocomplete, } from '@libs/SearchAutocompleteUtils'; -import {buildSearchQueryJSON, buildUserReadableQueryString, sanitizeSearchValue, shouldHighlight} from '@libs/SearchQueryUtils'; +import { + buildSearchQueryJSON, + buildUserReadableQueryString, + getQueryWithoutFilters, + sanitizeSearchValue, + shouldHighlight, +} from '@libs/SearchQueryUtils'; import StringUtils from '@libs/StringUtils'; import Timing from '@userActions/Timing'; import CONST from '@src/CONST'; @@ -503,12 +509,20 @@ function SearchAutocompleteList( return reportOptions.slice(0, 20); }, [autocompleteQueryValue, filterOptions, searchOptions]); - // Only call search when input value changes, not on focus + // Only call search when the query without filters has changed const prevQueryRef = useRef(autocompleteQueryValue); useEffect(() => { - // Only proceed if handleSearch exists and query has changed - if (!handleSearch || prevQueryRef.current === autocompleteQueryValue) { + // Only proceed if handleSearch exists + if (!handleSearch) { + return; + } + + const currentQueryWithoutFilters = getQueryWithoutFilters(autocompleteQueryValue); + const prevQueryWithoutFilters = getQueryWithoutFilters(prevQueryRef.current); + + // Skip the search if the query (without filters) hasn't changed + if (prevQueryRef.current && currentQueryWithoutFilters === prevQueryWithoutFilters) { return; } diff --git a/src/components/Search/SearchPageHeader/SearchPageHeaderInput.tsx b/src/components/Search/SearchPageHeader/SearchPageHeaderInput.tsx index fc94fb5d6166..6615ab17e5ce 100644 --- a/src/components/Search/SearchPageHeader/SearchPageHeaderInput.tsx +++ b/src/components/Search/SearchPageHeader/SearchPageHeaderInput.tsx @@ -35,6 +35,7 @@ import {getAutocompleteQueryWithComma, getQueryWithoutAutocompletedPart} from '@ import { buildUserReadableQueryString, buildUserReadableQueryStringWithPolicyID, + getQueryWithoutFilters, getQueryWithUpdatedValues, getQueryWithUpdatedValuesWithoutPolicy, isDefaultExpensesQuery, @@ -145,14 +146,23 @@ function SearchPageHeaderInput({queryJSON, searchRouterListVisible, hideSearchRo // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + const prevQueryRef = useRef(''); + const handleSearchAction = useCallback( (value: string) => { - if (!isAutocompleteListVisible || !handleSearch) { + const queryWithoutFilters = getQueryWithoutFilters(value); + const prevQueryWithoutFilters = getQueryWithoutFilters(prevQueryRef.current); + + // Only call handleSearch if the query (without filters) has changed or if there's no previous query + if (!handleSearch || (prevQueryRef.current && queryWithoutFilters === prevQueryWithoutFilters)) { return; } + + // Update the reference to the current query + prevQueryRef.current = value; handleSearch(value); }, - [handleSearch, isAutocompleteListVisible], + [handleSearch], ); const onSearchQueryChange = useCallback( diff --git a/src/libs/SearchQueryUtils.ts b/src/libs/SearchQueryUtils.ts index 8028b122ede7..015bb15bac45 100644 --- a/src/libs/SearchQueryUtils.ts +++ b/src/libs/SearchQueryUtils.ts @@ -25,6 +25,7 @@ import {getReportName} from './ReportUtils'; import {parse as parseSearchQuery} from './SearchParser/searchParser'; import {hashText} from './UserUtils'; import {isValidDate} from './ValidationUtils'; +import {parseForAutocomplete} from '@libs/SearchAutocompleteUtils'; type FilterKeys = keyof typeof CONST.SEARCH.SYNTAX_FILTER_KEYS; @@ -954,6 +955,25 @@ function getCurrentSearchQueryJSON() { return queryJSON; } +/** + * Extracts the query text without the filter parts. + * This is used to determine if a user's core search terms have changed, + * ignoring any filter modifications. + * + * @param searchQuery - The complete search query string + * @returns The query without filters (core search terms only) + */ +function getQueryWithoutFilters(searchQuery: string) { + const parsedQuery = parseForAutocomplete(searchQuery); + const lastFilter = parsedQuery?.ranges.at(-1); + + if (!lastFilter) { + return searchQuery.trim(); + } + + return searchQuery.slice(lastFilter.start + lastFilter.length).trim(); +} + /** * Converts a filter key from old naming (camelCase) to user friendly naming (kebab-case). * @@ -1000,6 +1020,7 @@ export { getQueryWithUpdatedValues, getQueryWithUpdatedValuesWithoutPolicy, getCurrentSearchQueryJSON, + getQueryWithoutFilters, getUserFriendlyKey, isDefaultExpensesQuery, shouldHighlight, From cc05bde49824c1022e97546a90b5166b2c0e3275 Mon Sep 17 00:00:00 2001 From: "marta.sudol" Date: Tue, 29 Apr 2025 15:07:15 +0200 Subject: [PATCH 17/26] prettier & eslint fixes --- src/components/Search/SearchAutocompleteList.tsx | 8 +------- src/libs/SearchQueryUtils.ts | 2 +- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/components/Search/SearchAutocompleteList.tsx b/src/components/Search/SearchAutocompleteList.tsx index cee77e66efe3..2b7b8226d97c 100644 --- a/src/components/Search/SearchAutocompleteList.tsx +++ b/src/components/Search/SearchAutocompleteList.tsx @@ -34,13 +34,7 @@ import { getQueryWithoutAutocompletedPart, parseForAutocomplete, } from '@libs/SearchAutocompleteUtils'; -import { - buildSearchQueryJSON, - buildUserReadableQueryString, - getQueryWithoutFilters, - sanitizeSearchValue, - shouldHighlight, -} from '@libs/SearchQueryUtils'; +import {buildSearchQueryJSON, buildUserReadableQueryString, getQueryWithoutFilters, sanitizeSearchValue, shouldHighlight} from '@libs/SearchQueryUtils'; import StringUtils from '@libs/StringUtils'; import Timing from '@userActions/Timing'; import CONST from '@src/CONST'; diff --git a/src/libs/SearchQueryUtils.ts b/src/libs/SearchQueryUtils.ts index 015bb15bac45..44652d816dd8 100644 --- a/src/libs/SearchQueryUtils.ts +++ b/src/libs/SearchQueryUtils.ts @@ -2,6 +2,7 @@ import cloneDeep from 'lodash/cloneDeep'; import type {OnyxCollection} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import type {ASTNode, QueryFilter, QueryFilters, SearchDateFilterKeys, SearchFilterKey, SearchQueryJSON, SearchQueryString, SearchStatus, UserFriendlyKey} from '@components/Search/types'; +import {parseForAutocomplete} from '@libs/SearchAutocompleteUtils'; import CONST from '@src/CONST'; import NAVIGATORS from '@src/NAVIGATORS'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -25,7 +26,6 @@ import {getReportName} from './ReportUtils'; import {parse as parseSearchQuery} from './SearchParser/searchParser'; import {hashText} from './UserUtils'; import {isValidDate} from './ValidationUtils'; -import {parseForAutocomplete} from '@libs/SearchAutocompleteUtils'; type FilterKeys = keyof typeof CONST.SEARCH.SYNTAX_FILTER_KEYS; From a75af9c0f022b1c4cb18ecc40cf6fc3970f02ee6 Mon Sep 17 00:00:00 2001 From: "marta.sudol" Date: Tue, 29 Apr 2025 15:12:21 +0200 Subject: [PATCH 18/26] prettier & eslint fixes --- src/libs/SearchQueryUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/SearchQueryUtils.ts b/src/libs/SearchQueryUtils.ts index 44652d816dd8..c3c1b861b207 100644 --- a/src/libs/SearchQueryUtils.ts +++ b/src/libs/SearchQueryUtils.ts @@ -2,7 +2,6 @@ import cloneDeep from 'lodash/cloneDeep'; import type {OnyxCollection} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import type {ASTNode, QueryFilter, QueryFilters, SearchDateFilterKeys, SearchFilterKey, SearchQueryJSON, SearchQueryString, SearchStatus, UserFriendlyKey} from '@components/Search/types'; -import {parseForAutocomplete} from '@libs/SearchAutocompleteUtils'; import CONST from '@src/CONST'; import NAVIGATORS from '@src/NAVIGATORS'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -11,6 +10,7 @@ import type {SearchAdvancedFiltersForm} from '@src/types/form'; import FILTER_KEYS, {DATE_FILTER_KEYS} from '@src/types/form/SearchAdvancedFiltersForm'; import type * as OnyxTypes from '@src/types/onyx'; import type {SearchDataTypes} from '@src/types/onyx/SearchResults'; +import {parseForAutocomplete} from './SearchAutocompleteUtils'; import type {CardFeedNamesWithType} from './CardFeedUtils'; import {getWorkspaceCardFeedKey} from './CardFeedUtils'; import {getCardDescription} from './CardUtils'; From 2d8922926dd3b19a24733cec68b4597fa8889691 Mon Sep 17 00:00:00 2001 From: "marta.sudol" Date: Tue, 29 Apr 2025 15:14:48 +0200 Subject: [PATCH 19/26] prettier & eslint fixes --- src/libs/SearchQueryUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/SearchQueryUtils.ts b/src/libs/SearchQueryUtils.ts index c3c1b861b207..afc1c3055097 100644 --- a/src/libs/SearchQueryUtils.ts +++ b/src/libs/SearchQueryUtils.ts @@ -10,7 +10,6 @@ import type {SearchAdvancedFiltersForm} from '@src/types/form'; import FILTER_KEYS, {DATE_FILTER_KEYS} from '@src/types/form/SearchAdvancedFiltersForm'; import type * as OnyxTypes from '@src/types/onyx'; import type {SearchDataTypes} from '@src/types/onyx/SearchResults'; -import {parseForAutocomplete} from './SearchAutocompleteUtils'; import type {CardFeedNamesWithType} from './CardFeedUtils'; import {getWorkspaceCardFeedKey} from './CardFeedUtils'; import {getCardDescription} from './CardUtils'; @@ -23,6 +22,7 @@ import type {SearchFullscreenNavigatorParamList} from './Navigation/types'; import {getPersonalDetailByEmail} from './PersonalDetailsUtils'; import {getCleanedTagName, getTagNamesFromTagsLists} from './PolicyUtils'; import {getReportName} from './ReportUtils'; +import {parseForAutocomplete} from './SearchAutocompleteUtils'; import {parse as parseSearchQuery} from './SearchParser/searchParser'; import {hashText} from './UserUtils'; import {isValidDate} from './ValidationUtils'; From 9420b43065df624eec2e2a66bec860be14daffc8 Mon Sep 17 00:00:00 2001 From: "marta.sudol" Date: Wed, 30 Apr 2025 09:26:59 +0200 Subject: [PATCH 20/26] Refactoring --- .../Search/SearchAutocompleteList.tsx | 33 +++++++------------ .../SearchPageHeaderInput.tsx | 12 +------ 2 files changed, 13 insertions(+), 32 deletions(-) diff --git a/src/components/Search/SearchAutocompleteList.tsx b/src/components/Search/SearchAutocompleteList.tsx index 2b7b8226d97c..aeb86b4a0856 100644 --- a/src/components/Search/SearchAutocompleteList.tsx +++ b/src/components/Search/SearchAutocompleteList.tsx @@ -1,5 +1,5 @@ import {Str} from 'expensify-common'; -import React, {forwardRef, useCallback, useEffect, useMemo, useRef, useState} from 'react'; +import React, {forwardRef, useCallback, useEffect, useMemo, useState} from 'react'; import type {ForwardedRef} from 'react'; import {useOnyx} from 'react-native-onyx'; import * as Expensicons from '@components/Icon/Expensicons'; @@ -31,8 +31,7 @@ import { getAutocompleteRecentTags, getAutocompleteTags, getAutocompleteTaxList, - getQueryWithoutAutocompletedPart, - parseForAutocomplete, + getQueryWithoutAutocompletedPart, parseForAutocomplete, } from '@libs/SearchAutocompleteUtils'; import {buildSearchQueryJSON, buildUserReadableQueryString, getQueryWithoutFilters, sanitizeSearchValue, shouldHighlight} from '@libs/SearchQueryUtils'; import StringUtils from '@libs/StringUtils'; @@ -245,8 +244,14 @@ function SearchAutocompleteList( }, [activeWorkspaceID, allPoliciesTags]); const recentTagsAutocompleteList = getAutocompleteRecentTags(allRecentTags, activeWorkspaceID); + const [autocompleteParsedQuery, autocompleteQueryWithoutFilters] = useMemo(() => { + const parsedQuery = parseForAutocomplete(autocompleteQueryValue); + const queryWithoutFilters = getQueryWithoutFilters(autocompleteQueryValue); + return [parsedQuery, queryWithoutFilters]; + }, [autocompleteQueryValue]); + + const autocompleteSuggestions = useMemo(() => { - const autocompleteParsedQuery = parseForAutocomplete(autocompleteQueryValue); const {autocomplete, ranges = []} = autocompleteParsedQuery ?? {}; const autocompleteKey = autocomplete?.key; const autocompleteValue = autocomplete?.value ?? ''; @@ -440,7 +445,7 @@ function SearchAutocompleteList( } } }, [ - autocompleteQueryValue, + autocompleteParsedQuery, tagAutocompleteList, recentTagsAutocompleteList, categoryAutocompleteList, @@ -503,27 +508,13 @@ function SearchAutocompleteList( return reportOptions.slice(0, 20); }, [autocompleteQueryValue, filterOptions, searchOptions]); - // Only call search when the query without filters has changed - const prevQueryRef = useRef(autocompleteQueryValue); - useEffect(() => { - // Only proceed if handleSearch exists if (!handleSearch) { return; } - const currentQueryWithoutFilters = getQueryWithoutFilters(autocompleteQueryValue); - const prevQueryWithoutFilters = getQueryWithoutFilters(prevQueryRef.current); - - // Skip the search if the query (without filters) hasn't changed - if (prevQueryRef.current && currentQueryWithoutFilters === prevQueryWithoutFilters) { - return; - } - - // Update reference and call search API - prevQueryRef.current = autocompleteQueryValue; - handleSearch(autocompleteQueryValue); - }, [autocompleteQueryValue, handleSearch]); + handleSearch(autocompleteQueryWithoutFilters); + }, [autocompleteQueryWithoutFilters, handleSearch]); /* Sections generation */ const sections: Array> = []; diff --git a/src/components/Search/SearchPageHeader/SearchPageHeaderInput.tsx b/src/components/Search/SearchPageHeader/SearchPageHeaderInput.tsx index 6615ab17e5ce..87749501e2b4 100644 --- a/src/components/Search/SearchPageHeader/SearchPageHeaderInput.tsx +++ b/src/components/Search/SearchPageHeader/SearchPageHeaderInput.tsx @@ -35,7 +35,6 @@ import {getAutocompleteQueryWithComma, getQueryWithoutAutocompletedPart} from '@ import { buildUserReadableQueryString, buildUserReadableQueryStringWithPolicyID, - getQueryWithoutFilters, getQueryWithUpdatedValues, getQueryWithUpdatedValuesWithoutPolicy, isDefaultExpensesQuery, @@ -146,20 +145,11 @@ function SearchPageHeaderInput({queryJSON, searchRouterListVisible, hideSearchRo // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - const prevQueryRef = useRef(''); - const handleSearchAction = useCallback( (value: string) => { - const queryWithoutFilters = getQueryWithoutFilters(value); - const prevQueryWithoutFilters = getQueryWithoutFilters(prevQueryRef.current); - - // Only call handleSearch if the query (without filters) has changed or if there's no previous query - if (!handleSearch || (prevQueryRef.current && queryWithoutFilters === prevQueryWithoutFilters)) { + if (!handleSearch) { return; } - - // Update the reference to the current query - prevQueryRef.current = value; handleSearch(value); }, [handleSearch], From 43c5bb1fdeb5f1167a967c0f1d2d7206fe014bb6 Mon Sep 17 00:00:00 2001 From: "marta.sudol" Date: Wed, 30 Apr 2025 09:30:02 +0200 Subject: [PATCH 21/26] Prettier --- src/components/Search/SearchAutocompleteList.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Search/SearchAutocompleteList.tsx b/src/components/Search/SearchAutocompleteList.tsx index aeb86b4a0856..dd81318f1de0 100644 --- a/src/components/Search/SearchAutocompleteList.tsx +++ b/src/components/Search/SearchAutocompleteList.tsx @@ -31,7 +31,8 @@ import { getAutocompleteRecentTags, getAutocompleteTags, getAutocompleteTaxList, - getQueryWithoutAutocompletedPart, parseForAutocomplete, + getQueryWithoutAutocompletedPart, + parseForAutocomplete, } from '@libs/SearchAutocompleteUtils'; import {buildSearchQueryJSON, buildUserReadableQueryString, getQueryWithoutFilters, sanitizeSearchValue, shouldHighlight} from '@libs/SearchQueryUtils'; import StringUtils from '@libs/StringUtils'; @@ -250,7 +251,6 @@ function SearchAutocompleteList( return [parsedQuery, queryWithoutFilters]; }, [autocompleteQueryValue]); - const autocompleteSuggestions = useMemo(() => { const {autocomplete, ranges = []} = autocompleteParsedQuery ?? {}; const autocompleteKey = autocomplete?.key; From 6f9412929c8dbcdc36c2d36c55f1d895e4e28ea1 Mon Sep 17 00:00:00 2001 From: "marta.sudol" Date: Mon, 12 May 2025 10:49:05 +0200 Subject: [PATCH 22/26] build search query optimization --- src/libs/SearchQueryUtils.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/libs/SearchQueryUtils.ts b/src/libs/SearchQueryUtils.ts index 43ac4b669aa3..60e658883753 100644 --- a/src/libs/SearchQueryUtils.ts +++ b/src/libs/SearchQueryUtils.ts @@ -844,7 +844,14 @@ function getQueryWithoutFilters(searchQuery: string) { return searchQuery.trim(); } - return searchQuery.slice(lastFilter.start + lastFilter.length).trim(); + const queryJSON = buildSearchQueryJSON(searchQuery); + if (!queryJSON) { + return searchQuery.trim(); + } + + const keywordFilter = queryJSON.flatFilters.find((filter) => filter.key === 'keyword'); + + return keywordFilter?.filters.map((filter) => filter.value).join(' ') ?? ''; } /** From 5997f36e5fb6c6062f7b503c2fca6593eaba45ca Mon Sep 17 00:00:00 2001 From: "marta.sudol" Date: Tue, 13 May 2025 09:28:11 +0200 Subject: [PATCH 23/26] PR comments addressed --- src/components/Search/SearchAutocompleteList.tsx | 2 +- src/libs/SearchQueryUtils.ts | 10 +--------- src/pages/Search/SearchPageNarrow.tsx | 13 ++++++++++++- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/components/Search/SearchAutocompleteList.tsx b/src/components/Search/SearchAutocompleteList.tsx index 16ef0988607c..945156af349e 100644 --- a/src/components/Search/SearchAutocompleteList.tsx +++ b/src/components/Search/SearchAutocompleteList.tsx @@ -57,7 +57,7 @@ type SearchAutocompleteListProps = { autocompleteQueryValue: string; /** Callback to trigger search action * */ - handleSearch?: (value: string) => void; + handleSearch: (value: string) => void; /** An optional item to always display on the top of the router list */ searchQueryItem?: SearchQueryItem; diff --git a/src/libs/SearchQueryUtils.ts b/src/libs/SearchQueryUtils.ts index 60e658883753..e18fa83c8c8a 100644 --- a/src/libs/SearchQueryUtils.ts +++ b/src/libs/SearchQueryUtils.ts @@ -22,7 +22,6 @@ import type {SearchFullscreenNavigatorParamList} from './Navigation/types'; import {getPersonalDetailByEmail} from './PersonalDetailsUtils'; import {getCleanedTagName, getTagNamesFromTagsLists} from './PolicyUtils'; import {getReportName} from './ReportUtils'; -import {parseForAutocomplete} from './SearchAutocompleteUtils'; import {parse as parseSearchQuery} from './SearchParser/searchParser'; import {hashText} from './UserUtils'; import {isValidDate} from './ValidationUtils'; @@ -837,16 +836,9 @@ function getCurrentSearchQueryJSON() { * @returns The query without filters (core search terms only) */ function getQueryWithoutFilters(searchQuery: string) { - const parsedQuery = parseForAutocomplete(searchQuery); - const lastFilter = parsedQuery?.ranges.at(-1); - - if (!lastFilter) { - return searchQuery.trim(); - } - const queryJSON = buildSearchQueryJSON(searchQuery); if (!queryJSON) { - return searchQuery.trim(); + return ''; } const keywordFilter = queryJSON.flatFilters.find((filter) => filter.key === 'keyword'); diff --git a/src/pages/Search/SearchPageNarrow.tsx b/src/pages/Search/SearchPageNarrow.tsx index 8d938c9b269f..8368c6685c37 100644 --- a/src/pages/Search/SearchPageNarrow.tsx +++ b/src/pages/Search/SearchPageNarrow.tsx @@ -14,7 +14,7 @@ import {useSearchContext} from '@components/Search/SearchContext'; import SearchPageHeader from '@components/Search/SearchPageHeader/SearchPageHeader'; import type {SearchHeaderOptionValue} from '@components/Search/SearchPageHeader/SearchPageHeader'; import SearchStatusBar from '@components/Search/SearchPageHeader/SearchStatusBar'; -import type {SearchQueryJSON} from '@components/Search/types'; +import type {SearchParams, SearchQueryJSON} from '@components/Search/types'; import useHandleBackButton from '@hooks/useHandleBackButton'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; @@ -31,6 +31,8 @@ import variables from '@styles/variables'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type {SearchResults} from '@src/types/onyx'; +import {searchInServer} from '@userActions/Report'; +import {search} from '@userActions/Search'; const TOO_CLOSE_TO_TOP_DISTANCE = 10; const TOO_CLOSE_TO_BOTTOM_DISTANCE = 10; @@ -107,6 +109,14 @@ function SearchPageNarrow({queryJSON, searchName, headerButtonsOptions, currentS Navigation.goBack(ROUTES.SEARCH_ROOT.getRoute({query: buildCannedSearchQuery()})); }, [searchRouterListVisible]); + const handleSearchAction = useCallback((value: SearchParams | string) => { + if (typeof value === 'string') { + searchInServer(value); + } else { + search(value); + } + }, []); + if (!queryJSON) { return ( )} From aa28dc9810ebcfd1bfb4b0beae497be3f6c24295 Mon Sep 17 00:00:00 2001 From: "marta.sudol" Date: Tue, 13 May 2025 09:31:03 +0200 Subject: [PATCH 24/26] prettier fixes --- src/pages/Search/SearchPageNarrow.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/Search/SearchPageNarrow.tsx b/src/pages/Search/SearchPageNarrow.tsx index 8368c6685c37..c0b589f1acd4 100644 --- a/src/pages/Search/SearchPageNarrow.tsx +++ b/src/pages/Search/SearchPageNarrow.tsx @@ -28,11 +28,11 @@ import Navigation from '@libs/Navigation/Navigation'; import {buildCannedSearchQuery, isCannedSearchQuery} from '@libs/SearchQueryUtils'; import {isSearchDataLoaded} from '@libs/SearchUIUtils'; import variables from '@styles/variables'; +import {searchInServer} from '@userActions/Report'; +import {search} from '@userActions/Search'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type {SearchResults} from '@src/types/onyx'; -import {searchInServer} from '@userActions/Report'; -import {search} from '@userActions/Search'; const TOO_CLOSE_TO_TOP_DISTANCE = 10; const TOO_CLOSE_TO_BOTTOM_DISTANCE = 10; From 83d600efb10d7cb80be8b7963c7668ab1f93c70c Mon Sep 17 00:00:00 2001 From: "marta.sudol" Date: Wed, 14 May 2025 12:37:28 +0200 Subject: [PATCH 25/26] fix: `/SearchForReports` call on autocomplete result item click --- .../Search/SearchPageHeader/SearchPageHeaderInput.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/components/Search/SearchPageHeader/SearchPageHeaderInput.tsx b/src/components/Search/SearchPageHeader/SearchPageHeaderInput.tsx index c0b8bd0cc0c2..af7f523a702c 100644 --- a/src/components/Search/SearchPageHeader/SearchPageHeaderInput.tsx +++ b/src/components/Search/SearchPageHeader/SearchPageHeaderInput.tsx @@ -84,9 +84,14 @@ function SearchPageHeaderInput({queryJSON, searchRouterListVisible, hideSearchRo const [isAutocompleteListVisible, setIsAutocompleteListVisible] = useState(false); const listRef = useRef(null); const textInputRef = useRef(null); + const hasMountedRef = useRef(false); const isFocused = useIsFocused(); const {registerSearchPageInput} = useSearchRouterContext(); + useEffect(() => { + hasMountedRef.current = true; + }, []); + // useEffect for blurring TextInput when we cancel SearchRouter interaction on narrow layout useEffect(() => { if (!displayNarrowHeader || !!searchRouterListVisible || !textInputRef.current || !textInputRef.current.isFocused()) { @@ -135,14 +140,14 @@ function SearchPageHeaderInput({queryJSON, searchRouterListVisible, hideSearchRo const handleSearchAction = useCallback( (value: string) => { - if (!handleSearch) { + // Skip calling handleSearch on the initial mount + if (!hasMountedRef.current || !handleSearch) { return; } handleSearch(value); }, [handleSearch], ); - const onSearchQueryChange = useCallback( (userQuery: string) => { const singleLineUserQuery = StringUtils.lineBreaksToSpaces(userQuery, true); From 2f7f97f06a834c1069c5d2bb6c110fae26a3d6fb Mon Sep 17 00:00:00 2001 From: "marta.sudol" Date: Wed, 14 May 2025 12:46:13 +0200 Subject: [PATCH 26/26] fix: `/SearchForReports` call missing for narrow resolutions --- src/components/Search/SearchPageHeader/SearchPageHeader.tsx | 2 +- .../Search/SearchPageHeader/SearchPageHeaderInput.tsx | 4 ++-- src/components/Search/index.tsx | 6 +----- src/pages/Search/SearchPageNarrow.tsx | 2 ++ 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/components/Search/SearchPageHeader/SearchPageHeader.tsx b/src/components/Search/SearchPageHeader/SearchPageHeader.tsx index de3133bdbe18..62244fd382c2 100644 --- a/src/components/Search/SearchPageHeader/SearchPageHeader.tsx +++ b/src/components/Search/SearchPageHeader/SearchPageHeader.tsx @@ -28,7 +28,7 @@ type SearchPageHeaderProps = { hideSearchRouterList?: () => void; onSearchRouterFocus?: () => void; headerButtonsOptions: Array>; - handleSearch?: (value: string) => void; + handleSearch: (value: string) => void; }; type SearchHeaderOptionValue = DeepValueOf | undefined; diff --git a/src/components/Search/SearchPageHeader/SearchPageHeaderInput.tsx b/src/components/Search/SearchPageHeader/SearchPageHeaderInput.tsx index af7f523a702c..16dcb0177e9d 100644 --- a/src/components/Search/SearchPageHeader/SearchPageHeaderInput.tsx +++ b/src/components/Search/SearchPageHeader/SearchPageHeaderInput.tsx @@ -50,7 +50,7 @@ type SearchPageHeaderInputProps = { onSearchRouterFocus?: () => void; searchName?: string; inputRightComponent: React.ReactNode; - handleSearch?: (value: string) => void; + handleSearch: (value: string) => void; }; function SearchPageHeaderInput({queryJSON, searchRouterListVisible, hideSearchRouterList, onSearchRouterFocus, searchName, inputRightComponent, handleSearch}: SearchPageHeaderInputProps) { @@ -141,7 +141,7 @@ function SearchPageHeaderInput({queryJSON, searchRouterListVisible, hideSearchRo const handleSearchAction = useCallback( (value: string) => { // Skip calling handleSearch on the initial mount - if (!hasMountedRef.current || !handleSearch) { + if (!hasMountedRef.current) { return; } handleSearch(value); diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx index 02c565eeac65..d7a55c9e2e5d 100644 --- a/src/components/Search/index.tsx +++ b/src/components/Search/index.tsx @@ -56,7 +56,7 @@ type SearchProps = { contentContainerStyle?: StyleProp; currentSearchResults?: SearchResults; lastNonEmptySearchResults?: SearchResults; - handleSearch?: (value: SearchParams) => void; + handleSearch: (value: SearchParams) => void; }; function mapTransactionItemToSelectedEntry(item: TransactionListItemType): [string, SelectedTransactionInfo] { @@ -202,10 +202,6 @@ function Search({queryJSON, currentSearchResults, lastNonEmptySearchResults, onS return; } - if (!handleSearch) { - return; - } - handleSearch({queryJSON, offset}); }, [handleSearch, isOffline, offset, queryJSON]); diff --git a/src/pages/Search/SearchPageNarrow.tsx b/src/pages/Search/SearchPageNarrow.tsx index c0b589f1acd4..3993df9bf495 100644 --- a/src/pages/Search/SearchPageNarrow.tsx +++ b/src/pages/Search/SearchPageNarrow.tsx @@ -171,6 +171,7 @@ function SearchPageNarrow({queryJSON, searchName, headerButtonsOptions, currentS setSearchRouterListVisible(true); }} headerButtonsOptions={headerButtonsOptions} + handleSearch={handleSearchAction} /> @@ -201,6 +202,7 @@ function SearchPageNarrow({queryJSON, searchName, headerButtonsOptions, currentS queryJSON={queryJSON} searchName={searchName} headerButtonsOptions={headerButtonsOptions} + handleSearch={handleSearchAction} /> )}