Refactor transaction type handling to use a single card parameter ins…#84242
Conversation
…tead of card list. Update TypeCell and related tests accordingly.
…to callstack-internal/szymonzalarski/update-getTransactionType-to-take-only-necessary-card
|
@codex review |
|
Codex Review: Didn't find any major issues. What shall we delve into next? ℹ️ About Codex in GitHubCodex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback". |
7fcb2b2 to
ac02aa7
Compare
|
@eh2077 all yours |
|
@szymonzalarski98 do we have measurements for this? |
Reviewer Checklist
Screenshots/VideosAndroid: HybridAppScreen.Recording.2026-03-07.at.12.18.55.AM.movAndroid: mWeb ChromeScreen.Recording.2026-03-07.at.12.19.35.AM.moviOS: HybridAppScreen.Recording.2026-03-07.at.12.19.55.AM.moviOS: mWeb SafariScreen.Recording.2026-03-07.at.12.20.17.AM.movMacOS: Chrome / SafariScreen.Recording.2026-03-07.at.12.18.12.AM.mov |
|
@luacmartins I'll do the measurements and post them here |
|
@luacmartins Hey, here we have comaprison between our changes and main, there is no difference, the API is just simpler :)
The table is based on Reassure test, the code is here: /**
* Perf test for getTransactionType — OLD API (cardList).
* Run this on `main` branch:
* NODE_OPTIONS=--experimental-vm-modules npx reassure measure --testMatch="getTransactionType-main" --baseline --compare=false
*/
import {measureFunction} from 'reassure';
import CONST from '@src/CONST';
import type {CardList, Transaction} from '@src/types/onyx';
import {getTransactionType} from '../../src/libs/TransactionUtils';
import createRandomCard from '../utils/collections/card';
const CARD_LIST_SIZE = 500;
function generateTransaction(overrides: Partial<Transaction> = {}): Transaction {
return {
transactionID: '1',
reportID: '1',
amount: 100,
currency: 'USD',
comment: {},
created: '2024-01-01',
merchant: 'Test Merchant',
...overrides,
} as Transaction;
}
function createCardListFixture(size: number): CardList {
const cards: CardList = {};
for (let i = 1; i <= size; i++) {
cards[i] = createRandomCard(i);
}
return cards;
}
const cardList = createCardListFixture(CARD_LIST_SIZE);
const cashCardID = 999;
cardList[cashCardID] = {
...createRandomCard(cashCardID),
cardName: CONST.COMPANY_CARDS.CARD_NAME.CASH,
};
const cashTransaction = generateTransaction({cardID: cashCardID});
const cardTransaction = generateTransaction({cardID: 1, cardName: 'Some Card'});
const distanceTransaction = generateTransaction({
comment: {
type: CONST.TRANSACTION.TYPE.CUSTOM_UNIT,
customUnit: {name: CONST.CUSTOM_UNITS.NAME_DISTANCE},
},
merchant: '(none)',
});
describe('getTransactionType (old API — cardList)', () => {
test('[getTransactionType] cash transaction with cardList', async () => {
await measureFunction(() => getTransactionType(cashTransaction, cardList));
});
test('[getTransactionType] card transaction with cardList', async () => {
await measureFunction(() => getTransactionType(cardTransaction, cardList));
});
test('[getTransactionType] distance transaction', async () => {
await measureFunction(() => getTransactionType(distanceTransaction));
});
test('[getTransactionType] transaction without cardList', async () => {
await measureFunction(() => getTransactionType(cashTransaction));
});
});Our branch: /**
* Perf test for getTransactionType — NEW API (single card).
* Run this on the feature branch:
* NODE_OPTIONS=--experimental-vm-modules npx reassure measure --testMatch="getTransactionType.perf-test" --compare=false
*/
import {measureFunction} from 'reassure';
import CONST from '@src/CONST';
import type {Card, CardList, Transaction} from '@src/types/onyx';
import {getTransactionType} from '../../src/libs/TransactionUtils';
import createRandomCard from '../utils/collections/card';
const CARD_LIST_SIZE = 500;
function generateTransaction(overrides: Partial<Transaction> = {}): Transaction {
return {
transactionID: '1',
reportID: '1',
amount: 100,
currency: 'USD',
comment: {},
created: '2024-01-01',
merchant: 'Test Merchant',
...overrides,
} as Transaction;
}
function createCardListFixture(size: number): CardList {
const cards: CardList = {};
for (let i = 1; i <= size; i++) {
cards[i] = createRandomCard(i);
}
return cards;
}
const cardList = createCardListFixture(CARD_LIST_SIZE);
const cashCardID = 999;
const cashCard: Card = {
...createRandomCard(cashCardID),
cardName: CONST.COMPANY_CARDS.CARD_NAME.CASH,
};
cardList[cashCardID] = cashCard;
const cashTransaction = generateTransaction({cardID: cashCardID});
const cardTransaction = generateTransaction({cardID: 1, cardName: 'Some Card'});
const distanceTransaction = generateTransaction({
comment: {
type: CONST.TRANSACTION.TYPE.CUSTOM_UNIT,
customUnit: {name: CONST.CUSTOM_UNITS.NAME_DISTANCE},
},
merchant: '(none)',
});
describe('getTransactionType (new API — single card)', () => {
test('[getTransactionType] cash transaction with card', async () => {
await measureFunction(() => getTransactionType(cashTransaction, cashCard));
});
test('[getTransactionType] card transaction with card', async () => {
const card = cardList[1];
await measureFunction(() => getTransactionType(cardTransaction, card));
});
test('[getTransactionType] distance transaction', async () => {
await measureFunction(() => getTransactionType(distanceTransaction));
});
test('[getTransactionType] transaction without card', async () => {
await measureFunction(() => getTransactionType(cashTransaction));
});
}); |
|
Sounds good. Still seems like a good simplification |
eh2077
left a comment
There was a problem hiding this comment.
Looks good, confirmed replacing CardList with Card remains exiting functionality and simplifies the code.
|
✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release. |
|
🚧 @luacmartins has triggered a test Expensify/App build. You can view the workflow run here. |
|
🧪🧪 Use the links below to test this adhoc build on Android, iOS, and Web. Happy testing! 🧪🧪
|
|
🚀 Deployed to staging by https://github.com/luacmartins in version: 9.3.33-0 🚀
|
|
🚀 Deployed to production by https://github.com/luacmartins in version: 9.3.33-5 🚀
|

Update getTransactionType to accept only the necessary Card instead of the entire CardList. Update TypeCell to use an inline Onyx selector for fetching a single card, and update related tests accordingly.
Explanation of Change
getTransactionType previously accepted an optional CardList (the full card dictionary) as its second parameter, but internally only ever looked up a single card by transaction.cardID. This change narrows the parameter to accept only the specific Card object, moving the lookup responsibility to the caller.
In TypeCell, instead of subscribing to the entire CARD_LIST Onyx key, we now use an inline selector that extracts only the relevant card. This also improves performance — the component will only re-render when the specific card changes, not when any card in the list changes.
Fixed Issues
$ #83713
PROPOSAL:
Tests
Offline tests
QA Steps
Same as tests
PR Author Checklist
### Fixed Issuessection aboveTestssectionOffline stepssectionQA stepssectiontoggleReportand notonIconClick)src/languages/*files and using the translation methodSTYLE.md) were followedAvatar, I verified the components usingAvatarare working as expected)StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))npm run compress-svg)Avataris modified, I verified thatAvataris working as expected in all cases)Designlabel and/or tagged@Expensify/designso the design team can review the changes.ScrollViewcomponent to make it scrollable when more elements are added to the page.mainbranch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTeststeps.Screenshots/Videos
Android: Native
Android: mWeb Chrome
iOS: Native
iOS: mWeb Safari
MacOS: Chrome / Safari