From 960d7e5897524f19ac474cfad2841a52c065bc5a Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Tue, 16 Dec 2025 19:53:24 -0700 Subject: [PATCH 1/4] keep horizontal scroll --- src/components/Search/SearchList/index.tsx | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/components/Search/SearchList/index.tsx b/src/components/Search/SearchList/index.tsx index db4dfa18f065..d3cc2662b2d9 100644 --- a/src/components/Search/SearchList/index.tsx +++ b/src/components/Search/SearchList/index.tsx @@ -6,7 +6,8 @@ import type {FlashListProps, FlashListRef, ViewToken} from '@shopify/flash-list' import React, {useCallback, useContext, useImperativeHandle, useMemo, useRef, useState} from 'react'; import type {ForwardedRef} from 'react'; import {View} from 'react-native'; -import type {NativeSyntheticEvent, StyleProp, ViewStyle} from 'react-native'; +// eslint-disable-next-line no-restricted-imports +import type {NativeScrollEvent, NativeSyntheticEvent, ScrollView as RNScrollView, StyleProp, ViewStyle} from 'react-native'; import Animated, {Easing, FadeOutUp, LinearTransition} from 'react-native-reanimated'; import Checkbox from '@components/Checkbox'; import * as Expensicons from '@components/Icon/Expensicons'; @@ -233,6 +234,19 @@ function SearchList({ const minTableWidth = getTableMinWidth(columns); const shouldScrollHorizontally = !!SearchTableHeader && minTableWidth > windowWidth; + const horizontalScrollViewRef = useRef(null); + const horizontalScrollOffsetRef = useRef(0); + + const handleHorizontalScroll = useCallback((event: NativeSyntheticEvent) => { + horizontalScrollOffsetRef.current = event.nativeEvent.contentOffset.x; + }, []); + + const restoreHorizontalScrollPosition = useCallback(() => { + if (horizontalScrollOffsetRef.current > 0) { + horizontalScrollViewRef.current?.scrollTo({x: horizontalScrollOffsetRef.current, animated: false}); + } + }, []); + const handleLongPressRow = useCallback( (item: SearchListItem, itemTransactions?: TransactionListItemType[]) => { const currentRoute = navigationRef.current?.getCurrentRoute(); @@ -452,10 +466,14 @@ function SearchList({ if (shouldScrollHorizontally) { return ( {content} From de39433b2c2ea75c81893611f96333bb6a47e8eb Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Tue, 16 Dec 2025 20:00:02 -0700 Subject: [PATCH 2/4] add fix --- src/components/Search/SearchList/index.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/components/Search/SearchList/index.tsx b/src/components/Search/SearchList/index.tsx index d3cc2662b2d9..3f4561221a1f 100644 --- a/src/components/Search/SearchList/index.tsx +++ b/src/components/Search/SearchList/index.tsx @@ -241,10 +241,12 @@ function SearchList({ horizontalScrollOffsetRef.current = event.nativeEvent.contentOffset.x; }, []); - const restoreHorizontalScrollPosition = useCallback(() => { - if (horizontalScrollOffsetRef.current > 0) { - horizontalScrollViewRef.current?.scrollTo({x: horizontalScrollOffsetRef.current, animated: false}); + // Restore horizontal scroll position after content size changes + const handleContentSizeChange = useCallback(() => { + if (horizontalScrollOffsetRef.current <= 0) { + return; } + horizontalScrollViewRef.current?.scrollTo({x: horizontalScrollOffsetRef.current, animated: false}); }, []); const handleLongPressRow = useCallback( @@ -473,7 +475,7 @@ function SearchList({ contentContainerStyle={{width: minTableWidth}} onScroll={handleHorizontalScroll} scrollEventThrottle={16} - onLayout={restoreHorizontalScrollPosition} + onContentSizeChange={handleContentSizeChange} > {content} From c1a6550ffcade971cd748b97be5324fc3ea3235f Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Tue, 16 Dec 2025 21:05:12 -0700 Subject: [PATCH 3/4] fix scroll --- src/components/Search/SearchList/index.tsx | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/components/Search/SearchList/index.tsx b/src/components/Search/SearchList/index.tsx index 3f4561221a1f..11b4b711c73d 100644 --- a/src/components/Search/SearchList/index.tsx +++ b/src/components/Search/SearchList/index.tsx @@ -3,7 +3,7 @@ import {isUserValidatedSelector} from '@selectors/Account'; import {accountIDSelector} from '@selectors/Session'; import {tierNameSelector} from '@selectors/UserWallet'; import type {FlashListProps, FlashListRef, ViewToken} from '@shopify/flash-list'; -import React, {useCallback, useContext, useImperativeHandle, useMemo, useRef, useState} from 'react'; +import React, {useCallback, useContext, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState} from 'react'; import type {ForwardedRef} from 'react'; import {View} from 'react-native'; // eslint-disable-next-line no-restricted-imports @@ -52,6 +52,9 @@ import BaseSearchList from './BaseSearchList'; const easing = Easing.bezier(0.76, 0.0, 0.24, 1.0); +// Module-level storage for horizontal scroll offset (single value since we only have one search view at a time) +let savedHorizontalScrollOffset = 0; + type SearchListItem = TransactionListItemType | TransactionGroupListItemType | ReportActionListItemType | TaskListItemType; type SearchListItemComponentType = typeof TransactionListItem | typeof ChatListItem | typeof TransactionGroupListItem | typeof TaskListItem; @@ -235,19 +238,18 @@ function SearchList({ const shouldScrollHorizontally = !!SearchTableHeader && minTableWidth > windowWidth; const horizontalScrollViewRef = useRef(null); - const horizontalScrollOffsetRef = useRef(0); const handleHorizontalScroll = useCallback((event: NativeSyntheticEvent) => { - horizontalScrollOffsetRef.current = event.nativeEvent.contentOffset.x; + savedHorizontalScrollOffset = event.nativeEvent.contentOffset.x; }, []); - // Restore horizontal scroll position after content size changes - const handleContentSizeChange = useCallback(() => { - if (horizontalScrollOffsetRef.current <= 0) { + // Restore horizontal scroll position synchronously before paint using useLayoutEffect + useLayoutEffect(() => { + if (!shouldScrollHorizontally || savedHorizontalScrollOffset <= 0) { return; } - horizontalScrollViewRef.current?.scrollTo({x: horizontalScrollOffsetRef.current, animated: false}); - }, []); + horizontalScrollViewRef.current?.scrollTo({x: savedHorizontalScrollOffset, animated: false}); + }, [data, shouldScrollHorizontally]); const handleLongPressRow = useCallback( (item: SearchListItem, itemTransactions?: TransactionListItemType[]) => { @@ -473,9 +475,9 @@ function SearchList({ showsHorizontalScrollIndicator style={styles.flex1} contentContainerStyle={{width: minTableWidth}} + contentOffset={{x: savedHorizontalScrollOffset, y: 0}} onScroll={handleHorizontalScroll} scrollEventThrottle={16} - onContentSizeChange={handleContentSizeChange} > {content} From b88aa336ed9609a5467e63da9c213bfd750b7011 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Tue, 16 Dec 2025 21:09:27 -0700 Subject: [PATCH 4/4] update comments --- src/components/Search/SearchList/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Search/SearchList/index.tsx b/src/components/Search/SearchList/index.tsx index 11b4b711c73d..bff474935cf4 100644 --- a/src/components/Search/SearchList/index.tsx +++ b/src/components/Search/SearchList/index.tsx @@ -52,7 +52,7 @@ import BaseSearchList from './BaseSearchList'; const easing = Easing.bezier(0.76, 0.0, 0.24, 1.0); -// Module-level storage for horizontal scroll offset (single value since we only have one search view at a time) +// Keep a ref to the horizontal scroll offset so we can restore it if users change the search query let savedHorizontalScrollOffset = 0; type SearchListItem = TransactionListItemType | TransactionGroupListItemType | ReportActionListItemType | TaskListItemType; @@ -243,7 +243,7 @@ function SearchList({ savedHorizontalScrollOffset = event.nativeEvent.contentOffset.x; }, []); - // Restore horizontal scroll position synchronously before paint using useLayoutEffect + // Restore horizontal scroll position synchronously before paint using useLayoutEffect to avoid a visible shift on the table useLayoutEffect(() => { if (!shouldScrollHorizontally || savedHorizontalScrollOffset <= 0) { return;