Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions __mocks__/react-native-onyx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import type {ConnectOptions, OnyxKey} from 'react-native-onyx';

// eslint-disable-next-line no-restricted-imports
import Onyx, {useOnyx} from 'react-native-onyx';
import Onyx, {useOnyx, useOnyxState} from 'react-native-onyx';

let connectCallbackDelay = 0;
function addDelayToConnectCallback(delay: number) {
Expand Down Expand Up @@ -41,4 +41,4 @@ const reactNativeOnyxMock: ReactNativeOnyxMock = {

export default reactNativeOnyxMock;

export {useOnyx};
export {useOnyx, useOnyxState};
2 changes: 0 additions & 2 deletions src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1484,7 +1484,6 @@ const ONYXKEYS = {
OUTSTANDING_REPORTS_BY_POLICY_ID: 'outstandingReportsByPolicyID',
VISIBLE_REPORT_ACTIONS: 'visibleReportActions',
NON_PERSONAL_AND_WORKSPACE_CARD_LIST: 'nonPersonalAndWorkspaceCardList',
PERSONAL_AND_WORKSPACE_CARD_LIST: 'personalAndWorkspaceCardList',
CARD_FEED_ERRORS: 'cardFeedErrors',
RAM_ONLY_SORTED_REPORT_ACTIONS: 'sortedReportActions',
LOGIN_TO_ACCOUNT_ID_MAP: 'loginToAccountIDMap',
Expand Down Expand Up @@ -2085,7 +2084,6 @@ type OnyxDerivedValuesMapping = {
[ONYXKEYS.DERIVED.OUTSTANDING_REPORTS_BY_POLICY_ID]: OnyxTypes.OutstandingReportsByPolicyIDDerivedValue;
[ONYXKEYS.DERIVED.VISIBLE_REPORT_ACTIONS]: OnyxTypes.VisibleReportActionsDerivedValue;
[ONYXKEYS.DERIVED.NON_PERSONAL_AND_WORKSPACE_CARD_LIST]: OnyxTypes.NonPersonalAndWorkspaceCardListDerivedValue;
[ONYXKEYS.DERIVED.PERSONAL_AND_WORKSPACE_CARD_LIST]: OnyxTypes.PersonalAndWorkspaceCardListDerivedValue;
[ONYXKEYS.DERIVED.CARD_FEED_ERRORS]: OnyxTypes.CardFeedErrorsDerivedValue;
[ONYXKEYS.DERIVED.RAM_ONLY_SORTED_REPORT_ACTIONS]: OnyxTypes.SortedReportActionsDerivedValue;
[ONYXKEYS.DERIVED.LOGIN_TO_ACCOUNT_ID_MAP]: OnyxTypes.LoginToAccountIDMapDerivedValue;
Expand Down
3 changes: 2 additions & 1 deletion src/components/Search/SearchAutocompleteList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset';
import useLocalize from '@hooks/useLocalize';
import useOnyx from '@hooks/useOnyx';
import usePermissions from '@hooks/usePermissions';
import usePersonalAndWorkspaceCardList from '@hooks/usePersonalAndWorkspaceCardList';
import useReportAttributes from '@hooks/useReportAttributes';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useSortedReportActionsData from '@hooks/useSortedReportActionsData';
Expand Down Expand Up @@ -224,7 +225,7 @@ function SearchAutocompleteList({
const lastActions = sortedReportActionsData?.lastActions;
const personalDetails = usePersonalDetails();
const [reports] = useOnyx(ONYXKEYS.COLLECTION.REPORT);
const [personalAndWorkspaceCards] = useOnyx(ONYXKEYS.DERIVED.PERSONAL_AND_WORKSPACE_CARD_LIST);
const personalAndWorkspaceCards = usePersonalAndWorkspaceCardList();
const [allFeeds] = useOnyx(ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER);
const [bankAccountList] = useOnyx(ONYXKEYS.BANK_ACCOUNT_LIST);
const allCards = personalAndWorkspaceCards ?? CONST.EMPTY_OBJECT;
Expand Down
3 changes: 2 additions & 1 deletion src/components/Search/SearchRouter/SearchRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import useKeyboardShortcut from '@hooks/useKeyboardShortcut';
import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset';
import useLocalize from '@hooks/useLocalize';
import useOnyx from '@hooks/useOnyx';
import usePersonalAndWorkspaceCardList from '@hooks/usePersonalAndWorkspaceCardList';
import {useGetPersonalDetailsByLogin} from '@hooks/usePersonalDetailByLogin';
import useReportAttributes from '@hooks/useReportAttributes';
import useReportOrReportDraft from '@hooks/useReportOrReportDraft';
Expand Down Expand Up @@ -131,7 +132,7 @@ function SearchRouter({onRouterClose, shouldHideInputCaret, isSearchRouterDispla
const {currentSearchQueryJSON, currentSearchHash, currentSearchKey} = useSearchQueryContext();
const [reports] = useOnyx(ONYXKEYS.COLLECTION.REPORT);
const [policies] = useOnyx(ONYXKEYS.COLLECTION.POLICY);
const [personalAndWorkspaceCards] = useOnyx(ONYXKEYS.DERIVED.PERSONAL_AND_WORKSPACE_CARD_LIST);
const personalAndWorkspaceCards = usePersonalAndWorkspaceCardList();
const [allFeeds] = useOnyx(ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER);
const [bankAccountList] = useOnyx(ONYXKEYS.BANK_ACCOUNT_LIST);
const getPersonalDetailsByLogin = useGetPersonalDetailsByLogin();
Expand Down
5 changes: 2 additions & 3 deletions src/components/Search/hooks/useFilterCardValue.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import useLocalize from '@hooks/useLocalize';
import useOnyx from '@hooks/useOnyx';
import usePersonalAndWorkspaceCardList from '@hooks/usePersonalAndWorkspaceCardList';

import {getCardDescription} from '@libs/CardUtils';

import ONYXKEYS from '@src/ONYXKEYS';
import {filterCardsHiddenFromSearch} from '@src/selectors/Card';

function useFilterCardValue(value: string[]): string {
const {translate} = useLocalize();
const [searchCards] = useOnyx(ONYXKEYS.DERIVED.PERSONAL_AND_WORKSPACE_CARD_LIST, {selector: filterCardsHiddenFromSearch});
const searchCards = usePersonalAndWorkspaceCardList(filterCardsHiddenFromSearch);

const cardNames = Object.values(searchCards ?? {})
.filter((card) => value.includes(card.cardID.toString()))
Expand Down
3 changes: 2 additions & 1 deletion src/components/Search/hooks/useFilterFeedData.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import useFeedKeysWithAssignedCards from '@hooks/useFeedKeysWithAssignedCards';
import useLocalize from '@hooks/useLocalize';
import useOnyx from '@hooks/useOnyx';
import usePersonalAndWorkspaceCardList from '@hooks/usePersonalAndWorkspaceCardList';

import {getFeedOptions} from '@libs/SearchUIUtils';

Expand All @@ -9,7 +10,7 @@ import ONYXKEYS from '@src/ONYXKEYS';
function useFilterFeedData(value: string[] | undefined) {
const {translate, localeCompare} = useLocalize();
const [allFeeds] = useOnyx(ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER);
const [personalAndWorkspaceCards] = useOnyx(ONYXKEYS.DERIVED.PERSONAL_AND_WORKSPACE_CARD_LIST);
const personalAndWorkspaceCards = usePersonalAndWorkspaceCardList();
const [policies] = useOnyx(ONYXKEYS.COLLECTION.POLICY);
const [domains] = useOnyx(ONYXKEYS.COLLECTION.DOMAIN);
const [expensifyCardSettings] = useOnyx(ONYXKEYS.COLLECTION.PRIVATE_EXPENSIFY_CARD_SETTINGS);
Expand Down
3 changes: 2 additions & 1 deletion src/components/Search/hooks/useSearchSnapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import useLocalize from '@hooks/useLocalize';
import useMultipleSnapshots from '@hooks/useMultipleSnapshots';
import useNetwork from '@hooks/useNetwork';
import useOnyx from '@hooks/useOnyx';
import usePersonalAndWorkspaceCardList from '@hooks/usePersonalAndWorkspaceCardList';
import {useAllPersonalDetails} from '@hooks/usePersonalDetails';
import usePolicyForMovingExpenses from '@hooks/usePolicyForMovingExpenses';
import useReportAttributes from '@hooks/useReportAttributes';
Expand Down Expand Up @@ -169,7 +170,7 @@ function useSearchSnapshot({queryJSON, searchResults, transactions, reportAction
const [bankAccountList] = useOnyx(ONYXKEYS.BANK_ACCOUNT_LIST);
const [onyxPersonalDetailsList] = useAllPersonalDetails();
const [cardFeeds] = useOnyx(ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER);
const [personalAndWorkspaceCards] = useOnyx(ONYXKEYS.DERIVED.PERSONAL_AND_WORKSPACE_CARD_LIST);
const personalAndWorkspaceCards = usePersonalAndWorkspaceCardList();
const [nonPersonalAndWorkspaceCards] = useOnyx(ONYXKEYS.DERIVED.NON_PERSONAL_AND_WORKSPACE_CARD_LIST);
const [customCardNames] = useOnyx(ONYXKEYS.NVP_EXPENSIFY_COMPANY_CARDS_CUSTOM_NAMES);
const [conciergeReportID] = useOnyx(ONYXKEYS.CONCIERGE_REPORT_ID);
Expand Down
3 changes: 2 additions & 1 deletion src/hooks/useAdvancedSearchFilters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {emailSelector} from '@selectors/Session';
import useLocalize from './useLocalize';
import useOnyx from './useOnyx';
import usePermissions from './usePermissions';
import usePersonalAndWorkspaceCardList from './usePersonalAndWorkspaceCardList';
import useWorkspaceList from './useWorkspaceList';

/**
Expand Down Expand Up @@ -312,7 +313,7 @@ function shouldDisplayCardFilterSelector(cardList: OnyxEntry<CardList>) {
}

function useAdvancedSearchFilters(type: SearchDataTypes | undefined) {
const [shouldDisplayCardFilter] = useOnyx(ONYXKEYS.DERIVED.PERSONAL_AND_WORKSPACE_CARD_LIST, {selector: shouldDisplayCardFilterSelector});
const shouldDisplayCardFilter = usePersonalAndWorkspaceCardList(shouldDisplayCardFilterSelector);
const [policies = getEmptyObject<NonNullable<OnyxCollection<Policy>>>()] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {selector: advancedSearchPoliciesSelector});
const [policyDerived] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {selector: policyDerivedSelector});
const {isBetaEnabled} = usePermissions();
Expand Down
43 changes: 43 additions & 0 deletions src/hooks/usePersonalAndWorkspaceCardList.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import ONYXKEYS from '@src/ONYXKEYS';
import type {CardList} from '@src/types/onyx';

import {personalAndWorkspaceCardListSelector} from '@selectors/Card';
import {deepEqual} from 'fast-equals';
import {useOnyxState} from 'react-native-onyx';

type CardListSelector<TReturn> = (cardList: CardList) => TReturn;

const dependencies = [ONYXKEYS.CARD_LIST, ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST] as const;

// useOnyxState runs the selector on every write to either key, so each selector's output is kept per merged list
const selectedValues = new WeakMap<CardListSelector<unknown>, {cardList: CardList; value: unknown}>();

function select<TReturn>(selector: CardListSelector<TReturn>, cardList: CardList): TReturn {
const cached = selectedValues.get(selector);
if (cached?.cardList === cardList) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- each entry only ever holds its own selector's output
return cached.value as TReturn;
}
// One compare per selector shared by all consumers, so useOnyxState can compare outputs by identity
const fresh = selector(cardList);
const value = cached && deepEqual(cached.value, fresh) ? cached.value : fresh;
selectedValues.set(selector, {cardList, value});
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- each entry only ever holds its own selector's output
return value as TReturn;
}

// Pass a module-level selector: outputs are compared by identity, so a new selector each render returns a new object each render
function usePersonalAndWorkspaceCardList(): CardList;
function usePersonalAndWorkspaceCardList<TReturn>(selector: CardListSelector<TReturn>): TReturn;
function usePersonalAndWorkspaceCardList<TReturn>(selector?: CardListSelector<TReturn>) {
return useOnyxState(
(state) => {
const cardList = personalAndWorkspaceCardListSelector(state[ONYXKEYS.CARD_LIST], state[ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST]);
return selector ? select(selector, cardList) : cardList;
},
// Both the merge and select() return the previous object for equal content, so identity is enough
{dependencies, selectorEquality: (a, b) => a === b},
);
}

export default usePersonalAndWorkspaceCardList;
3 changes: 2 additions & 1 deletion src/hooks/useSearchSections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import useCurrentUserPersonalDetails from './useCurrentUserPersonalDetails';
import useFilterPendingDeleteReports from './useFilterPendingDeleteReports';
import useLocalize from './useLocalize';
import useOnyx from './useOnyx';
import usePersonalAndWorkspaceCardList from './usePersonalAndWorkspaceCardList';
import useReportAttributes from './useReportAttributes';

type UseSearchSectionsResult = {
Expand All @@ -28,7 +29,7 @@ function useSearchSections(): UseSearchSectionsResult {
const {convertToDisplayString} = useCurrencyListActions();

const [cardFeeds] = useOnyx(ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER);
const [personalAndWorkspaceCards] = useOnyx(ONYXKEYS.DERIVED.PERSONAL_AND_WORKSPACE_CARD_LIST);
const personalAndWorkspaceCards = usePersonalAndWorkspaceCardList();
const [nonPersonalAndWorkspaceCards] = useOnyx(ONYXKEYS.DERIVED.NON_PERSONAL_AND_WORKSPACE_CARD_LIST);
const [bankAccountList] = useOnyx(ONYXKEYS.BANK_ACCOUNT_LIST);

Expand Down
2 changes: 0 additions & 2 deletions src/libs/actions/OnyxDerived/ONYX_DERIVED_VALUES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import loginToAccountIDMapConfig from './configs/loginToAccountIDMap';
import nonPersonalAndWorkspaceCardListConfig from './configs/nonPersonalAndWorkspaceCardList';
import oneOnOneChatReportIDsConfig from './configs/oneOnOneChatReportIDs';
import outstandingReportsByPolicyIDConfig from './configs/outstandingReportsByPolicyID';
import personalAndWorkspaceCardListConfig from './configs/personalAndWorkspaceCardList';
import reportAttributesConfig from './configs/reportAttributes';
import reportTransactionsAndViolationsConfig from './configs/reportTransactionsAndViolations';
import sortedReportActionsConfig from './configs/sortedReportActions';
Expand All @@ -27,7 +26,6 @@ const ONYX_DERIVED_VALUES = {
[ONYXKEYS.DERIVED.OUTSTANDING_REPORTS_BY_POLICY_ID]: outstandingReportsByPolicyIDConfig,
[ONYXKEYS.DERIVED.VISIBLE_REPORT_ACTIONS]: visibleReportActionsConfig,
[ONYXKEYS.DERIVED.NON_PERSONAL_AND_WORKSPACE_CARD_LIST]: nonPersonalAndWorkspaceCardListConfig,
[ONYXKEYS.DERIVED.PERSONAL_AND_WORKSPACE_CARD_LIST]: personalAndWorkspaceCardListConfig,
[ONYXKEYS.DERIVED.CARD_FEED_ERRORS]: cardFeedErrorsConfig,
[ONYXKEYS.DERIVED.RAM_ONLY_SORTED_REPORT_ACTIONS]: sortedReportActionsConfig,
[ONYXKEYS.DERIVED.LOGIN_TO_ACCOUNT_ID_MAP]: loginToAccountIDMapConfig,
Expand Down

This file was deleted.

42 changes: 41 additions & 1 deletion src/selectors/Card.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
import {getExpensifyCardFeedsForDisplay} from '@libs/CardFeedUtils';
import {hasActiveExpensifyCard, hasAssignedCardMatching, isActiveCard, isCard, isCardHiddenFromSearch, isCSVFeedOrExpensifyCard, isExpensifyCard, isPersonalCard} from '@libs/CardUtils';
import {
hasActiveExpensifyCard,
hasAssignedCardMatching,
isActiveCard,
isCard,
isCardHiddenFromSearch,
isCSVFeedOrExpensifyCard,
isExpensifyCard,
isPersonalCard,
mergeCardListWithWorkspaceFeeds,
} from '@libs/CardUtils';
import {filterObject} from '@libs/ObjectUtils';

import CONST from '@src/CONST';
Expand All @@ -8,6 +18,8 @@ import type {CardFeeds, CardList, NonPersonalAndWorkspaceCardListDerivedValue, W

import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';

import {deepEqual} from 'fast-equals';

/**
* Builds a lightweight map of "${domainID}_${feedName}" keys that have card entries.
* A feed counts as having cards when:
Expand Down Expand Up @@ -87,6 +99,33 @@ const defaultExpensifyCardSelector = (allCards: OnyxEntry<NonPersonalAndWorkspac
return expensifyCardFeedsForDisplaySelector(allCards).at(0);
};

/**
* Merges the user's card list with every workspace card feed. The last pair of inputs and its result are kept, so
* every mounted consumer shares one merge per write. A merge equal to the last one returns the last object, so a
* write that leaves the merged list unchanged doesn't make consumers re-run their selectors.
*/
const createCardListWithWorkspaceFeedsSelector = (shouldFilterOutPersonalCards: boolean) => {
let lastCardList: OnyxEntry<CardList>;
let lastWorkspaceCardFeeds: OnyxCollection<WorkspaceCardsList>;
let lastResult: CardList | undefined;

return (cardList: OnyxEntry<CardList>, workspaceCardFeeds: OnyxCollection<WorkspaceCardsList>): CardList => {
if (lastResult && cardList === lastCardList && workspaceCardFeeds === lastWorkspaceCardFeeds) {
return lastResult;
}
lastCardList = cardList;
lastWorkspaceCardFeeds = workspaceCardFeeds;
const result = mergeCardListWithWorkspaceFeeds(workspaceCardFeeds ?? CONST.EMPTY_OBJECT, cardList, shouldFilterOutPersonalCards);
if (!lastResult || !deepEqual(result, lastResult)) {
lastResult = result;
}
return lastResult;
};
};

/** The user's cards, personal ones included, plus every workspace feed card. */
const personalAndWorkspaceCardListSelector = createCardListWithWorkspaceFeedsSelector(false);

/**
* Returns a selector that picks a single card from the card list by card ID.
*/
Expand Down Expand Up @@ -135,4 +174,5 @@ export {
companyCardCustomNamesSelector,
hasIssuedExpensifyCardSelector,
hasActiveExpensifyCardSelector,
personalAndWorkspaceCardListSelector,
};
6 changes: 0 additions & 6 deletions src/types/onyx/DerivedValues.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,11 +209,6 @@ type SortedReportActionsDerivedValue = {
transactionThreadIDs: Record<string, string | undefined>;
};

/**
* The derived value for merged personal and workspace card feeds.
*/
type PersonalAndWorkspaceCardListDerivedValue = CardList;

/**
* The derived value mapping each user's login (lowercased) to their accountID.
*
Expand Down Expand Up @@ -257,7 +252,6 @@ export type {
VisibleReportActionsDerivedValue,
SortedReportActionsDerivedValue,
NonPersonalAndWorkspaceCardListDerivedValue,
PersonalAndWorkspaceCardListDerivedValue,
CardFeedErrorsDerivedValue,
LoginToAccountIDMapDerivedValue,
GuideAccountIDsDerivedValue,
Expand Down
2 changes: 0 additions & 2 deletions src/types/onyx/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ import type {
NonPersonalAndWorkspaceCardListDerivedValue,
OneOnOneChatReportIDsDerivedValue,
OutstandingReportsByPolicyIDDerivedValue,
PersonalAndWorkspaceCardListDerivedValue,
ReportAttributesDerivedValue,
ReportTransactionsAndViolationsDerivedValue,
SortedReportActionsDerivedValue,
Expand Down Expand Up @@ -522,7 +521,6 @@ export type {
VisibleReportActionsDerivedValue,
SortedReportActionsDerivedValue,
NonPersonalAndWorkspaceCardListDerivedValue,
PersonalAndWorkspaceCardListDerivedValue,
CardFeedErrorsDerivedValue,
LoginToAccountIDMapDerivedValue,
GuideAccountIDsDerivedValue,
Expand Down
Loading
Loading