From b9d9c4f626bd97e27a3c34f6c501197de6164213 Mon Sep 17 00:00:00 2001 From: daledah Date: Wed, 22 Oct 2025 22:11:43 +0700 Subject: [PATCH 1/3] fix: handle selection mode in search context --- src/components/MoneyReportHeader.tsx | 2 +- src/components/Search/SearchContext.tsx | 7 +++ .../SelectionListWithModal/index.tsx | 50 ++++-------------- src/hooks/useHandleSelectionMode.ts | 52 +++++++++++++++++++ 4 files changed, 69 insertions(+), 42 deletions(-) create mode 100644 src/hooks/useHandleSelectionMode.ts diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 2e41275790938..0e1853c214da5 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -1173,7 +1173,7 @@ function MoneyReportHeader({ }; }, []); - if (isMobileSelectionModeEnabled) { + if (isMobileSelectionModeEnabled && shouldUseNarrowLayout) { // If mobile selection mode is enabled but only one or no transactions remain, turn it off const visibleTransactions = transactions.filter((t) => t.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || isOffline); if (visibleTransactions.length <= 1) { diff --git a/src/components/Search/SearchContext.tsx b/src/components/Search/SearchContext.tsx index 782d0531e9314..34063b5ed663e 100644 --- a/src/components/Search/SearchContext.tsx +++ b/src/components/Search/SearchContext.tsx @@ -1,4 +1,5 @@ import React, {useCallback, useContext, useMemo, useRef, useState} from 'react'; +import useHandleSelectionMode from '@hooks/useHandleSelectionMode'; import {isMoneyRequestReport} from '@libs/ReportUtils'; import {isTransactionListItemType, isTransactionReportGroupListItemType} from '@libs/SearchUIUtils'; import type {SearchKey} from '@libs/SearchUIUtils'; @@ -195,6 +196,12 @@ function SearchContextProvider({children}: ChildrenProps) { })); }, []); + const selectedItems = useMemo(() => { + return [...searchContextData.selectedTransactionIDs, ...searchContextData.selectedReports.map((item) => item.reportID)]; + }, [searchContextData.selectedReports, searchContextData.selectedTransactionIDs]); + + useHandleSelectionMode(selectedItems); + const searchContext = useMemo( () => ({ ...searchContextData, diff --git a/src/components/SelectionListWithModal/index.tsx b/src/components/SelectionListWithModal/index.tsx index d75e314449805..f84f28a5663a4 100644 --- a/src/components/SelectionListWithModal/index.tsx +++ b/src/components/SelectionListWithModal/index.tsx @@ -1,21 +1,21 @@ import {useIsFocused} from '@react-navigation/native'; import type {ForwardedRef} from 'react'; -import React, {forwardRef, useEffect, useRef, useState} from 'react'; +import React, {forwardRef, useMemo, useState} from 'react'; import {CheckSquare} from '@components/Icon/Expensicons'; import MenuItem from '@components/MenuItem'; import Modal from '@components/Modal'; import SelectionList from '@components/SelectionListWithSections'; import type {ListItem, SelectionListHandle, SelectionListProps} from '@components/SelectionListWithSections/types'; +import useHandleSelectionMode from '@hooks/useHandleSelectionMode'; import useLocalize from '@hooks/useLocalize'; import useMobileSelectionMode from '@hooks/useMobileSelectionMode'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; -import {turnOffMobileSelectionMode, turnOnMobileSelectionMode} from '@libs/actions/MobileSelectionMode'; +import {turnOnMobileSelectionMode} from '@libs/actions/MobileSelectionMode'; import CONST from '@src/CONST'; type SelectionListWithModalProps = SelectionListProps & { turnOnSelectionModeOnLongPress?: boolean; onTurnOnSelectionMode?: (item: TItem | null) => void; - isSelected?: (item: TItem) => boolean; isScreenFocused?: boolean; }; @@ -42,53 +42,21 @@ function SelectionListWithModal( const isFocused = useIsFocused(); const isMobileSelectionModeEnabled = useMobileSelectionMode(); - // Check if selection should be on when the modal is opened - const wasSelectionOnRef = useRef(false); - // Keep track of the number of selected items to determine if we should turn off selection mode - const selectionRef = useRef(0); - useEffect(() => { - // We can access 0 index safely as we are not displaying multiple sections in table view - const selectedItems = + const selectedItems = useMemo( + () => selectedItemsProp ?? sections[0].data.filter((item) => { if (isSelected) { return isSelected(item); } return !!item.isSelected; - }); - selectionRef.current = selectedItems.length; - - if (!isSmallScreenWidth) { - if (selectedItems.length === 0 && isMobileSelectionModeEnabled) { - turnOffMobileSelectionMode(); - } - return; - } - if (!isFocused) { - return; - } - if (!wasSelectionOnRef.current && selectedItems.length > 0) { - wasSelectionOnRef.current = true; - } - if (selectedItems.length > 0 && !isMobileSelectionModeEnabled) { - turnOnMobileSelectionMode(); - } else if (selectedItems.length === 0 && isMobileSelectionModeEnabled && !wasSelectionOnRef.current) { - turnOffMobileSelectionMode(); - } - // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps - }, [sections, selectedItemsProp, isMobileSelectionModeEnabled, isSmallScreenWidth, isSelected, isFocused]); - - useEffect( - () => () => { - if (selectionRef.current !== 0) { - return; - } - turnOffMobileSelectionMode(); - }, - [], + }), + [isSelected, sections, selectedItemsProp], ); + useHandleSelectionMode(selectedItems); + const handleLongPressRow = (item: TItem) => { // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing if (!turnOnSelectionModeOnLongPress || !isSmallScreenWidth || item?.isDisabled || item?.isDisabledCheckbox || (!isFocused && !isScreenFocused)) { diff --git a/src/hooks/useHandleSelectionMode.ts b/src/hooks/useHandleSelectionMode.ts new file mode 100644 index 0000000000000..77efb9a3fa389 --- /dev/null +++ b/src/hooks/useHandleSelectionMode.ts @@ -0,0 +1,52 @@ +import {useIsFocused} from '@react-navigation/native'; +import {useEffect, useRef} from 'react'; +import type {ListItem} from '@components/SelectionListWithSections/types'; +import {turnOffMobileSelectionMode, turnOnMobileSelectionMode} from '@libs/actions/MobileSelectionMode'; +import useMobileSelectionMode from './useMobileSelectionMode'; +import useResponsiveLayout from './useResponsiveLayout'; + +function useHandleSelectionMode(selectedItems: string[] | TItem[]) { + // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth + const {isSmallScreenWidth} = useResponsiveLayout(); + const isFocused = useIsFocused(); + + const isMobileSelectionModeEnabled = useMobileSelectionMode(); + // Check if selection should be on when the modal is opened + const wasSelectionOnRef = useRef(false); + // Keep track of the number of selected items to determine if we should turn off selection mode + const selectionRef = useRef(0); + + useEffect(() => { + selectionRef.current = selectedItems.length; + + if (!isSmallScreenWidth) { + if (selectedItems.length === 0 && isMobileSelectionModeEnabled) { + turnOffMobileSelectionMode(); + } + return; + } + if (!isFocused) { + return; + } + if (!wasSelectionOnRef.current && selectedItems.length > 0) { + wasSelectionOnRef.current = true; + } + if (selectedItems.length > 0 && !isMobileSelectionModeEnabled) { + turnOnMobileSelectionMode(); + } else if (selectedItems.length === 0 && isMobileSelectionModeEnabled && !wasSelectionOnRef.current) { + turnOffMobileSelectionMode(); + } + }, [isMobileSelectionModeEnabled, isSmallScreenWidth, isFocused, selectedItems.length]); + + useEffect( + () => () => { + if (selectionRef.current !== 0) { + return; + } + turnOffMobileSelectionMode(); + }, + [], + ); +} + +export default useHandleSelectionMode; From fba176ab8c90620b1eb88a7969f2cb97064d9026 Mon Sep 17 00:00:00 2001 From: daledah Date: Mon, 3 Nov 2025 16:39:24 +0700 Subject: [PATCH 2/3] fix regresioons --- .../MoneyRequestReportTransactionList.tsx | 2 ++ src/components/Search/SearchContext.tsx | 7 ------- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx index c801dbdf40b35..27fc0c593e9e4 100644 --- a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx +++ b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx @@ -17,6 +17,7 @@ import Text from '@components/Text'; import {WideRHPContext} from '@components/WideRHPContextProvider'; import useCopySelectionHelper from '@hooks/useCopySelectionHelper'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; +import useHandleSelectionMode from '@hooks/useHandleSelectionMode'; import useLocalize from '@hooks/useLocalize'; import useMobileSelectionMode from '@hooks/useMobileSelectionMode'; import useReportIsArchived from '@hooks/useReportIsArchived'; @@ -163,6 +164,7 @@ function MoneyRequestReportTransactionList({ }, [hasPendingDeletionTransaction, transactions]); const {selectedTransactionIDs, setSelectedTransactions, clearSelectedTransactions} = useSearchContext(); + useHandleSelectionMode(selectedTransactionIDs); const isMobileSelectionModeEnabled = useMobileSelectionMode(); const personalDetailsList = usePersonalDetails(); diff --git a/src/components/Search/SearchContext.tsx b/src/components/Search/SearchContext.tsx index 34063b5ed663e..782d0531e9314 100644 --- a/src/components/Search/SearchContext.tsx +++ b/src/components/Search/SearchContext.tsx @@ -1,5 +1,4 @@ import React, {useCallback, useContext, useMemo, useRef, useState} from 'react'; -import useHandleSelectionMode from '@hooks/useHandleSelectionMode'; import {isMoneyRequestReport} from '@libs/ReportUtils'; import {isTransactionListItemType, isTransactionReportGroupListItemType} from '@libs/SearchUIUtils'; import type {SearchKey} from '@libs/SearchUIUtils'; @@ -196,12 +195,6 @@ function SearchContextProvider({children}: ChildrenProps) { })); }, []); - const selectedItems = useMemo(() => { - return [...searchContextData.selectedTransactionIDs, ...searchContextData.selectedReports.map((item) => item.reportID)]; - }, [searchContextData.selectedReports, searchContextData.selectedTransactionIDs]); - - useHandleSelectionMode(selectedItems); - const searchContext = useMemo( () => ({ ...searchContextData, From 3d57c741bff3887aa89143ed0988651136f5b68e Mon Sep 17 00:00:00 2001 From: daledah Date: Tue, 4 Nov 2025 23:48:12 +0700 Subject: [PATCH 3/3] fix comments --- src/components/SelectionListWithModal/index.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/SelectionListWithModal/index.tsx b/src/components/SelectionListWithModal/index.tsx index f84f28a5663a4..a6f006728b673 100644 --- a/src/components/SelectionListWithModal/index.tsx +++ b/src/components/SelectionListWithModal/index.tsx @@ -43,16 +43,18 @@ function SelectionListWithModal( const isMobileSelectionModeEnabled = useMobileSelectionMode(); + const sectionData = sections[0]?.data; const selectedItems = useMemo( () => selectedItemsProp ?? - sections[0].data.filter((item) => { + sectionData?.filter((item) => { if (isSelected) { return isSelected(item); } return !!item.isSelected; - }), - [isSelected, sections, selectedItemsProp], + }) ?? + [], + [isSelected, sectionData, selectedItemsProp], ); useHandleSelectionMode(selectedItems);