Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
49cca88
Confirm new expenses with a “View” toast instead of scrolling to and …
mhawryluk Jul 20, 2026
066854f
Merge branch 'main' into declarative-expense-added-growl
mhawryluk Jul 20, 2026
956d2a2
Fix tests
mhawryluk Jul 20, 2026
b3e03b2
Invoke findSelfDMReportID only when a transaction is active in Expen…
mhawryluk Jul 21, 2026
a9be08a
Add comment for latestTransactionID last-created assumption
mhawryluk Jul 21, 2026
62ee8e8
Clear EXPENSE_ADDED_GROWL_TRANSACTION_IDS on new session
mhawryluk Jul 21, 2026
e53cdd3
Add key to ensure remounting GrowlNotificationContent in ExpenseAdded…
mhawryluk Jul 21, 2026
ac42067
Remove mergeExpenseAddedGrowlTransactionIDs call from navigateAfterEx…
mhawryluk Jul 21, 2026
698268c
Remove useAnimatedHighlightStyle usage from Spend table
mhawryluk Jul 21, 2026
e863597
Merge branch 'main' into declarative-expense-added-growl
mhawryluk Jul 21, 2026
ca1593a
Remove unused handleNavigateAfterExpenseCreate
mhawryluk Jul 21, 2026
569cb42
Fix not showing growl when submitting photo from gallery
mhawryluk Jul 23, 2026
2ab5a2c
Merge branch 'main' into declarative-expense-added-growl
mhawryluk Jul 24, 2026
c0c93d4
Fix after merge
mhawryluk Jul 24, 2026
62419b6
Fix after merge 2
mhawryluk Jul 24, 2026
cb74505
Rename files from useSearchHighlightAndScroll to useSearchAutoRefetch
mhawryluk Jul 28, 2026
3dc7dd2
Add jsdoc to UseSearchAutoRefetch
mhawryluk Jul 28, 2026
13d868f
Separate ExpenseAddedGrowlContent to remove onyx connections when no …
mhawryluk Jul 28, 2026
8d64539
Derive backTo from the existing RHP's own fallback route when forceRe…
mhawryluk Jul 28, 2026
42fb7e7
Add jsdoc to NavigateAfterExpenseCreateParams
mhawryluk Jul 28, 2026
5e8f6bc
Add a comment about surfacing only the latest transaction id from a b…
mhawryluk Jul 28, 2026
8a9d087
Update outdated ExpenseFlatSearchView comment
mhawryluk Jul 28, 2026
8db62fd
Merge branch 'main' into declarative-expense-added-growl
mhawryluk Jul 28, 2026
67a4612
Fix after merge
mhawryluk Jul 28, 2026
83ee6a4
Merge branch 'main' into declarative-expense-added-growl
mhawryluk Jul 29, 2026
57509e3
Merge branch 'main' into declarative-expense-added-growl
mhawryluk Jul 30, 2026
a5a2eac
Merge branch 'main' into declarative-expense-added-growl
mhawryluk Aug 4, 2026
a6214e4
Fix after merge
mhawryluk Aug 4, 2026
676871e
Merge branch 'main' into declarative-expense-added-growl
mhawryluk Aug 10, 2026
0df81a2
Fix after merge
mhawryluk Aug 10, 2026
2d9ff2d
Add greek translations
mhawryluk Aug 10, 2026
93daab3
Merge branch 'main' into declarative-expense-added-growl
mhawryluk Aug 11, 2026
ccf2044
Yet another fix after merge
mhawryluk Aug 11, 2026
ec4fe71
Merge branch 'main' into declarative-expense-added-growl
mhawryluk Aug 12, 2026
dbe59bf
Fix after merge
mhawryluk Aug 12, 2026
fed957e
Fix tests
mhawryluk Aug 12, 2026
b3de581
Merge branch 'main' into declarative-expense-added-growl
mhawryluk Aug 13, 2026
a790b17
Merge branch 'main' into declarative-expense-added-growl
mhawryluk Aug 14, 2026
7dd55bb
Implement Melvin's nit suggestions
mhawryluk Aug 14, 2026
a5892ff
Add guard for skipping growl after reverse split operation
mhawryluk Aug 14, 2026
19a6548
Show growl for duplicate only via bulk action
mhawryluk Aug 14, 2026
c2e9b51
Merge branch 'main' into declarative-expense-added-growl
mhawryluk Aug 17, 2026
241c28f
Fix after merge
mhawryluk Aug 17, 2026
019defc
Merge branch 'main' into declarative-expense-added-growl
mhawryluk Aug 17, 2026
dfa97df
Fix after merge
mhawryluk Aug 17, 2026
a11ed61
Merge branch 'main' into declarative-expense-added-growl
mhawryluk Aug 18, 2026
3e330dc
Extract getCurrentRouteBackTo helper
mhawryluk Aug 18, 2026
2ba8bc5
Add StyleUtils.getSearchRowBackgroundStyle
mhawryluk Aug 18, 2026
083807d
Fix eslint warnings in useSearchAutoRefetch.ts
mhawryluk Aug 18, 2026
f4b67b5
Don't show the growl after log out
mhawryluk Aug 18, 2026
38660d9
Merge branch 'main' into declarative-expense-added-growl
mhawryluk Aug 19, 2026
2236a01
Merge branch 'main' into declarative-expense-added-growl
mhawryluk Aug 20, 2026
1f6e651
The last fix after merge
mhawryluk Aug 20, 2026
b680e10
Comments overhaul
mhawryluk Aug 21, 2026
30449e4
Merge branch 'main' into declarative-expense-added-growl
mhawryluk Aug 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion config/eslint/eslint.seatbelt.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,6 @@
"../../src/hooks/useReviewDuplicatesNavigation.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1
"../../src/hooks/useReviewDuplicatesNavigation.tsx" "react-hooks/set-state-in-effect" 1
"../../src/hooks/useSearchBulkActions.ts" "@typescript-eslint/no-unsafe-type-assertion" 10
"../../src/hooks/useSearchHighlightAndScroll.ts" "@typescript-eslint/no-unsafe-type-assertion" 4
"../../src/hooks/useSidebarOrderedReports.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1
"../../src/hooks/useSidebarOrderedReports.tsx" "react-hooks/refs" 5
"../../src/hooks/useSidebarOrderedReports.tsx" "react-hooks/set-state-in-effect" 1
Expand Down
4 changes: 4 additions & 0 deletions src/GlobalModals.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ import React, {startTransition, useEffect, useState} from 'react';

import DelegateNoAccessModalProvider from './components/DelegateNoAccessModalProvider';
import EmojiPicker from './components/EmojiPicker/EmojiPicker';
import ExpenseAddedGrowl from './components/ExpenseAddedGrowl';
import GrowlNotification from './components/GrowlNotification';
import LazyModalSlot from './components/LazyModalSlot';
import useIsAuthenticated from './hooks/useIsAuthenticated';
import * as EmojiPickerAction from './libs/actions/EmojiPickerAction';
import {growlRef} from './libs/Growl';
import * as ReportActionContextMenu from './pages/inbox/report/ContextMenu/ReportActionContextMenu';
Expand All @@ -24,6 +26,7 @@ const IDLE_CALLBACK_TIMEOUT_MS = 2000;
function GlobalModals() {
const [shouldRenderContextMenu, setShouldRenderContextMenu] = useState(false);
const [shouldRenderDeferredModals, setShouldRenderDeferredModals] = useState(false);
const isAuthenticated = useIsAuthenticated();

// Defer loading the context menu and rare-condition modals until after startup to avoid
// pulling in their dependencies (ContextMenuActions, ReportUtils, ModifiedExpenseMessage,
Expand All @@ -50,6 +53,7 @@ function GlobalModals() {
return (
<>
<GrowlNotification ref={growlRef} />
{isAuthenticated && <ExpenseAddedGrowl />}
<DelegateNoAccessModalProvider>
{shouldRenderContextMenu && (
<LazyModalSlot>
Expand Down
7 changes: 4 additions & 3 deletions src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import type {Attendee, DistanceExpenseType, Participant} from './types/onyx/IOU'
import type Onboarding from './types/onyx/Onboarding';
import type {AnyOnyxUpdate} from './types/onyx/Request';
import type {SavedCSVColumnLayoutList} from './types/onyx/SavedCSVColumnLayout';
import type {SearchDataTypes} from './types/onyx/SearchResults';
import type AssertTypesEqual from './types/utils/AssertTypesEqual';
import type DeepValueOf from './types/utils/DeepValueOf';

Expand Down Expand Up @@ -848,8 +849,8 @@ const ONYXKEYS = {
/** Whether the user has denied the contact import permission prompt */
HAS_DENIED_CONTACT_IMPORT_PROMPT: 'hasDeniedContactImportPrompt',

/** The transaction IDs to be highlighted when opening the Expenses search route page */
TRANSACTION_IDS_HIGHLIGHT_ON_SEARCH_ROUTE: 'transactionIdsHighlightOnSearchRoute',
/** Maps each newly-added transaction ID to its search data type, flagging it for the "Expense added" growl */
EXPENSE_ADDED_GROWL_TRANSACTION_IDS: 'expenseAddedGrowlTransactionIDs',

/** The report ID to be highlighted when returning to the workspace rooms page */
ROOM_ID_HIGHLIGHT_ON_ROOMS_PAGE: 'roomIDHighlightOnRoomsPage',
Expand Down Expand Up @@ -1818,7 +1819,7 @@ type OnyxValuesMapping = {
[ONYXKEYS.NVP_REPORT_DETAILS_COLUMNS]: string[];
[ONYXKEYS.HAS_DENIED_CONTACT_IMPORT_PROMPT]: boolean | undefined;
[ONYXKEYS.PERSONAL_POLICY_ID]: string;
[ONYXKEYS.TRANSACTION_IDS_HIGHLIGHT_ON_SEARCH_ROUTE]: Record<string, Record<string, boolean>>;
[ONYXKEYS.EXPENSE_ADDED_GROWL_TRANSACTION_IDS]: Record<string, SearchDataTypes>;
[ONYXKEYS.ROOM_ID_HIGHLIGHT_ON_ROOMS_PAGE]: string | null;
[ONYXKEYS.DOMAIN_GROUP_CREATE_PREFERRED_POLICY_ID]: string | undefined;
};
Expand Down
164 changes: 164 additions & 0 deletions src/components/ExpenseAddedGrowl.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
import useLocalize from '@hooks/useLocalize';
import useOnyx from '@hooks/useOnyx';

import {createTransactionThreadReport, setOptimisticTransactionThread} from '@libs/actions/Report';
import {mergeExpenseAddedGrowlTransactionIDs} from '@libs/actions/Transaction';
import Log from '@libs/Log';
import {navigateToCreatedExpense} from '@libs/Navigation/helpers/navigateAfterExpenseCreate';
import Navigation from '@libs/Navigation/Navigation';
import {getIOUActionForTransactionID} from '@libs/ReportActionsUtils';
import {findSelfDMReportID, isInvoiceReport, isMoneyRequestReport} from '@libs/ReportUtils';

import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {SearchDataTypes} from '@src/types/onyx/SearchResults';

import type {Dispatch, SetStateAction} from 'react';

import {useEffect, useRef, useState} from 'react';

import GrowlNotificationContent from './GrowlNotification/GrowlNotificationContent';

type ActiveGrowl = {
/** ID of the transaction the growl was raised for */
transactionID: string;

/** Search data type the growl belongs to (expense, invoice, etc.) */
dataType: SearchDataTypes;

/** Identifies each growl instance so a stale slide-out dismissal can't clear a newer growl */
nonce: number;
};

type ExpenseAddedGrowlContentProps = {
/** Transaction to read for the growl: the active growl's transaction, or the latest pending signal ID */
transactionID: string;

/** Pending expense-added notifications, keyed by transaction ID with its search data type as the value */
signal: Record<string, SearchDataTypes> | undefined;

/** Currently displayed growl */
active: ActiveGrowl | null;

/** Setter for the active growl */
setActive: Dispatch<SetStateAction<ActiveGrowl | null>>;
};

/** Watches the "an expense was just added" Onyx signal and shows an "Expense added" growl with a "View" action. */
function ExpenseAddedGrowl() {
const [active, setActive] = useState<ActiveGrowl | null>(null);
const [signal] = useOnyx(ONYXKEYS.EXPENSE_ADDED_GROWL_TRANSACTION_IDS);
const transactionID = active?.transactionID ?? Object.keys(signal ?? {}).at(-1);

if (!transactionID) {
return null;
}

return (
<ExpenseAddedGrowlContent
transactionID={transactionID}
signal={signal}
active={active}
setActive={setActive}
/>
);
}

function ExpenseAddedGrowlContent({transactionID, signal, active, setActive}: ExpenseAddedGrowlContentProps) {
const nonceRef = useRef(0);

const {translate} = useLocalize();
const currentUserPersonalDetails = useCurrentUserPersonalDetails();
const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST);
Comment thread
mhawryluk marked this conversation as resolved.
const [betas] = useOnyx(ONYXKEYS.BETAS);
const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED);
const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`);
const reportID = transaction?.reportID;
const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`);

// A tracked expense's IOU action lives on the self-DM report, not on transaction.reportID, so read actions
// from the report that actually hosts it so "View" can resolve the real transaction thread.
const isUnreportedExpense = !reportID || reportID === CONST.REPORT.UNREPORTED_REPORT_ID;
const selfDMReportID = isUnreportedExpense ? findSelfDMReportID() : undefined;
const hostReportID = isUnreportedExpense ? selfDMReportID : reportID;
const [reportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${hostReportID}`);

// A tracked expense lives in a chat, not an expense report. Leaving iouReportID undefined tells
// navigateToCreatedExpense to open the transaction thread directly instead of an expense report RHP.
const iouReport = isMoneyRequestReport(report) || isInvoiceReport(report) ? report : undefined;
const iouReportID = iouReport?.reportID;

useEffect(() => {
Comment thread
mhawryluk marked this conversation as resolved.
if (active) {
return;
}
const pendingTransactionIDs = Object.keys(signal ?? {});

// The last key is the last-created transaction: IDs are large (rand64), so they exceed the array-index
// range and JS preserves insertion order rather than sorting them numerically ascending.
const latestTransactionID = pendingTransactionIDs.at(-1);
Comment thread
mhawryluk marked this conversation as resolved.
const dataType = latestTransactionID ? signal?.[latestTransactionID] : undefined;
if (!latestTransactionID || !dataType || !transaction) {
return;
}

// Clear every pending ID but surface only the latest: rapid creations that pile up before this runs
// collapse into a single growl for the newest expense.
mergeExpenseAddedGrowlTransactionIDs(Object.fromEntries(pendingTransactionIDs.map((id) => [id, null])));
Comment thread
mhawryluk marked this conversation as resolved.

// Suppress the growl when the user is already viewing the expense's money-request report
if (Navigation.getTopmostReportId() === transaction.reportID) {
return;
}
nonceRef.current += 1;
setActive({transactionID: latestTransactionID, dataType, nonce: nonceRef.current});
}, [signal, active, transaction, setActive]);

if (!active) {
return null;
}

const isInvoice = active.dataType === CONST.SEARCH.DATA_TYPES.INVOICE;

// Build the thread on press rather than when the growl shows, so it is only created if the user taps
// "View" and is built against the freshest Onyx data.
const navigateToExpense = () => {
const iouAction = getIOUActionForTransactionID(Object.values(reportActions ?? {}), active.transactionID);
let threadReportID = transaction?.transactionThreadReportID ?? iouAction?.childReportID;
if (threadReportID) {
setOptimisticTransactionThread(threadReportID, iouReport?.reportID, iouAction?.reportActionID, iouReport?.policyID);
} else {
const optimisticThread = createTransactionThreadReport({
introSelected,
currentUserLogin: currentUserPersonalDetails?.login ?? '',
currentUserAccountID: currentUserPersonalDetails?.accountID ?? CONST.DEFAULT_NUMBER_ID,
betas,
iouReport,
iouReportAction: iouAction,
transaction,
personalDetails,
});
threadReportID = optimisticThread?.reportID;
}
if (!threadReportID) {
Log.warn('[ExpenseAddedGrowl] Unable to resolve transaction thread reportID on View press.');
return;
}
navigateToCreatedExpense({threadReportID, transactionID: active.transactionID, iouReportID});
};

return (
<GrowlNotificationContent
Comment thread
mhawryluk marked this conversation as resolved.
key={active.nonce}
nonce={active.nonce}
type={CONST.GROWL.SUCCESS}
duration={CONST.GROWL.DURATION_WITH_ACTION}
bodyText={translate(isInvoice ? 'iou.invoiceSent' : 'iou.expenseAdded')}
action={{label: translate('common.view'), onPress: navigateToExpense}}
onDismissed={(dismissedNonce) => setActive((prev) => (prev?.nonce === dismissedNonce ? null : prev))}
/>
);
}

export default ExpenseAddedGrowl;
6 changes: 1 addition & 5 deletions src/components/Search/ChatSearchView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import CONST from '@src/CONST';

import type {NativeSyntheticEvent} from 'react-native';

import React, {useImperativeHandle} from 'react';
import React from 'react';

import type {SearchListItem} from './SearchList/ListItem/types';
import type {CommonSearchViewProps} from './searchViewProps';
Expand Down Expand Up @@ -49,7 +49,6 @@ function ChatSearchView({
onScroll,
contentContainerStyle,
containerStyle,
ref,
}: ChatSearchViewProps) {
const {type} = queryJSON;

Expand All @@ -65,9 +64,6 @@ function ChatSearchView({
const selectedItemsLength = data.reduce((acc, item) => acc + (item.keyForList && selectedTransactions[item.keyForList]?.isSelected ? 1 : 0), 0);
const totalItems = data.filter((item) => !isRowDeleted(item)).length;

// Flat data maps 1:1 to the rendered list, so highlight-scroll-to-index is the same as scroll-to-data-index.
useImperativeHandle(ref, () => ({scrollToIndex: scrollToListIndex}), [scrollToListIndex]);

const renderItem = (item: SearchListItem, index: number, isItemFocused: boolean, onFocus?: (event: NativeSyntheticEvent<ExtendedTargetedEvent>) => void) => (
// Chat rows never animate their exit (only grouped expenses do), so the wrapper just preserves the overflow clip.
<AnimatedExitRow
Expand Down
15 changes: 5 additions & 10 deletions src/components/Search/ExpenseFlatSearchView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import CONST from '@src/CONST';

import type {NativeSyntheticEvent} from 'react-native';

import React, {useImperativeHandle} from 'react';
import React from 'react';

import type {SearchListItem} from './SearchList/ListItem/types';
import type {CommonSearchViewProps, TransactionViewExtras} from './searchViewProps';
Expand All @@ -29,11 +29,10 @@ const isRowDeleted = (item: SearchListItem) => item.pendingAction === CONST.RED_
* `toggle`/`toggleAll`/`selectedTransactions`). The shared list state and interactions come from
* `useSearchListViewState`, and the surrounding chrome (horizontal scroll, header bar, long-press menu)
* from `SearchListViewLayout`; this view only owns the flat-expense specifics: the `TransactionListItem`
* renderer, single-pass visibility/selection counts, and the highlight-scroll imperative handle.
* `TransactionListItem` is the only row renderer here and rows always animate, so the
* group/sticky/chat/task branches of `SearchList` do not apply. Keyboard navigation is inherited from
* `BaseSearchList`; the post-create highlight stays in the router (the snapshot stamps
* `shouldAnimateInHighlight`, and `newTransactions` flows into `extraData`).
* renderer and single-pass visibility/selection counts.
* `TransactionListItem` is the only row renderer here, so the group/sticky/chat/task branches of
* `SearchList` do not apply. Keyboard navigation is inherited from `BaseSearchList`; `newTransactions`
* flows into `extraData` so the list re-renders when a freshly-created expense lands.
*/
function ExpenseFlatSearchView({
queryJSON,
Expand All @@ -55,7 +54,6 @@ function ExpenseFlatSearchView({
onScroll,
contentContainerStyle,
containerStyle,
ref,
}: ExpenseFlatSearchViewProps) {
const {type} = queryJSON;

Expand Down Expand Up @@ -89,9 +87,6 @@ function ExpenseFlatSearchView({
const selectedItemsLength = data.reduce((acc, item) => acc + (item.keyForList && selectedTransactions[item.keyForList]?.isSelected ? 1 : 0), 0);
const totalItems = data.filter((item) => !isRowDeleted(item)).length;

// Flat data maps 1:1 to the rendered list, so highlight-scroll-to-index is the same as scroll-to-data-index.
useImperativeHandle(ref, () => ({scrollToIndex: scrollToListIndex}), [scrollToListIndex]);

const renderItem = (item: SearchListItem, index: number, isItemFocused: boolean, onFocus?: (event: NativeSyntheticEvent<ExtendedTargetedEvent>) => void) => {
const isDisabled = isRowDeleted(item);
// Only expense row exits animate; invoice and trip transaction lists do not (matches the legacy per-type gate).
Expand Down
26 changes: 2 additions & 24 deletions src/components/Search/ExpenseGroupedSearchView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import type {Transaction} from '@src/types/onyx';

import type {NativeSyntheticEvent} from 'react-native';

import React, {useImperativeHandle, useState} from 'react';
import React, {useState} from 'react';

import type {SearchListItem} from './SearchList/ListItem/types';
import type {CommonSearchViewProps, TransactionViewExtras} from './searchViewProps';
Expand Down Expand Up @@ -89,8 +89,7 @@ function buildNewTransactionIDMap(data: SearchListItem[], newTransactions: Trans
* `useSearchListViewState`, and the surrounding chrome from `SearchListViewLayout`. This view owns the group
* machinery: on wide web it splits each group into a sticky `GroupHeader` plus an expandable
* `GroupChildrenContainer` (`shouldSplitGroups`); otherwise each group renders through `TransactionGroupListItem`.
* Selection counts are report-aware (flattened over child transactions plus empty groups), and the scroll
* handle remaps the router's data index to the split-list index.
* Selection counts are report-aware (flattened over child transactions plus empty groups).
*/
function ExpenseGroupedSearchView({
queryJSON,
Expand All @@ -112,7 +111,6 @@ function ExpenseGroupedSearchView({
onScroll,
contentContainerStyle,
containerStyle,
ref,
}: ExpenseGroupedSearchViewProps) {
const {type, groupBy} = queryJSON;
const {isLargeScreenWidth} = useResponsiveLayout();
Expand Down Expand Up @@ -172,26 +170,6 @@ function ExpenseGroupedSearchView({
const firstVisibleIndex = listData.findIndex(isItemVisible);
const lastVisibleIndex = listData.findLastIndex(isItemVisible);

// The router highlights by source-data index; remap it to the split-list index before scrolling.
useImperativeHandle(
ref,
() => ({
scrollToIndex: (index: number, animated = true) => {
if (!shouldSplit) {
scrollToListIndex(index, animated);
return;
}
const item = data.at(index);
if (!item) {
return;
}
const splitIndex = item.keyForList ? listData.findIndex((listItem) => listItem.keyForList === `header_${item.keyForList}`) : -1;
scrollToListIndex(splitIndex !== -1 ? splitIndex : index, animated);
},
}),
[data, listData, shouldSplit, scrollToListIndex],
);

const getItemType = (item: SearchListItem) => {
if (!shouldSplit) {
return undefined;
Expand Down
6 changes: 1 addition & 5 deletions src/components/Search/ExpenseReportSearchView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import CONST from '@src/CONST';

import type {NativeSyntheticEvent} from 'react-native';

import React, {useImperativeHandle} from 'react';
import React from 'react';

import type {SearchListItem} from './SearchList/ListItem/types';
import type {CommonSearchViewProps} from './searchViewProps';
Expand Down Expand Up @@ -55,7 +55,6 @@ function ExpenseReportSearchView({
onScroll,
contentContainerStyle,
containerStyle,
ref,
}: ExpenseReportSearchViewProps) {
const {type} = queryJSON;

Expand Down Expand Up @@ -92,9 +91,6 @@ function ExpenseReportSearchView({
emptyReports.reduce((acc, item) => acc + (isRowSelected(item.keyForList, selectedTransactions) ? 1 : 0), 0);
const totalItems = flattenedTransactions.filter((item) => !isRowDeleted(item)).length + emptyReports.filter((item) => !isRowDeleted(item)).length;

// Report data maps 1:1 to the rendered list, so highlight-scroll-to-index is the same as scroll-to-data-index.
useImperativeHandle(ref, () => ({scrollToIndex: scrollToListIndex}), [scrollToListIndex]);

const renderItem = (item: SearchListItem, index: number, isItemFocused: boolean, onFocus?: (event: NativeSyntheticEvent<ExtendedTargetedEvent>) => void) => (
// Report rows never animate their exit (only grouped expenses do), so the wrapper just preserves the overflow clip.
<AnimatedExitRow
Expand Down
Loading
Loading